diff mbox series

[01/21] mongoose: Update to version 7.14

Message ID 20240615191941.40301-2-Michael.Glembotzki@iris-sensing.com
State Changes Requested
Headers show
Series Update Mongoose to 7.14 | expand

Commit Message

Michael Glembotzki June 15, 2024, 7:11 p.m. UTC
mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05

Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com>
---
 mongoose/mongoose.c | 21614 +++++++++++++++++++++++++++++++-----------
 mongoose/mongoose.h |  2003 +++-
 2 files changed, 17594 insertions(+), 6023 deletions(-)


+#include <sys/types.h>

+#include <time.h>

+

+#ifndef MG_IO_SIZE

+#define MG_IO_SIZE 1460

+#endif

+

+#endif // MG_ARCH == MG_ARCH_RTTHREAD

+
+
+#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
+    MG_ARCH == MG_ARCH_CMSIS_RTOS2
 
 #include <ctype.h>
 #include <errno.h>
@@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <alloca.h>
 #include <string.h>
 #include <time.h>
+#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
+#include "cmsis_os.h"  // keep this include
+// https://developer.arm.com/documentation/ka003821/latest
+extern uint32_t rt_time_get(void);
+#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
+#include "cmsis_os2.h"  // keep this include
+#endif
+
+#define strdup(s) mg_mprintf("%s", s)
+
+#if defined(__ARMCC_VERSION)
+#define mode_t size_t
+#define mkdir(a, b) mg_mkdir(a, b)
+static inline int mg_mkdir(const char *path, mode_t mode) {
+  (void) path, (void) mode;
+  return -1;
+}
+#endif
 
-#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP)
+#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) &&     \
+    !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \
+    (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
 #define MG_ENABLE_RL 1
+#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
+#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
+#endif
 #endif
 
 #endif
@@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
 #define MG_INVALID_SOCKET INVALID_SOCKET
 #define MG_SOCKET_TYPE SOCKET
 typedef unsigned long nfds_t;
-#define MG_SOCKET_ERRNO WSAGetLastError()
 #if defined(_MSC_VER)
 #pragma comment(lib, "ws2_32.lib")
 #ifndef alloca
@@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
 #endif
 #endif
 #define poll(a, b, c) WSAPoll((a), (b), (c))
-#ifndef SO_EXCLUSIVEADDRUSE
-#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
-#endif
 #define closesocket(x) closesocket(x)
 
 typedef int socklen_t;
@@ -423,16 +468,24 @@ typedef int socklen_t;
 #define MG_PATH_MAX FILENAME_MAX
 #endif
 
-#ifndef EINPROGRESS
-#define EINPROGRESS WSAEINPROGRESS
-#endif
-#ifndef EWOULDBLOCK
-#define EWOULDBLOCK WSAEWOULDBLOCK
+#ifndef SO_EXCLUSIVEADDRUSE
+#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
 #endif
 
+#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0)
+
+#define MG_SOCK_PENDING(errcode)                                            \
+  (((errcode) < 0) &&                                                       \
+   (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \
+    WSAGetLastError() == WSAEWOULDBLOCK))
+
+#define MG_SOCK_RESET(errcode) \
+  (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
+
 #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
-#define sleep(x) Sleep(x)
+#define sleep(x) Sleep((x) *1000)
 #define mkdir(a, b) _mkdir(a)
+#define timegm(x) _mkgmtime(x)
 
 #ifndef S_ISDIR
 #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
@@ -442,6 +495,10 @@ typedef int socklen_t;
 #define MG_ENABLE_DIRLIST 1
 #endif
 
+#ifndef SIGPIPE
+#define SIGPIPE 0
+#endif
+
 #endif
 
 
@@ -451,8 +508,9 @@ typedef int socklen_t;
 
 #include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <zephyr/net/socket.h>
+#include <zephyr/posix/fcntl.h>
+#include <zephyr/posix/sys/select.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -464,11 +522,18 @@ typedef int socklen_t;
 
 #define MG_PUTCHAR(x) printk("%c", x)
 #ifndef strdup
-#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
+#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
 #endif
 #define strerror(x) zsock_gai_strerror(x)
+
+#ifndef FD_CLOEXEC
 #define FD_CLOEXEC 0
+#endif
+
+#ifndef F_SETFD
 #define F_SETFD 0
+#endif
+
 #define MG_ENABLE_SSI 0
 
 int rand(void);
@@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
 
 #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
 
-#include <ctype.h>
-#include <errno.h>
 #include <limits.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <time.h>
-
-#include <FreeRTOS.h>
 #include <list.h>
-#include <task.h>
 
 #include <FreeRTOS_IP.h>
 #include <FreeRTOS_Sockets.h>
+#include <FreeRTOS_errno_TCP.h>  // contents to be moved and file removed, some day
 
 #define MG_SOCKET_TYPE Socket_t
 #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
@@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
 #define SO_ERROR 0
 #define SOL_SOCKET 0
 #define SO_REUSEADDR 0
+
+#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
+
+#define MG_SOCK_PENDING(errcode)                 \
+  ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
+   (errcode) == -pdFREERTOS_ERRNO_EISCONN ||     \
+   (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
+   (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
+
+#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
+
+// actually only if optional timeout is enabled
+#define MG_SOCK_INTR(fd) (fd == NULL)
+
 #define sockaddr_in freertos_sockaddr
 #define sockaddr freertos_sockaddr
 #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
@@ -543,8 +610,17 @@ static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
 
 
 #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
-#if defined(__GNUC__)
+
+#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
 #include <sys/stat.h>
+#endif
+
+struct timeval;
+
+#include <lwip/sockets.h>
+
+#if !LWIP_TIMEVAL_PRIVATE
+#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both
 #include <sys/time.h>
 #else
 struct timeval {
@@ -552,8 +628,7 @@ struct timeval {
   long tv_usec;
 };
 #endif
-
-#include <lwip/sockets.h>
+#endif
 
 #if LWIP_SOCKET != 1
 // Sockets support disabled in LWIP by default
@@ -565,16 +640,25 @@ struct timeval {
 #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
 #include <rl_net.h>
 
-#define MG_ENABLE_CUSTOM_MILLIS 1
 #define closesocket(x) closesocket(x)
-#define mkdir(a, b) (-1)
-#define EWOULDBLOCK BSD_EWOULDBLOCK
-#define EAGAIN BSD_EWOULDBLOCK
-#define EINPROGRESS BSD_EWOULDBLOCK
-#define EINTR BSD_EWOULDBLOCK
-#define ECONNRESET BSD_ECONNRESET
-#define EPIPE BSD_ECONNRESET
+
 #define TCP_NODELAY SO_KEEPALIVE
+
+#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
+
+#define MG_SOCK_PENDING(errcode)                                \
+  ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
+   (errcode) == BSD_EINPROGRESS)
+
+#define MG_SOCK_RESET(errcode) \
+  ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
+
+// In blocking mode, which is enabled by default, accept() waits for a
+// connection request. In non blocking mode, you must call accept()
+// again if the error code BSD_EWOULDBLOCK is returned.
+#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
+
+#define socklen_t int
 #endif
 
 
@@ -582,8 +666,12 @@ struct timeval {
 #define MG_ENABLE_LOG 1
 #endif
 
-#ifndef MG_ENABLE_MIP
-#define MG_ENABLE_MIP 0  // Mongoose built-in network stack
+#ifndef MG_ENABLE_CUSTOM_LOG
+#define MG_ENABLE_CUSTOM_LOG 0  // Let user define their own MG_LOG
+#endif
+
+#ifndef MG_ENABLE_TCPIP
+#define MG_ENABLE_TCPIP 0  // Mongoose built-in network stack
 #endif
 
 #ifndef MG_ENABLE_LWIP
@@ -599,7 +687,7 @@ struct timeval {
 #endif
 
 #ifndef MG_ENABLE_SOCKET
-#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
+#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
 #endif
 
 #ifndef MG_ENABLE_POLL
@@ -614,18 +702,6 @@ struct timeval {
 #define MG_ENABLE_FATFS 0
 #endif
 
-#ifndef MG_ENABLE_MBEDTLS
-#define MG_ENABLE_MBEDTLS 0
-#endif
-
-#ifndef MG_ENABLE_OPENSSL
-#define MG_ENABLE_OPENSSL 0
-#endif
-
-#ifndef MG_ENABLE_CUSTOM_TLS
-#define MG_ENABLE_CUSTOM_TLS 0
-#endif
-
 #ifndef MG_ENABLE_SSI
 #define MG_ENABLE_SSI 0
 #endif
@@ -634,6 +710,10 @@ struct timeval {
 #define MG_ENABLE_IPV6 0
 #endif
 
+#ifndef MG_IPV6_V6ONLY
+#define MG_IPV6_V6ONLY 0  // IPv6 socket binds only to V6, not V4 address
+#endif
+
 #ifndef MG_ENABLE_MD5
 #define MG_ENABLE_MD5 1
 #endif
@@ -659,12 +739,16 @@ struct timeval {
 #define MG_ENABLE_PACKED_FS 0
 #endif
 
+#ifndef MG_ENABLE_ASSERT
+#define MG_ENABLE_ASSERT 0
+#endif
+
 #ifndef MG_IO_SIZE
 #define MG_IO_SIZE 2048  // Granularity of the send/recv IO buffer growth
 #endif
 
 #ifndef MG_MAX_RECV_SIZE
-#define MG_MAX_RECV_SIZE (3 * 1024 * 1024)  // Maximum recv IO buffer size
+#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL)  // Maximum recv IO buffer size
 #endif
 
 #ifndef MG_DATA_SIZE
@@ -688,18 +772,18 @@ struct timeval {
 #endif
 
 #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
-#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
+#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
 #endif
 
 #ifndef MG_DIRSEP
 #define MG_DIRSEP '/'
 #endif
 
-#ifndef MG_ENABLE_FILE
+#ifndef MG_ENABLE_POSIX_FS
 #if defined(FOPEN_MAX)
-#define MG_ENABLE_FILE 1
+#define MG_ENABLE_POSIX_FS 1
 #else
-#define MG_ENABLE_FILE 0
+#define MG_ENABLE_POSIX_FS 0
 #endif
 #endif
 
@@ -732,60 +816,112 @@ struct timeval {
 #define MG_EPOLL_MOD(c, wr)
 #endif
 
+#ifndef MG_ENABLE_PROFILE
+#define MG_ENABLE_PROFILE 0
+#endif
 
+#ifndef MG_ENABLE_TCPIP_DRIVER_INIT    // mg_mgr_init() will also initialize
+#define MG_ENABLE_TCPIP_DRIVER_INIT 1  // enabled built-in driver for
+#endif                                 // Mongoose built-in network stack
 
+#ifndef MG_TCPIP_IP                      // e.g. MG_IPV4(192, 168, 0, 223)
+#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
+#endif
 
-struct mg_str {
-  const char *ptr;  // Pointer to string data
-  size_t len;       // String len
-};
+#ifndef MG_TCPIP_MASK
+#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
+#endif
+
+#ifndef MG_TCPIP_GW
+#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
+#endif
+
+#ifndef MG_SET_MAC_ADDRESS
+#define MG_SET_MAC_ADDRESS(mac)
+#endif
 
-#define MG_NULL_STR \
-  { NULL, 0 }
+#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
+#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
+#endif
 
-#define MG_C_STR(a) \
-  { (a), sizeof(a) - 1 }
+
+
+
+// Describes an arbitrary chunk of memory
+struct mg_str {
+  char *buf;   // String data
+  size_t len;  // String length
+};
 
 // Using macro to avoid shadowing C++ struct constructor, see #1298
 #define mg_str(s) mg_str_s(s)
 
 struct mg_str mg_str(const char *s);
 struct mg_str mg_str_n(const char *s, size_t n);
-int mg_lower(const char *s);
-int mg_ncasecmp(const char *s1, const char *s2, size_t len);
 int mg_casecmp(const char *s1, const char *s2);
-int mg_vcmp(const struct mg_str *s1, const char *s2);
-int mg_vcasecmp(const struct mg_str *str1, const char *str2);
 int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
-struct mg_str mg_strstrip(struct mg_str s);
-struct mg_str mg_strdup(const struct mg_str s);
-const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
+int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2);
 bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps);
-bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n);
-bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
-bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char delim);
-char *mg_hex(const void *buf, size_t len, char *dst);
-void mg_unhex(const char *buf, size_t len, unsigned char *to);
-unsigned long mg_unhexn(const char *s, size_t len);
-int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
-int64_t mg_to64(struct mg_str str);
-uint64_t mg_tou64(struct mg_str str);
-char *mg_remove_double_dots(char *s);
+bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char delim);
+
+bool mg_str_to_num(struct mg_str, int base, void *val, size_t val_len);
+
 
 
 
+// Single producer, single consumer non-blocking queue
+
+struct mg_queue {
+  char *buf;
+  size_t size;
+  volatile size_t tail;
+  volatile size_t head;
+};
+
+void mg_queue_init(struct mg_queue *, char *, size_t);        // Init queue
+size_t mg_queue_book(struct mg_queue *, char **buf, size_t);  // Reserve space
+void mg_queue_add(struct mg_queue *, size_t);                 // Add new message
+size_t mg_queue_next(struct mg_queue *, char **);  // Get oldest message
+void mg_queue_del(struct mg_queue *, size_t);      // Delete oldest message
 
 
-typedef void (*mg_pfn_t)(char, void *);                  // Custom putchar
+
+
+typedef void (*mg_pfn_t)(char, void *);                  // Output function
 typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *);  // %M printer
-void mg_pfn_iobuf(char ch, void *param);                 // iobuf printer
 
 size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *);
 size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...);
+
+
+
+
+
+
+// Convenience wrappers around mg_xprintf
 size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
 size_t mg_snprintf(char *, size_t, const char *fmt, ...);
 char *mg_vmprintf(const char *fmt, va_list *ap);
 char *mg_mprintf(const char *fmt, ...);
+size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *);
+size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
+
+// %M print helper functions
+size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap);
+size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap);
+
+// Various output functions
+void mg_pfn_iobuf(char ch, void *param);  // param: struct mg_iobuf *
+void mg_pfn_stdout(char c, void *param);  // param: ignored
+
+// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", MG_ESC("hi"))
+#define MG_ESC(str) mg_print_esc, 0, (str)
 
 
 
@@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
 
 
 enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
+extern int mg_log_level;  // Current log level, one of MG_LL_*
+
 void mg_log(const char *fmt, ...);
-bool mg_log_prefix(int ll, const char *file, int line, const char *fname);
-void mg_log_set(int log_level);
+void mg_log_prefix(int ll, const char *file, int line, const char *fname);
+// bool mg_log2(int ll, const char *file, int line, const char *fmt, ...);
 void mg_hexdump(const void *buf, size_t len);
 void mg_log_set_fn(mg_pfn_t fn, void *param);
 
+#define mg_log_set(level_) mg_log_level = (level_)
+
 #if MG_ENABLE_LOG
-#define MG_LOG(level, args)                                                \
-  do {                                                                     \
-    if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \
+#define MG_LOG(level, args)                                 \
+  do {                                                      \
+    if ((level) <= mg_log_level) {                          \
+      mg_log_prefix((level), __FILE__, __LINE__, __func__); \
+      mg_log args;                                          \
+    }                                                       \
   } while (0)
 #else
 #define MG_LOG(level, args) \
@@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
 // stat(), write(), read() calls.
 struct mg_fs {
   int (*st)(const char *path, size_t *size, time_t *mtime);  // stat file
-  void (*ls)(const char *path, void (*fn)(const char *, void *), void *);
+  void (*ls)(const char *path, void (*fn)(const char *, void *),
+             void *);  // List directory entries: call fn(file_name, fn_data)
+                       // for each directory entry
   void *(*op)(const char *path, int flags);             // Open file
   void (*cl)(void *fd);                                 // Close file
   size_t (*rd)(void *fd, void *buf, size_t len);        // Read file
@@ -877,28 +1022,84 @@ struct mg_fd {
 
 struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags);
 void mg_fs_close(struct mg_fd *fd);
-char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size);
+bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len);
+struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
 bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t);
 bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...);
 
+// Packed API
+const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
+const char *mg_unlist(size_t no);             // Get no'th packed filename
+struct mg_str mg_unpacked(const char *path);  // Packed file as mg_str
+
 
 
 
 
 
+
+#if MG_ENABLE_ASSERT
+#include <assert.h>
+#elif !defined(assert)
+#define assert(x)
+#endif
+
+void mg_bzero(volatile unsigned char *buf, size_t len);
 void mg_random(void *buf, size_t len);
 char *mg_random_str(char *buf, size_t len);
 uint16_t mg_ntohs(uint16_t net);
 uint32_t mg_ntohl(uint32_t net);
 uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
-uint64_t mg_millis(void);
+uint64_t mg_millis(void);  // Return milliseconds since boot
+uint64_t mg_now(void);     // Return milliseconds since Epoch
+bool mg_path_is_sane(const struct mg_str path);
 
 #define mg_htons(x) mg_ntohs(x)
 #define mg_htonl(x) mg_ntohl(x)
 
-#define MG_U32(a, b, c, d)                                      \
-  (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
-   ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
+#define MG_U32(a, b, c, d)                                           \
+  (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \
+   ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
+
+#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
+
+// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
+#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
+#define MG_IPADDR_PARTS(ADDR) \
+  MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
+
+#define MG_REG(x) ((volatile uint32_t *) (x))[0]
+#define MG_BIT(x) (((uint32_t) 1U) << (x))
+#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK)
+
+#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a)))
+#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
+
+#if defined(__GNUC__)
+#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
+#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
+#elif defined(__CCRH__)
+#define MG_RH850_DISABLE_IRQ() __DI()
+#define MG_RH850_ENABLE_IRQ() __EI()
+#else
+#define MG_ARM_DISABLE_IRQ()
+#define MG_ARM_ENABLE_IRQ()
+#endif
+
+#if defined(__CC_ARM)
+#define MG_DSB() __dsb(0xf)
+#elif defined(__ARMCC_VERSION)
+#define MG_DSB() __builtin_arm_dsb(0xf)
+#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
+#define MG_DSB() asm("DSB 0xf")
+#elif defined(__ICCARM__)
+#define MG_DSB() __iar_builtin_DSB()
+#else
+#define MG_DSB()
+#endif
+
+struct mg_addr;
+int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
 
 // Linked list management macros
 #define LIST_ADD_HEAD(type_, head_, elem_) \
@@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
 size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t);
 size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
 
-int mg_base64_update(unsigned char p, char *to, int len);
-int mg_base64_final(char *to, int len);
-int mg_base64_encode(const unsigned char *p, int n, char *to);
-int mg_base64_decode(const char *src, int n, char *dst);
+
+size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
+size_t mg_base64_final(char *buf, size_t len);
+size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
+size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
 
 
 
@@ -976,35 +1178,793 @@ typedef struct {
 void mg_sha1_init(mg_sha1_ctx *);
 void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len);
 void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
+// https://github.com/B-Con/crypto-algorithms
+// Author:     Brad Conte (brad AT bradconte.com)
+// Disclaimer: This code is presented "as is" without any guarantees.
+// Details:    Defines the API for the corresponding SHA1 implementation.
+// Copyright:  public domain
+
+
+
+
+
+typedef struct {
+  uint32_t state[8];
+  uint64_t bits;
+  uint32_t len;
+  unsigned char buffer[64];
+} mg_sha256_ctx;
+
+void mg_sha256_init(mg_sha256_ctx *);
+void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len);
+void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
+void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data,
+                    size_t datasz);
+#ifndef TLS_X15519_H
+#define TLS_X15519_H
+
+
+
+#define X25519_BYTES 32
+extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
+
+int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
+                  const uint8_t x1[X25519_BYTES], int clamp);
+
+
+#endif /* TLS_X15519_H */
+/******************************************************************************
+ *
+ * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+ *
+ * This is a simple and straightforward implementation of AES-GCM authenticated
+ * encryption. The focus of this work was correctness & accuracy. It is written
+ * in straight 'C' without any particular focus upon optimization or speed. It
+ * should be endian (memory byte order) neutral since the few places that care
+ * are handled explicitly.
+ *
+ * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
+ *
+ * It is intended for general purpose use, but was written in support of GRC's
+ * reference implementation of the SQRL (Secure Quick Reliable Login) client.
+ *
+ * See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
+ *         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \
+ *         gcm/gcm-revised-spec.pdf
+ *
+ * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+ * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+ *
+ *******************************************************************************/
+#ifndef TLS_AES128_H
+#define TLS_AES128_H
+
+typedef unsigned char uchar;  // add some convienent shorter types
+typedef unsigned int uint;
+
+/******************************************************************************
+ *  AES_CONTEXT : cipher context / holds inter-call data
+ ******************************************************************************/
+typedef struct {
+  int mode;          // 1 for Encryption, 0 for Decryption
+  int rounds;        // keysize-based rounds count
+  uint32_t *rk;      // pointer to current round key
+  uint32_t buf[68];  // key expansion buffer
+} aes_context;
+
+
+#define GCM_AUTH_FAILURE 0x55555555  // authentication failure
+
+/******************************************************************************
+ *  GCM_CONTEXT : MUST be called once before ANY use of this library
+ ******************************************************************************/
+int mg_gcm_initialize(void);
+
+//
+//  aes-gcm.h
+//  MKo
+//
+//  Created by Markus Kosmal on 20/11/14.
+//
+//
+int mg_aes_gcm_encrypt(unsigned char *output, const unsigned char *input,
+                       size_t input_length, const unsigned char *key,
+                       const size_t key_len, const unsigned char *iv,
+                       const size_t iv_len, unsigned char *aead,
+                       size_t aead_len, unsigned char *tag,
+                       const size_t tag_len);
+
+int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
+                       size_t input_length, const unsigned char *key,
+                       const size_t key_len, const unsigned char *iv,
+                       const size_t iv_len);
+
+#endif /* TLS_AES128_H */
+
+// End of aes128 PD
+
+
+
+#define MG_UECC_SUPPORTS_secp256r1 1
+/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */
+
+#ifndef _UECC_H_
+#define _UECC_H_
+
+/* Platform selection options.
+If MG_UECC_PLATFORM is not defined, the code will try to guess it based on
+compiler macros. Possible values for MG_UECC_PLATFORM are defined below: */
+#define mg_uecc_arch_other 0
+#define mg_uecc_x86 1
+#define mg_uecc_x86_64 2
+#define mg_uecc_arm 3
+#define mg_uecc_arm_thumb 4
+#define mg_uecc_arm_thumb2 5
+#define mg_uecc_arm64 6
+#define mg_uecc_avr 7
+
+/* If desired, you can define MG_UECC_WORD_SIZE as appropriate for your platform
+(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly defined then it will
+be automatically set based on your platform. */
+
+/* Optimization level; trade speed for code size.
+   Larger values produce code that is faster but larger.
+   Currently supported values are 0 - 4; 0 is unusably slow for most
+   applications. Optimization level 4 currently only has an effect ARM platforms
+   where more than one curve is enabled. */
+#ifndef MG_UECC_OPTIMIZATION_LEVEL
+#define MG_UECC_OPTIMIZATION_LEVEL 2
+#endif
+
+/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a
+specific function to be used for (scalar) squaring instead of the generic
+multiplication function. This can make things faster somewhat faster, but
+increases the code size. */
+#ifndef MG_UECC_SQUARE_FUNC
+#define MG_UECC_SQUARE_FUNC 0
+#endif
+
+/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will
+switch to native little-endian format for *all* arrays passed in and out of the
+public API. This includes public and private keys, shared secrets, signatures
+and message hashes. Using this switch reduces the amount of call stack memory
+used by uECC, since less intermediate translations are required. Note that this
+will *only* work on native little-endian processors and it will treat the
+uint8_t arrays passed into the public API as word arrays, therefore requiring
+the provided byte arrays to be word aligned on architectures that do not support
+unaligned accesses. IMPORTANT: Keys and signatures generated with
+MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures
+generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same
+endianness. */
+#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
+#endif
+
+/* Curve support selection. Set to 0 to remove that curve. */
+#ifndef MG_UECC_SUPPORTS_secp160r1
+#define MG_UECC_SUPPORTS_secp160r1 0
+#endif
+#ifndef MG_UECC_SUPPORTS_secp192r1
+#define MG_UECC_SUPPORTS_secp192r1 0
+#endif
+#ifndef MG_UECC_SUPPORTS_secp224r1
+#define MG_UECC_SUPPORTS_secp224r1 0
+#endif
+#ifndef MG_UECC_SUPPORTS_secp256r1
+#define MG_UECC_SUPPORTS_secp256r1 1
+#endif
+#ifndef MG_UECC_SUPPORTS_secp256k1
+#define MG_UECC_SUPPORTS_secp256k1 0
+#endif
+
+/* Specifies whether compressed point format is supported.
+   Set to 0 to disable point compression/decompression functions. */
+#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
+#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
+#endif
+
+struct MG_UECC_Curve_t;
+typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if MG_UECC_SUPPORTS_secp160r1
+MG_UECC_Curve mg_uecc_secp160r1(void);
+#endif
+#if MG_UECC_SUPPORTS_secp192r1
+MG_UECC_Curve mg_uecc_secp192r1(void);
+#endif
+#if MG_UECC_SUPPORTS_secp224r1
+MG_UECC_Curve mg_uecc_secp224r1(void);
+#endif
+#if MG_UECC_SUPPORTS_secp256r1
+MG_UECC_Curve mg_uecc_secp256r1(void);
+#endif
+#if MG_UECC_SUPPORTS_secp256k1
+MG_UECC_Curve mg_uecc_secp256k1(void);
+#endif
+
+/* MG_UECC_RNG_Function type
+The RNG function should fill 'size' random bytes into 'dest'. It should return 1
+if 'dest' was filled with random data, or 0 if the random data could not be
+generated. The filled-in values should be either truly random, or from a
+cryptographically-secure PRNG.
+
+A correctly functioning RNG function must be set (using mg_uecc_set_rng())
+before calling mg_uecc_make_key() or mg_uecc_sign().
+
+Setting a correctly functioning RNG function improves the resistance to
+side-channel attacks for mg_uecc_shared_secret() and
+mg_uecc_sign_deterministic().
+
+A correct RNG function is set by default when building for Windows, Linux, or OS
+X. If you are building on another POSIX-compliant system that supports
+/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
+RNG. For embedded platforms there is no predefined RNG function; you must
+provide your own.
+*/
+typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
+
+/* mg_uecc_set_rng() function.
+Set the function that will be used to generate random bytes. The RNG function
+should return 1 if the random data was generated, or 0 if the random data could
+not be generated.
+
+On platforms where there is no predefined RNG function (eg embedded platforms),
+this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
+
+Inputs:
+    rng_function - The function that will be used to generate random bytes.
+*/
+void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
+
+/* mg_uecc_get_rng() function.
+
+Returns the function that will be used to generate random bytes.
+*/
+MG_UECC_RNG_Function mg_uecc_get_rng(void);
+
+/* mg_uecc_curve_private_key_size() function.
+
+Returns the size of a private key for the curve in bytes.
+*/
+int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
+
+/* mg_uecc_curve_public_key_size() function.
+
+Returns the size of a public key for the curve in bytes.
+*/
+int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
+
+/* mg_uecc_make_key() function.
+Create a public/private key pair.
+
+Outputs:
+    public_key  - Will be filled in with the public key. Must be at least 2 *
+the curve size (in bytes) long. For example, if the curve is secp256r1,
+public_key must be 64 bytes long. private_key - Will be filled in with the
+private key. Must be as long as the curve order; this is typically the same as
+the curve size, except for secp160r1. For example, if the curve is secp256r1,
+private_key must be 32 bytes long.
+
+                  For secp160r1, private_key must be 21 bytes long! Note that
+the first byte will almost always be 0 (there is about a 1 in 2^80 chance of it
+being non-zero).
+
+Returns 1 if the key pair was generated successfully, 0 if an error occurred.
+*/
+int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
+                     MG_UECC_Curve curve);
+
+/* mg_uecc_shared_secret() function.
+Compute a shared secret given your secret key and someone else's public key. If
+the public key is not from a trusted source and has not been previously
+verified, you should verify it first using mg_uecc_valid_public_key(). Note: It
+is recommended that you hash the result of mg_uecc_shared_secret() before using
+it for symmetric encryption or HMAC.
+
+Inputs:
+    public_key  - The public key of the remote party.
+    private_key - Your private key.
+
+Outputs:
+    secret - Will be filled in with the shared secret value. Must be the same
+size as the curve size; for example, if the curve is secp256r1, secret must be
+32 bytes long.
+
+Returns 1 if the shared secret was generated successfully, 0 if an error
+occurred.
+*/
+int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
+                          uint8_t *secret, MG_UECC_Curve curve);
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+/* mg_uecc_compress() function.
+Compress a public key.
+
+Inputs:
+    public_key - The public key to compress.
+
+Outputs:
+    compressed - Will be filled in with the compressed public key. Must be at
+least (curve size + 1) bytes long; for example, if the curve is secp256r1,
+                 compressed must be 33 bytes long.
+*/
+void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
+                      MG_UECC_Curve curve);
+
+/* mg_uecc_decompress() function.
+Decompress a compressed public key.
+
+Inputs:
+    compressed - The compressed public key.
+
+Outputs:
+    public_key - Will be filled in with the decompressed public key.
+*/
+void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
+                        MG_UECC_Curve curve);
+#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
+
+/* mg_uecc_valid_public_key() function.
+Check to see if a public key is valid.
+
+Note that you are not required to check for a valid public key before using any
+other uECC functions. However, you may wish to avoid spending CPU time computing
+a shared secret or verifying a signature using an invalid public key.
+
+Inputs:
+    public_key - The public key to check.
+
+Returns 1 if the public key is valid, 0 if it is invalid.
+*/
+int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve);
+
+/* mg_uecc_compute_public_key() function.
+Compute the corresponding public key for a private key.
+
+Inputs:
+    private_key - The private key to compute the public key for
+
+Outputs:
+    public_key - Will be filled in with the corresponding public key
+
+Returns 1 if the key was computed successfully, 0 if an error occurred.
+*/
+int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key,
+                               MG_UECC_Curve curve);
+
+/* mg_uecc_sign() function.
+Generate an ECDSA signature for a given hash value.
+
+Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and
+pass it in to this function along with your private key.
+
+Inputs:
+    private_key  - Your private key.
+    message_hash - The hash of the message to sign.
+    hash_size    - The size of message_hash in bytes.
+
+Outputs:
+    signature - Will be filled in with the signature value. Must be at least 2 *
+curve size long. For example, if the curve is secp256r1, signature must be 64
+bytes long.
+
+Returns 1 if the signature generated successfully, 0 if an error occurred.
+*/
+int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash,
+                 unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
+
+/* MG_UECC_HashContext structure.
+This is used to pass in an arbitrary hash function to
+mg_uecc_sign_deterministic(). The structure will be used for multiple hash
+computations; each time a new hash is computed, init_hash() will be called,
+followed by one or more calls to update_hash(), and finally a call to
+finish_hash() to produce the resulting hash.
+
+The intention is that you will create a structure that includes
+MG_UECC_HashContext followed by any hash-specific data. For example:
+
+typedef struct SHA256_HashContext {
+    MG_UECC_HashContext uECC;
+    SHA256_CTX ctx;
+} SHA256_HashContext;
+
+void init_SHA256(MG_UECC_HashContext *base) {
+    SHA256_HashContext *context = (SHA256_HashContext *)base;
+    SHA256_Init(&context->ctx);
+}
+
+void update_SHA256(MG_UECC_HashContext *base,
+                   const uint8_t *message,
+                   unsigned message_size) {
+    SHA256_HashContext *context = (SHA256_HashContext *)base;
+    SHA256_Update(&context->ctx, message, message_size);
+}
+
+void finish_SHA256(MG_UECC_HashContext *base, uint8_t *hash_result) {
+    SHA256_HashContext *context = (SHA256_HashContext *)base;
+    SHA256_Final(hash_result, &context->ctx);
+}
+
+... when signing ...
+{
+    uint8_t tmp[32 + 32 + 64];
+    SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64,
+32, tmp}}; mg_uecc_sign_deterministic(key, message_hash, &ctx.uECC, signature);
+}
+*/
+typedef struct MG_UECC_HashContext {
+  void (*init_hash)(const struct MG_UECC_HashContext *context);
+  void (*update_hash)(const struct MG_UECC_HashContext *context,
+                      const uint8_t *message, unsigned message_size);
+  void (*finish_hash)(const struct MG_UECC_HashContext *context,
+                      uint8_t *hash_result);
+  unsigned
+      block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */
+  unsigned
+      result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */
+  uint8_t *tmp;    /* Must point to a buffer of at least (2 * result_size +
+                      block_size) bytes. */
+} MG_UECC_HashContext;
+
+/* mg_uecc_sign_deterministic() function.
+Generate an ECDSA signature for a given hash value, using a deterministic
+algorithm (see RFC 6979). You do not need to set the RNG using mg_uecc_set_rng()
+before calling this function; however, if the RNG is defined it will improve
+resistance to side-channel attacks.
+
+Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and
+pass it to this function along with your private key and a hash context. Note
+that the message_hash does not need to be computed with the same hash function
+used by hash_context.
+
+Inputs:
+    private_key  - Your private key.
+    message_hash - The hash of the message to sign.
+    hash_size    - The size of message_hash in bytes.
+    hash_context - A hash context to use.
+
+Outputs:
+    signature - Will be filled in with the signature value.
+
+Returns 1 if the signature generated successfully, 0 if an error occurred.
+*/
+int mg_uecc_sign_deterministic(const uint8_t *private_key,
+                               const uint8_t *message_hash, unsigned hash_size,
+                               const MG_UECC_HashContext *hash_context,
+                               uint8_t *signature, MG_UECC_Curve curve);
+
+/* mg_uecc_verify() function.
+Verify an ECDSA signature.
+
+Usage: Compute the hash of the signed data using the same hash as the signer and
+pass it to this function along with the signer's public key and the signature
+values (r and s).
+
+Inputs:
+    public_key   - The signer's public key.
+    message_hash - The hash of the signed data.
+    hash_size    - The size of message_hash in bytes.
+    signature    - The signature value.
+
+Returns 1 if the signature is valid, 0 if it is invalid.
+*/
+int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash,
+                   unsigned hash_size, const uint8_t *signature,
+                   MG_UECC_Curve curve);
+
+#ifdef __cplusplus
+} /* end of extern "C" */
+#endif
+
+#endif /* _UECC_H_ */
+
+/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
+
+#ifndef _UECC_VLI_H_
+#define _UECC_VLI_H_
+
+// 
+// 
+
+/* Functions for raw large-integer manipulation. These are only available
+   if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
+#ifndef MG_UECC_ENABLE_VLI_API
+#define MG_UECC_ENABLE_VLI_API 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if MG_UECC_ENABLE_VLI_API
+
+void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
+
+/* Constant-time comparison to zero - secure way to compare long integers */
+/* Returns 1 if vli == 0, 0 otherwise. */
+mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
+                                  wordcount_t num_words);
+
+/* Returns nonzero if bit 'bit' of vli is set. */
+mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, bitcount_t bit);
+
+/* Counts the number of bits required to represent vli. */
+bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
+                               const wordcount_t max_words);
+
+/* Sets dest = src. */
+void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t *src,
+                     wordcount_t num_words);
+
+/* Constant-time comparison function - secure way to compare long integers */
+/* Returns one if left == right, zero otherwise */
+mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
+                                 const mg_uecc_word_t *right,
+                                 wordcount_t num_words);
+
+/* Constant-time comparison function - secure way to compare long integers */
+/* Returns sign of left - right, in constant time. */
+cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
+                            const mg_uecc_word_t *right, wordcount_t num_words);
+
+/* Computes vli = vli >> 1. */
+void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t num_words);
+
+/* Computes result = left + right, returning carry. Can modify in place. */
+mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
+                               const mg_uecc_word_t *left,
+                               const mg_uecc_word_t *right,
+                               wordcount_t num_words);
+
+/* Computes result = left - right, returning borrow. Can modify in place. */
+mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
+                               const mg_uecc_word_t *left,
+                               const mg_uecc_word_t *right,
+                               wordcount_t num_words);
+
+/* Computes result = left * right. Result must be 2 * num_words long. */
+void mg_uecc_vli_mult(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                      const mg_uecc_word_t *right, wordcount_t num_words);
+
+/* Computes result = left^2. Result must be 2 * num_words long. */
+void mg_uecc_vli_square(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                        wordcount_t num_words);
+
+/* Computes result = (left + right) % mod.
+   Assumes that left < mod and right < mod, and that result does not overlap
+   mod. */
+void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                        const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
+                        wordcount_t num_words);
+
+/* Computes result = (left - right) % mod.
+   Assumes that left < mod and right < mod, and that result does not overlap
+   mod. */
+void mg_uecc_vli_modSub(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                        const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
+                        wordcount_t num_words);
+
+/* Computes result = product % mod, where product is 2N words long.
+   Currently only designed to work for mod == curve->p or curve_n. */
+void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t *product,
+                      const mg_uecc_word_t *mod, wordcount_t num_words);
+
+/* Calculates result = product (mod curve->p), where product is up to
+   2 * curve->num_words long. */
+void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product,
+                           MG_UECC_Curve curve);
+
+/* Computes result = (left * right) % mod.
+   Currently only designed to work for mod == curve->p or curve_n. */
+void mg_uecc_vli_modMult(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                         const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
+                         wordcount_t num_words);
+
+/* Computes result = (left * right) % curve->p. */
+void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
+                              const mg_uecc_word_t *left,
+                              const mg_uecc_word_t *right, MG_UECC_Curve curve);
+
+/* Computes result = left^2 % mod.
+   Currently only designed to work for mod == curve->p or curve_n. */
+void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const mg_uecc_word_t *left,
+                           const mg_uecc_word_t *mod, wordcount_t num_words);
+
+/* Computes result = left^2 % curve->p. */
+void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
+                                const mg_uecc_word_t *left,
+                                MG_UECC_Curve curve);
+
+/* Computes result = (1 / input) % mod.*/
+void mg_uecc_vli_modInv(mg_uecc_word_t *result, const mg_uecc_word_t *input,
+                        const mg_uecc_word_t *mod, wordcount_t num_words);
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+/* Calculates a = sqrt(a) (mod curve->p) */
+void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
+#endif
+
+/* Converts an integer in uECC native format to big-endian bytes. */
+void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
+                               const mg_uecc_word_t *native);
+/* Converts big-endian bytes to an integer in uECC native format. */
+void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const uint8_t *bytes,
+                               int num_bytes);
+
+unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
+unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
+unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
+unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
+unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
+unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
+
+const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
+const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
+const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
+const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
+
+int mg_uecc_valid_point(const mg_uecc_word_t *point, MG_UECC_Curve curve);
+
+/* Multiplies a point by a scalar. Points are represented by the X coordinate
+   followed by the Y coordinate in the same array, both coordinates are
+   curve->num_words long. Note that scalar must be curve->num_n_words long (NOT
+   curve->num_words). */
+void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
+                        const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
+
+/* Generates a random integer in the range 0 < random < top.
+   Both random and top have num_words words. */
+int mg_uecc_generate_random_int(mg_uecc_word_t *random,
+                                const mg_uecc_word_t *top,
+                                wordcount_t num_words);
+
+#endif /* MG_UECC_ENABLE_VLI_API */
+
+#ifdef __cplusplus
+} /* end of extern "C" */
+#endif
+
+#endif /* _UECC_VLI_H_ */
+
+/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
+
+#ifndef _UECC_TYPES_H_
+#define _UECC_TYPES_H_
+
+#ifndef MG_UECC_PLATFORM
+#if defined(__AVR__) && __AVR__
+#define MG_UECC_PLATFORM mg_uecc_avr
+#elif defined(__thumb2__) || \
+    defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
+#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
+#elif defined(__thumb__)
+#define MG_UECC_PLATFORM mg_uecc_arm_thumb
+#elif defined(__arm__) || defined(_M_ARM)
+#define MG_UECC_PLATFORM mg_uecc_arm
+#elif defined(__aarch64__)
+#define MG_UECC_PLATFORM mg_uecc_arm64
+#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
+    defined(__I86__)
+#define MG_UECC_PLATFORM mg_uecc_x86
+#elif defined(__amd64__) || defined(_M_X64)
+#define MG_UECC_PLATFORM mg_uecc_x86_64
+#else
+#define MG_UECC_PLATFORM mg_uecc_arch_other
+#endif
+#endif
+
+#ifndef MG_UECC_ARM_USE_UMAAL
+#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
+#define MG_UECC_ARM_USE_UMAAL 1
+#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \
+    (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
+#define MG_UECC_ARM_USE_UMAAL 1
+#else
+#define MG_UECC_ARM_USE_UMAAL 0
+#endif
+#endif
+
+#ifndef MG_UECC_WORD_SIZE
+#if MG_UECC_PLATFORM == mg_uecc_avr
+#define MG_UECC_WORD_SIZE 1
+#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64)
+#define MG_UECC_WORD_SIZE 8
+#else
+#define MG_UECC_WORD_SIZE 4
+#endif
+#endif
+
+#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
+    (MG_UECC_WORD_SIZE != 8)
+#error "Unsupported value for MG_UECC_WORD_SIZE"
+#endif
+
+#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
+#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
+#undef MG_UECC_WORD_SIZE
+#define MG_UECC_WORD_SIZE 1
+#endif
+
+#if ((MG_UECC_PLATFORM == mg_uecc_arm ||         \
+      MG_UECC_PLATFORM == mg_uecc_arm_thumb ||   \
+      MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
+     (MG_UECC_WORD_SIZE != 4))
+#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
+#undef MG_UECC_WORD_SIZE
+#define MG_UECC_WORD_SIZE 4
+#endif
+
+typedef int8_t wordcount_t;
+typedef int16_t bitcount_t;
+typedef int8_t cmpresult_t;
+
+#if (MG_UECC_WORD_SIZE == 1)
+
+typedef uint8_t mg_uecc_word_t;
+typedef uint16_t mg_uecc_dword_t;
+
+#define HIGH_BIT_SET 0x80
+#define MG_UECC_WORD_BITS 8
+#define MG_UECC_WORD_BITS_SHIFT 3
+#define MG_UECC_WORD_BITS_MASK 0x07
+
+#elif (MG_UECC_WORD_SIZE == 4)
+
+typedef uint32_t mg_uecc_word_t;
+typedef uint64_t mg_uecc_dword_t;
+
+#define HIGH_BIT_SET 0x80000000
+#define MG_UECC_WORD_BITS 32
+#define MG_UECC_WORD_BITS_SHIFT 5
+#define MG_UECC_WORD_BITS_MASK 0x01F
+
+#elif (MG_UECC_WORD_SIZE == 8)
+
+typedef uint64_t mg_uecc_word_t;
+
+#define HIGH_BIT_SET 0x8000000000000000U
+#define MG_UECC_WORD_BITS 64
+#define MG_UECC_WORD_BITS_SHIFT 6
+#define MG_UECC_WORD_BITS_MASK 0x03F
+
+#endif /* MG_UECC_WORD_SIZE */
+
+#endif /* _UECC_TYPES_H_ */
+// End of uecc BSD-2
 
 
 struct mg_connection;
 typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
-                                   void *ev_data, void *fn_data);
+                                   void *ev_data);
 void mg_call(struct mg_connection *c, int ev, void *ev_data);
 void mg_error(struct mg_connection *c, const char *fmt, ...);
 
 enum {
-  MG_EV_ERROR,       // Error                        char *error_message
-  MG_EV_OPEN,        // Connection created           NULL
-  MG_EV_POLL,        // mg_mgr_poll iteration        uint64_t *uptime_millis
-  MG_EV_RESOLVE,     // Host name is resolved        NULL
-  MG_EV_CONNECT,     // Connection established       NULL
-  MG_EV_ACCEPT,      // Connection accepted          NULL
-  MG_EV_TLS_HS,      // TLS handshake succeeded      NULL
-  MG_EV_READ,        // Data received from socket    long *bytes_read
-  MG_EV_WRITE,       // Data written to socket       long *bytes_written
-  MG_EV_CLOSE,       // Connection closed            NULL
-  MG_EV_HTTP_MSG,    // HTTP request/response        struct mg_http_message *
-  MG_EV_HTTP_CHUNK,  // HTTP chunk (partial msg)     struct mg_http_message *
-  MG_EV_WS_OPEN,     // Websocket handshake done     struct mg_http_message *
-  MG_EV_WS_MSG,      // Websocket msg, text or bin   struct mg_ws_message *
-  MG_EV_WS_CTL,      // Websocket control msg        struct mg_ws_message *
-  MG_EV_MQTT_CMD,    // MQTT low-level command       struct mg_mqtt_message *
-  MG_EV_MQTT_MSG,    // MQTT PUBLISH received        struct mg_mqtt_message *
-  MG_EV_MQTT_OPEN,   // MQTT CONNACK received        int *connack_status_code
-  MG_EV_SNTP_TIME,   // SNTP time received           uint64_t *epoch_millis
-  MG_EV_USER         // Starting ID for user events
+  MG_EV_ERROR,      // Error                        char *error_message
+  MG_EV_OPEN,       // Connection created           NULL
+  MG_EV_POLL,       // mg_mgr_poll iteration        uint64_t *uptime_millis
+  MG_EV_RESOLVE,    // Host name is resolved        NULL
+  MG_EV_CONNECT,    // Connection established       NULL
+  MG_EV_ACCEPT,     // Connection accepted          NULL
+  MG_EV_TLS_HS,     // TLS handshake succeeded      NULL
+  MG_EV_READ,       // Data received from socket    long *bytes_read
+  MG_EV_WRITE,      // Data written to socket       long *bytes_written
+  MG_EV_CLOSE,      // Connection closed            NULL
+  MG_EV_HTTP_HDRS,  // HTTP headers                 struct mg_http_message *
+  MG_EV_HTTP_MSG,   // Full HTTP request/response   struct mg_http_message *
+  MG_EV_WS_OPEN,    // Websocket handshake done     struct mg_http_message *
+  MG_EV_WS_MSG,     // Websocket msg, text or bin   struct mg_ws_message *
+  MG_EV_WS_CTL,     // Websocket control msg        struct mg_ws_message *
+  MG_EV_MQTT_CMD,   // MQTT low-level command       struct mg_mqtt_message *
+  MG_EV_MQTT_MSG,   // MQTT PUBLISH received        struct mg_mqtt_message *
+  MG_EV_MQTT_OPEN,  // MQTT CONNACK received        int *connack_status_code
+  MG_EV_SNTP_TIME,  // SNTP time received           uint64_t *epoch_millis
+  MG_EV_WAKEUP,     // mg_wakeup() data received    struct mg_str *data
+  MG_EV_USER        // Starting ID for user events
 };
 
 
@@ -1021,10 +1981,10 @@ struct mg_dns {
 };
 
 struct mg_addr {
-  uint16_t port;    // TCP or UDP port in network byte order
-  uint32_t ip;      // IP address in network byte order
-  uint8_t ip6[16];  // IPv6 address
-  bool is_ip6;      // True when address is IPv6 address
+  uint8_t ip[16];    // Holds IPv4 or IPv6 address, in network byte order
+  uint16_t port;     // TCP or UDP port in network byte order
+  uint8_t scope_id;  // IPv6 scope ID
+  bool is_ip6;       // True when address is IPv6 address
 };
 
 struct mg_mgr {
@@ -1036,12 +1996,14 @@ struct mg_mgr {
   unsigned long nextid;         // Next connection ID
   unsigned long timerid;        // Next timer ID
   void *userdata;               // Arbitrary user data pointer
+  void *tls_ctx;                // TLS context shared by all TLS sessions
   uint16_t mqtt_id;             // MQTT IDs for pub/sub
   void *active_dns_requests;    // DNS requests in progress
   struct mg_timer *timers;      // Active timers
   int epoll_fd;                 // Used when MG_EPOLL_ENABLE=1
   void *priv;                   // Used by the MIP stack
   size_t extraconnsize;         // Used by the MIP stack
+  MG_SOCKET_TYPE pipe;          // Socketpair end for mg_wakeup()
 #if MG_ENABLE_FREERTOS_TCP
   SocketSet_t ss;  // NOTE(lsm): referenced from socket struct
 #endif
@@ -1056,6 +2018,8 @@ struct mg_connection {
   unsigned long id;            // Auto-incrementing unique connection ID
   struct mg_iobuf recv;        // Incoming data
   struct mg_iobuf send;        // Outgoing data
+  struct mg_iobuf prof;        // Profile data enabled by MG_ENABLE_PROFILE
+  struct mg_iobuf rtls;        // TLS only. Incoming encrypted data
   mg_event_handler_t fn;       // User-specified event handler function
   void *fn_data;               // User-specified function parameter
   mg_event_handler_t pfn;      // Protocol-specific handler function
@@ -1066,6 +2030,7 @@ struct mg_connection {
   unsigned is_client : 1;      // Outbound (client) connection
   unsigned is_accepted : 1;    // Accepted (server) connection
   unsigned is_resolving : 1;   // Non-blocking DNS resolution is in progress
+  unsigned is_arplooking : 1;  // Non-blocking ARP resolution is in progress
   unsigned is_connecting : 1;  // Non-blocking connect is in progress
   unsigned is_tls : 1;         // TLS-enabled connection
   unsigned is_tls_hs : 1;      // TLS handshake is in progress
@@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *, const void *, size_t);
 size_t mg_printf(struct mg_connection *, const char *fmt, ...);
 size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap);
 bool mg_aton(struct mg_str str, struct mg_addr *addr);
-int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp);
 
 // These functions are used to integrate with custom network stacks
 struct mg_connection *mg_alloc_conn(struct mg_mgr *);
 void mg_close_conn(struct mg_connection *c);
 bool mg_open_listener(struct mg_connection *c, const char *url);
+
+// Utility functions
+bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len);
+bool mg_wakeup_init(struct mg_mgr *);
 struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
                               unsigned flags, void (*fn)(void *), void *arg);
 
-// Low-level IO primives used by TLS layer
-enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
-long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
-
 
 
 
@@ -1127,7 +2090,6 @@ struct mg_http_message {
   struct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers
   struct mg_str body;                                  // Body
   struct mg_str head;                                  // Request + headers
-  struct mg_str chunk;    // Chunk for chunked encoding,  or partial body
   struct mg_str message;  // Request + headers + body
 };
 
@@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t);
 int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form);
 size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len);
 void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
-bool mg_http_match_uri(const struct mg_http_message *, const char *glob);
 long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
-                    struct mg_fs *fs, const char *path, size_t max_size);
+                    struct mg_fs *fs, const char *dir, size_t max_size);
 void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
 struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
 size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
@@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root,
                        const char *fullpath);
 
 
+#define MG_TLS_NONE 0     // No TLS support
+#define MG_TLS_MBED 1     // mbedTLS
+#define MG_TLS_OPENSSL 2  // OpenSSL
+#define MG_TLS_WOLFSSL 5  // WolfSSL (based on OpenSSL)
+#define MG_TLS_BUILTIN 3  // Built-in
+#define MG_TLS_CUSTOM 4   // Custom implementation
+
+#ifndef MG_TLS
+#define MG_TLS MG_TLS_NONE
+#endif
+
 
 
 
 
 struct mg_tls_opts {
-  const char *ca;         // CA certificate file. For both listeners and clients
-  const char *crl;        // Certificate Revocation List. For clients
-  const char *cert;       // Certificate
-  const char *certkey;    // Certificate key
-  const char *ciphers;    // Cipher list
-  struct mg_str srvname;  // If not empty, enables server name verification
-  struct mg_fs *fs;       // FS API for reading certificate files
+  struct mg_str ca;       // PEM or DER
+  struct mg_str cert;     // PEM or DER
+  struct mg_str key;      // PEM or DER
+  struct mg_str name;     // If not empty, enable host name verification
+  int skip_verification;  // Skip certificate and host name verification
 };
 
-void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *);
+void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts);
 void mg_tls_free(struct mg_connection *);
 long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
 long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
 size_t mg_tls_pending(struct mg_connection *);
 void mg_tls_handshake(struct mg_connection *);
 
+// Private
+void mg_tls_ctx_init(struct mg_mgr *);
+void mg_tls_ctx_free(struct mg_mgr *);
+
+// Low-level IO primives used by TLS layer
+enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
+long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
+long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
 
 
 
 
 
 
-#if MG_ENABLE_MBEDTLS
+
+#if MG_TLS == MG_TLS_MBED
 #include <mbedtls/debug.h>
 #include <mbedtls/net_sockets.h>
 #include <mbedtls/ssl.h>
+#include <mbedtls/ssl_ticket.h>
+
+struct mg_tls_ctx {
+  int dummy;
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+  mbedtls_ssl_ticket_context tickets;
+#endif
+};
 
 struct mg_tls {
-  char *cafile;             // CA certificate path
   mbedtls_x509_crt ca;      // Parsed CA certificate
   mbedtls_x509_crt cert;    // Parsed certificate
+  mbedtls_pk_context pk;    // Private key context
   mbedtls_ssl_context ssl;  // SSL/TLS context
   mbedtls_ssl_config conf;  // SSL-TLS config
-  mbedtls_pk_context pk;    // Private key context
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+  mbedtls_ssl_ticket_context ticket;  // Session tickets context
+#endif
 };
 #endif
 
 
-#if MG_ENABLE_OPENSSL
+#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
 
-#ifdef CONFIG_SSL_IMPL_WOLFSSL
-#include <wolfssl/openssl/err.h>
-#include <wolfssl/openssl/ssl.h>
-#else
 #include <openssl/err.h>
 #include <openssl/ssl.h>
-#endif
 
 struct mg_tls {
+  BIO_METHOD *bm;
   SSL_CTX *ctx;
   SSL *ssl;
 };
@@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char *buf, size_t len);
 #define MQTT_CMD_DISCONNECT 14
 #define MQTT_CMD_AUTH 15
 
+#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
+#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
+#define MQTT_PROP_CONTENT_TYPE 0x03
+#define MQTT_PROP_RESPONSE_TOPIC 0x08
+#define MQTT_PROP_CORRELATION_DATA 0x09
+#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
+#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
+#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
+#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
+#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
+#define MQTT_PROP_AUTHENTICATION_DATA 0x16
+#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
+#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
+#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
+#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
+#define MQTT_PROP_SERVER_REFERENCE 0x1C
+#define MQTT_PROP_REASON_STRING 0x1F
+#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
+#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
+#define MQTT_PROP_TOPIC_ALIAS 0x23
+#define MQTT_PROP_MAXIMUM_QOS 0x24
+#define MQTT_PROP_RETAIN_AVAILABLE 0x25
+#define MQTT_PROP_USER_PROPERTY 0x26
+#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
+#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
+#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
+#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
+
+enum {
+  MQTT_PROP_TYPE_BYTE,
+  MQTT_PROP_TYPE_STRING,
+  MQTT_PROP_TYPE_STRING_PAIR,
+  MQTT_PROP_TYPE_BINARY_DATA,
+  MQTT_PROP_TYPE_VARIABLE_INT,
+  MQTT_PROP_TYPE_INT,
+  MQTT_PROP_TYPE_SHORT
+};
+
 enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
 
+struct mg_mqtt_prop {
+  uint8_t id;         // Enumerated at MQTT5 Reference
+  uint32_t iv;        // Integer value for 8-, 16-, 32-bit integers types
+  struct mg_str key;  // Non-NULL only for user property type
+  struct mg_str val;  // Non-NULL only for UTF-8 types and user properties
+};
+
 struct mg_mqtt_opts {
-  struct mg_str user;          // Username, can be empty
-  struct mg_str pass;          // Password, can be empty
-  struct mg_str client_id;     // Client ID
-  struct mg_str will_topic;    // Will topic
-  struct mg_str will_message;  // Will message
-  uint8_t will_qos;            // Will message quality of service
-  uint8_t version;             // Can be 4 (3.1.1), or 5. If 0, assume 4.
-  uint16_t keepalive;          // Keep-alive timer in seconds
-  bool will_retain;            // Retain last will
-  bool clean;                  // Use clean session, 0 or 1
+  struct mg_str user;               // Username, can be empty
+  struct mg_str pass;               // Password, can be empty
+  struct mg_str client_id;          // Client ID
+  struct mg_str topic;              // message/subscription topic
+  struct mg_str message;            // message content
+  uint8_t qos;                      // message quality of service
+  uint8_t version;                  // Can be 4 (3.1.1), or 5. If 0, assume 4
+  uint16_t keepalive;               // Keep-alive timer in seconds
+  uint16_t retransmit_id;           // For PUBLISH, init to 0
+  bool retain;                      // Retain flag
+  bool clean;                       // Clean session flag
+  struct mg_mqtt_prop *props;       // MQTT5 props array
+  size_t num_props;                 // number of props
+  struct mg_mqtt_prop *will_props;  // Valid only for CONNECT packet (MQTT5)
+  size_t num_will_props;            // Number of will props
 };
 
 struct mg_mqtt_message {
-  struct mg_str topic;  // Parsed topic
-  struct mg_str data;   // Parsed message
-  struct mg_str dgram;  // Whole MQTT datagram, including headers
-  uint16_t id;  // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
-  uint8_t cmd;  // MQTT command, one of MQTT_CMD_*
-  uint8_t qos;  // Quality of service
-  uint8_t ack;  // Connack return code. 0 - success
+  struct mg_str topic;  // Parsed topic for PUBLISH
+  struct mg_str data;   // Parsed message for PUBLISH
+  struct mg_str dgram;  // Whole MQTT packet, including headers
+  uint16_t id;          // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
+  uint8_t cmd;          // MQTT command, one of MQTT_CMD_*
+  uint8_t qos;          // Quality of service
+  uint8_t ack;          // CONNACK return code, 0 = success
+  size_t props_start;   // Offset to the start of the properties (MQTT5)
+  size_t props_size;    // Length of the properties
 };
 
 struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
@@ -1327,15 +2364,16 @@ struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
 struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
                                      mg_event_handler_t fn, void *fn_data);
 void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts);
-void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
-                 struct mg_str data, int qos, bool retain);
-void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos);
+uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts);
+void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts);
 int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *);
 void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags,
                          uint32_t len);
 void mg_mqtt_ping(struct mg_connection *);
 void mg_mqtt_pong(struct mg_connection *);
-void mg_mqtt_disconnect(struct mg_connection *);
+void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *);
+size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *,
+                         size_t ofs);
 
 
 
@@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
 enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 };
 int mg_json_get(struct mg_str json, const char *path, int *toklen);
 
+struct mg_str mg_json_get_tok(struct mg_str json, const char *path);
 bool mg_json_get_num(struct mg_str json, const char *path, double *v);
 bool mg_json_get_bool(struct mg_str json, const char *path, bool *v);
 long mg_json_get_long(struct mg_str json, const char *path, long dflt);
@@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json, const char *path);
 char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
 char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
 
+bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
+size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
+                    struct mg_str *val);
+
 
 
 
@@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
 void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
 void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
 void mg_rpc_list(struct mg_rpc_req *r);
+// Copyright (c) 2023 Cesanta Software Limited
+// All rights reserved
 
 
 
 
 
-struct mip_if;  // MIP network interface
+#define MG_OTA_NONE 0      // No OTA support
+#define MG_OTA_FLASH 1     // OTA via an internal flash
+#define MG_OTA_ESP32 2     // ESP32 OTA implementation
+#define MG_OTA_CUSTOM 100  // Custom implementation
 
-struct mip_driver {
-  bool (*init)(struct mip_if *);                         // Initialise driver
-  size_t (*tx)(const void *, size_t, struct mip_if *);   // Transmit frame
-  size_t (*rx)(void *buf, size_t len, struct mip_if *);  // Receive frame (poll)
-  bool (*up)(struct mip_if *);                           // Up/down status
-};
+#ifndef MG_OTA
+#define MG_OTA MG_OTA_NONE
+#endif
+
+#if defined(__GNUC__) && !defined(__APPLE__)
+#define MG_IRAM __attribute__((section(".iram")))
+#else
+#define MG_IRAM
+#endif
 
-// Receive queue - single producer, single consumer queue.  Interrupt-based
-// drivers copy received frames to the queue in interrupt context. mip_poll()
-// function runs in event loop context, reads from the queue
-struct queue {
-  uint8_t *buf;
-  size_t len;
-  volatile size_t tail, head;
+// Firmware update API
+bool mg_ota_begin(size_t new_firmware_size);     // Start writing
+bool mg_ota_write(const void *buf, size_t len);  // Write chunk, aligned to 1k
+bool mg_ota_end(void);                           // Stop writing
+
+enum {
+  MG_OTA_UNAVAILABLE = 0,  // No OTA information is present
+  MG_OTA_FIRST_BOOT = 1,   // Device booting the first time after the OTA
+  MG_OTA_UNCOMMITTED = 2,  // Ditto, but marking us for the rollback
+  MG_OTA_COMMITTED = 3     // The firmware is good
 };
+enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
+
+int mg_ota_status(int firmware);          // Return firmware status MG_OTA_*
+uint32_t mg_ota_crc32(int firmware);      // Return firmware checksum
+uint32_t mg_ota_timestamp(int firmware);  // Firmware timestamp, UNIX UTC epoch
+size_t mg_ota_size(int firmware);         // Firmware size
+
+bool mg_ota_commit(void);        // Commit current firmware
+bool mg_ota_rollback(void);      // Rollback to the previous firmware
+MG_IRAM void mg_ota_boot(void);  // Bootloader function
+// Copyright (c) 2023 Cesanta Software Limited
+// All rights reserved
 
-#define MIP_ARP_ENTRIES 5  // Number of ARP cache entries. Maximum 21
-#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES)  // ARP cache size
+
+
+
+
+#define MG_DEVICE_NONE 0  // Dummy system
+
+#define MG_DEVICE_STM32H5 1     // STM32 H5
+#define MG_DEVICE_STM32H7 2     // STM32 H7
+#define MG_DEVICE_CH32V307 100  // WCH CH32V307
+#define MG_DEVICE_U2A 200       // Renesas U2A16, U2A8, U2A6
+#define MG_DEVICE_RT1020 300    // IMXRT1020
+#define MG_DEVICE_RT1060 301    // IMXRT1060
+#define MG_DEVICE_CUSTOM 1000   // Custom implementation
+
+#ifndef MG_DEVICE
+#define MG_DEVICE MG_DEVICE_NONE
+#endif
+
+// Flash information
+void *mg_flash_start(void);         // Return flash start address
+size_t mg_flash_size(void);         // Return flash size
+size_t mg_flash_sector_size(void);  // Return flash sector size
+size_t mg_flash_write_align(void);  // Return flash write align, minimum 4
+int mg_flash_bank(void);            // 0: not dual bank, 1: bank1, 2: bank2
+
+// Write, erase, swap bank
+bool mg_flash_write(void *addr, const void *buf, size_t len);
+bool mg_flash_erase(void *sector);
+bool mg_flash_swap_bank(void);
+
+// Convenience functions to store data on a flash sector with wear levelling
+// If `sector` is NULL, then the last sector of flash is used
+bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len);
+bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len);
+
+void mg_device_reset(void);  // Reboot device immediately
+
+
+
+
+
+
+#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
+struct mg_tcpip_if;  // Mongoose TCP/IP network interface
+
+struct mg_tcpip_driver {
+  bool (*init)(struct mg_tcpip_if *);                         // Init driver
+  size_t (*tx)(const void *, size_t, struct mg_tcpip_if *);   // Transmit frame
+  size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *);  // Receive frame
+  bool (*up)(struct mg_tcpip_if *);                           // Up/down status
+};
 
 // Network interface
-struct mip_if {
-  uint8_t mac[6];             // MAC address. Must be set to a valid MAC
-  uint32_t ip, mask, gw;      // IP address, mask, default gateway
-  struct mg_str rx;           // Output (TX) buffer
-  struct mg_str tx;           // Input (RX) buffer
-  bool enable_dhcp_client;    // Enable DCHP client
-  bool enable_dhcp_server;    // Enable DCHP server
-  struct mip_driver *driver;  // Low level driver
-  void *driver_data;          // Driver-specific data
-  struct mg_mgr *mgr;         // Mongoose event manager
-  struct queue queue;         // Set queue.len for interrupt based drivers
+struct mg_tcpip_if {
+  uint8_t mac[6];                  // MAC address. Must be set to a valid MAC
+  uint32_t ip, mask, gw;           // IP address, mask, default gateway
+  struct mg_str tx;                // Output (TX) buffer
+  bool enable_dhcp_client;         // Enable DCHP client
+  bool enable_dhcp_server;         // Enable DCHP server
+  bool enable_get_gateway;         // DCHP server sets client as gateway
+  bool enable_crc32_check;         // Do a CRC check on RX frames and strip it
+  bool enable_mac_check;           // Do a MAC check on RX frames
+  struct mg_tcpip_driver *driver;  // Low level driver
+  void *driver_data;               // Driver-specific data
+  struct mg_mgr *mgr;              // Mongoose event manager
+  struct mg_queue recv_queue;      // Receive queue
+  uint16_t mtu;                    // Interface MTU
+#define MG_TCPIP_MTU_DEFAULT 1500
 
   // Internal state, user can use it but should not change it
-  uint64_t now;                   // Current time
-  uint64_t timer_1000ms;          // 1000 ms timer: for DHCP and link state
-  uint64_t lease_expire;          // Lease expiration time
-  uint8_t arp_cache[MIP_ARP_CS];  // Each entry is 12 bytes
-  uint16_t eport;                 // Next ephemeral port
-  uint16_t dropped;               // Number of dropped frames
-  uint8_t state;                  // Current state
-#define MIP_STATE_DOWN 0          // Interface is down
-#define MIP_STATE_UP 1            // Interface is up
-#define MIP_STATE_READY 2         // Interface is up and has IP
+  uint8_t gwmac[6];             // Router's MAC
+  uint64_t now;                 // Current time
+  uint64_t timer_1000ms;        // 1000 ms timer: for DHCP and link state
+  uint64_t lease_expire;        // Lease expiration time, in ms
+  uint16_t eport;               // Next ephemeral port
+  volatile uint32_t ndrop;      // Number of received, but dropped frames
+  volatile uint32_t nrecv;      // Number of received frames
+  volatile uint32_t nsent;      // Number of transmitted frames
+  volatile uint32_t nerr;       // Number of driver errors
+  uint8_t state;                // Current state
+#define MG_TCPIP_STATE_DOWN 0   // Interface is down
+#define MG_TCPIP_STATE_UP 1     // Interface is up
+#define MG_TCPIP_STATE_REQ 2    // Interface is up and has requested an IP
+#define MG_TCPIP_STATE_READY 3  // Interface is up and has an IP assigned
 };
 
-void mip_init(struct mg_mgr *, struct mip_if *);
-void mip_free(struct mip_if *);
-void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
-size_t mip_qread(void *buf, struct mip_if *ifp);
-// conveniency rx function for IRQ-driven drivers
-size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
-
-extern struct mip_driver mip_driver_stm32;
-extern struct mip_driver mip_driver_w5500;
-extern struct mip_driver mip_driver_tm4c;
+void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
+void mg_tcpip_free(struct mg_tcpip_if *);
+void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
+
+extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
+extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
+extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
+extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
+extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
+extern struct mg_tcpip_driver mg_tcpip_driver_same54;
+extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
+extern struct mg_tcpip_driver mg_tcpip_driver_ra;
+extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
+extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
 
 // Drivers that require SPI, can use this SPI abstraction
-struct mip_spi {
+struct mg_tcpip_spi {
   void *spi;                        // Opaque SPI bus descriptor
   void (*begin)(void *);            // SPI begin: slave select low
   void (*end)(void *);              // SPI end: slave select high
   uint8_t (*txn)(void *, uint8_t);  // SPI transaction: write 1 byte, read reply
 };
+#endif
 
-#ifdef MIP_QPROFILE
-enum {
-  QP_IRQTRIGGERED = 0,  // payload is number of interrupts so far
-  QP_FRAMEPUSHED,       // available space in the frame queue
-  QP_FRAMEPOPPED,       // available space in the frame queue
-  QP_FRAMEDONE,         // available space in the frame queue
-  QP_FRAMEDROPPED,      // number of dropped frames
-  QP_QUEUEOVF  // profiling queue is full, payload is number of frame drops
+
+
+// Macros to record timestamped events that happens with a connection.
+// They are saved into a c->prof IO buffer, each event is a name and a 32-bit
+// timestamp in milliseconds since connection init time.
+//
+// Test (run in two separate terminals):
+//   make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
+//   curl localhost:8000
+// Output:
+//   1ea1f1e7 2 net.c:150:mg_close_conn      3 profile:                                                            
+//   1ea1f1e8 2 net.c:150:mg_close_conn      1ea1f1e6 init                                                         
+//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_OPEN
+//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_ACCEPT 
+//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_READ
+//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_HTTP_MSG
+//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_WRITE
+//   1ea1f1e8 2 net.c:150:mg_close_conn          1 EV_CLOSE
+//
+// Usage:
+//   Enable profiling by setting MG_ENABLE_PROFILE=1
+//   Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure
+
+#if MG_ENABLE_PROFILE
+struct mg_profitem {
+  const char *name;    // Event name
+  uint32_t timestamp;  // Milliseconds since connection creation (MG_EV_OPEN)
 };
 
-void qp_mark(unsigned int type, int len);
-void qp_log(void);  // timestamp, type, payload
-void qp_init(void);
+#define MG_PROFILE_ALLOC_GRANULARITY 256  // Can save 32 items wih to realloc
+
+// Adding a profile item to the c->prof. Must be as fast as possible.
+// Reallocation of the c->prof iobuf is not desirable here, that's why we
+// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
+// This macro just inits and copies 8 bytes, and calls mg_millis(),
+// which should be fast enough.
+#define MG_PROF_ADD(c, name_)                                             \
+  do {                                                                    \
+    struct mg_iobuf *io = &c->prof;                                       \
+    uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp;      \
+    struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \
+    mg_iobuf_add(io, io->len, &item, sizeof(item));                       \
+  } while (0)
+
+// Initialising profile for a new connection. Not time sensitive
+#define MG_PROF_INIT(c)                                          \
+  do {                                                           \
+    struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
+    mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY);  \
+    mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first));  \
+  } while (0)
+
+#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
+
+// Dumping the profile. Not time sensitive
+#define MG_PROF_DUMP(c)                                            \
+  do {                                                             \
+    struct mg_iobuf *io = &c->prof;                                \
+    struct mg_profitem *p = (struct mg_profitem *) io->buf;        \
+    struct mg_profitem *e = &p[io->len / sizeof(*p)];              \
+    MG_INFO(("%lu profile:", c->id));                              \
+    while (p < e) {                                                \
+      MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
+      p++;                                                         \
+    }                                                              \
+  } while (0)
+
 #else
-#define qp_mark(a, b)
+#define MG_PROF_INIT(c)
+#define MG_PROF_FREE(c)
+#define MG_PROF_ADD(c, name)
+#define MG_PROF_DUMP(c)
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS
+
+#include "Driver_ETH_MAC.h"  // keep this include
+#include "Driver_ETH_PHY.h"  // keep this include
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT
+
+struct mg_tcpip_driver_imxrt_data {
+  // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk),
+  // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz
+  // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
+  //    ipg_clk       mdc_cr VALUE
+  //    --------------------------
+  //                  -1  <-- TODO() tell driver to guess the value
+  //    25 MHz         4
+  //    33 MHz         6
+  //    40 MHz         7
+  //    50 MHz         9
+  //    66 MHz        13
+  int mdc_cr;  // Valid values: -1 to 63
+
+  uint8_t phy_addr;  // PHY address
+};
+
+#ifndef MG_TCPIP_PHY_ADDR
+#define MG_TCPIP_PHY_ADDR 2
+#endif
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 24
+#endif
+
+#define MG_TCPIP_DRIVER_INIT(mgr)                                \
+  do {                                                           \
+    static struct mg_tcpip_driver_imxrt_data driver_data_;       \
+    static struct mg_tcpip_if mif_;                              \
+    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                      \
+    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                   \
+    mif_.ip = MG_TCPIP_IP;                                       \
+    mif_.mask = MG_TCPIP_MASK;                                   \
+    mif_.gw = MG_TCPIP_GW;                                       \
+    mif_.driver = &mg_tcpip_driver_imxrt;                        \
+    mif_.driver_data = &driver_data_;                            \
+    MG_SET_MAC_ADDRESS(mif_.mac);                                \
+    mg_tcpip_init(mgr, &mif_);                                   \
+    MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
+  } while (0)
+
 #endif
 
 
-struct mip_driver_stm32_data {
+
+
+struct mg_phy {
+  uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
+  void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
+};
+
+// PHY configuration settings, bitmask
+enum {
+  MG_PHY_LEDS_ACTIVE_HIGH =
+      (1 << 0),  // Set if PHY LEDs are connected to ground
+  MG_PHY_CLOCKS_MAC =
+      (1 << 1)   // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
+};
+
+enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
+
+void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
+bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
+               uint8_t *speed);
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA
+
+struct mg_tcpip_driver_ra_data {
+  // MDC clock "divider". MDC clock is software generated,
+  uint32_t clock;    // core clock frequency in Hz
+  uint16_t irqno;    // IRQn, R_ICU->IELSR[irqno]
+  uint8_t phy_addr;  // PHY address
+};
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54
+
+struct mg_tcpip_driver_same54_data {
+    int mdc_cr;
+};
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 5
+#endif
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
+    MG_ENABLE_DRIVER_STM32F
+
+struct mg_tcpip_driver_stm32f_data {
   // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
   //    HCLK range    DIVIDER    mdc_cr VALUE
   //    -------------------------------------
@@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
   //    216-310 MHz   HCLK/124       5
   //    110, 111 Reserved
   int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
+
+  uint8_t phy_addr;  // PHY address
+};
+
+#ifndef MG_TCPIP_PHY_ADDR
+#define MG_TCPIP_PHY_ADDR 0
+#endif
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 4
+#endif
+
+#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
+  do {                                                            \
+    static struct mg_tcpip_driver_stm32f_data driver_data_;       \
+    static struct mg_tcpip_if mif_;                               \
+    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
+    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
+    mif_.ip = MG_TCPIP_IP;                                        \
+    mif_.mask = MG_TCPIP_MASK;                                    \
+    mif_.gw = MG_TCPIP_GW;                                        \
+    mif_.driver = &mg_tcpip_driver_stm32f;                        \
+    mif_.driver_data = &driver_data_;                             \
+    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    mg_tcpip_init(mgr, &mif_);                                    \
+    MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
+  } while (0)
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
+    MG_ENABLE_DRIVER_STM32H
+
+struct mg_tcpip_driver_stm32h_data {
+  // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
+  //    HCLK range    DIVIDER    mdc_cr VALUE
+  //    -------------------------------------
+  //                                -1  <-- tell driver to guess the value
+  //    60-100 MHz    HCLK/42        0
+  //    100-150 MHz   HCLK/62        1
+  //    20-35 MHz     HCLK/16        2
+  //    35-60 MHz     HCLK/26        3
+  //    150-250 MHz   HCLK/102       4  <-- value for max speed HSI
+  //    250-300 MHz   HCLK/124       5  <-- value for Nucleo-H* on CSI
+  //    110, 111 Reserved
+  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
+
+  uint8_t phy_addr;  // PHY address
+  uint8_t phy_conf;  // PHY config
 };
 
+#ifndef MG_TCPIP_PHY_ADDR
+#define MG_TCPIP_PHY_ADDR 0
+#endif
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 4
+#endif
+
+#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
+  do {                                                            \
+    static struct mg_tcpip_driver_stm32h_data driver_data_;       \
+    static struct mg_tcpip_if mif_;                               \
+    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
+    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
+    mif_.ip = MG_TCPIP_IP;                                        \
+    mif_.mask = MG_TCPIP_MASK;                                    \
+    mif_.gw = MG_TCPIP_GW;                                        \
+    mif_.driver = &mg_tcpip_driver_stm32h;                        \
+    mif_.driver_data = &driver_data_;                             \
+    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    mg_tcpip_init(mgr, &mif_);                                    \
+    MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
+  } while (0)
+
+#endif
+
 
-struct mip_driver_tm4c_data {
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
+
+struct mg_tcpip_driver_tm4c_data {
   // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz
   //    SYSCLK range   DIVIDER   mdc_cr VALUE
   //    -------------------------------------
@@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
   int mdc_cr;  // Valid values: -1, 0, 1, 2, 3
 };
 
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 1
+#endif
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500
+
+#undef MG_ENABLE_TCPIP_DRIVER_INIT
+#define MG_ENABLE_TCPIP_DRIVER_INIT 0
+
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7
+
+struct mg_tcpip_driver_xmc7_data {
+  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
+  uint8_t phy_addr;
+};
+
+#ifndef MG_TCPIP_PHY_ADDR
+#define MG_TCPIP_PHY_ADDR 0
+#endif
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 3
+#endif
+
+#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
+  do {                                                            \
+    static struct mg_tcpip_driver_xmc7_data driver_data_;       \
+    static struct mg_tcpip_if mif_;                               \
+    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
+    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
+    mif_.ip = MG_TCPIP_IP;                                        \
+    mif_.mask = MG_TCPIP_MASK;                                    \
+    mif_.gw = MG_TCPIP_GW;                                        \
+    mif_.driver = &mg_tcpip_driver_xmc7;                        \
+    mif_.driver_data = &driver_data_;                             \
+    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    mg_tcpip_init(mgr, &mif_);                                    \
+    MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
+  } while (0)
+
+#endif
+
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC
+
+struct mg_tcpip_driver_xmc_data {
+  // 13.2.8.1 Station Management Functions
+  // MDC clock divider (). MDC clock is derived from ETH MAC clock
+  // It must not exceed 2.5MHz
+  // ETH Clock range  DIVIDER       mdc_cr VALUE
+  // --------------------------------------------
+  //                                     -1  <-- tell driver to guess the value
+  // 60-100 MHz       ETH Clock/42        0
+  // 100-150 MHz      ETH Clock/62        1
+  // 20-35 MHz        ETH Clock/16        2
+  // 35-60 MHz        ETH Clock/26        3
+  // 150-250 MHz      ETH Clock/102       4
+  // 250-300 MHz      ETH Clock/124       5
+  // 110, 111 Reserved
+  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
+  uint8_t phy_addr;
+};
+
+#ifndef MG_TCPIP_PHY_ADDR
+#define MG_TCPIP_PHY_ADDR 0
+#endif
+
+#ifndef MG_DRIVER_MDC_CR
+#define MG_DRIVER_MDC_CR 4
+#endif
+
+#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
+  do {                                                            \
+    static struct mg_tcpip_driver_xmc_data driver_data_;       \
+    static struct mg_tcpip_if mif_;                               \
+    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
+    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
+    mif_.ip = MG_TCPIP_IP;                                        \
+    mif_.mask = MG_TCPIP_MASK;                                    \
+    mif_.gw = MG_TCPIP_GW;                                        \
+    mif_.driver = &mg_tcpip_driver_xmc;                        \
+    mif_.driver_data = &driver_data_;                             \
+    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    mg_tcpip_init(mgr, &mif_);                                    \
+    MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
+  } while (0)
+
+#endif
+
 #ifdef __cplusplus
 }
 #endif

Comments

Stefano Babic July 7, 2024, 9:40 a.m. UTC | #1
Hi Michael,

I wanted to work on this, but I cannot apply clean, git am reports a
malformed patch. Can you check and repost ? Thanks.

Best regards,
Stefano

On 15.06.24 21:11, Michael Glembotzki wrote:
> mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05
>
> Signed-off-by: Michael Glembotzki <Michael.Glembotzki@iris-sensing.com>
> ---
>   mongoose/mongoose.c | 21614 +++++++++++++++++++++++++++++++-----------
>   mongoose/mongoose.h |  2003 +++-
>   2 files changed, 17594 insertions(+), 6023 deletions(-)
>
> diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
> index 83d8fe18..3fc0e14a 100644
> --- a/mongoose/mongoose.c
> +++ b/mongoose/mongoose.c
> @@ -1,5 +1,5 @@
>   // Copyright (c) 2004-2013 Sergey Lyubka
> -// Copyright (c) 2013-2022 Cesanta Software Limited
> +// Copyright (c) 2013-2024 Cesanta Software Limited
>   // All rights reserved
>   //
>   // This software is dual-licensed: you can redistribute it and/or modify
> @@ -15,7 +15,7 @@
>   // Alternatively, you can license this software under a commercial
>   // license, as set out in https://www.mongoose.ws/licensing/
>   //
> -// SPDX-License-Identifier: GPL-2.0-only
> +// SPDX-License-Identifier: GPL-2.0-only or commercial
>
>   #include "mongoose.h"
>
> @@ -24,8 +24,7 @@
>   #endif
>
>
> -
> -static int mg_b64idx(int c) {
> +static int mg_base64_encode_single(int c) {
>     if (c < 26) {
>       return c + 'A';
>     } else if (c < 52) {
> @@ -37,7 +36,7 @@ static int mg_b64idx(int c) {
>     }
>   }
>
> -static int mg_b64rev(int c) {
> +static int mg_base64_decode_single(int c) {
>     if (c >= 'A' && c <= 'Z') {
>       return c - 'A';
>     } else if (c >= 'a' && c <= 'z') {
> @@ -55,24 +54,24 @@ static int mg_b64rev(int c) {
>     }
>   }
>
> -int mg_base64_update(unsigned char ch, char *to, int n) {
> -  int rem = (n & 3) % 3;
> +size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
> +  unsigned long rem = (n & 3) % 3;
>     if (rem == 0) {
> -    to[n] = (char) mg_b64idx(ch >> 2);
> +    to[n] = (char) mg_base64_encode_single(ch >> 2);
>       to[++n] = (char) ((ch & 3) << 4);
>     } else if (rem == 1) {
> -    to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
> +    to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
>       to[++n] = (char) ((ch & 15) << 2);
>     } else {
> -    to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
> -    to[++n] = (char) mg_b64idx(ch & 63);
> +    to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
> +    to[++n] = (char) mg_base64_encode_single(ch & 63);
>       n++;
>     }
>     return n;
>   }
>
> -int mg_base64_final(char *to, int n) {
> -  int saved = n;
> +size_t mg_base64_final(char *to, size_t n) {
> +  size_t saved = n;
>     // printf("---[%.*s]\n", n, to);
>     if (n & 3) n = mg_base64_update(0, to, n);
>     if ((saved & 3) == 2) n--;
> @@ -82,20 +81,27 @@ int mg_base64_final(char *to, int n) {
>     return n;
>   }
>
> -int mg_base64_encode(const unsigned char *p, int n, char *to) {
> -  int i, len = 0;
> +size_t mg_base64_encode(const unsigned char *p, size_t n, char *to, size_t dl) {
> +  size_t i, len = 0;
> +  if (dl > 0) to[0] = '\0';
> +  if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
>     for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
>     len = mg_base64_final(to, len);
>     return len;
>   }
>
> -int mg_base64_decode(const char *src, int n, char *dst) {
> +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t dl) {
>     const char *end = src == NULL ? NULL : src + n;  // Cannot add to NULL
> -  int len = 0;
> +  size_t len = 0;
> +  if (dl < n / 4 * 3 + 1) goto fail;
>     while (src != NULL && src + 3 < end) {
> -    int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = mg_b64rev(src[2]),
> -        d = mg_b64rev(src[3]);
> -    if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0;
> +    int a = mg_base64_decode_single(src[0]),
> +        b = mg_base64_decode_single(src[1]),
> +        c = mg_base64_decode_single(src[2]),
> +        d = mg_base64_decode_single(src[3]);
> +    if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
> +      goto fail;
> +    }
>       dst[len++] = (char) ((a << 2) | (b >> 4));
>       if (src[2] != '=') {
>         dst[len++] = (char) ((b << 4) | (c >> 2));
> @@ -105,1942 +111,2915 @@ int mg_base64_decode(const char *src, int n, char *dst) {
>     }
>     dst[len] = '\0';
>     return len;
> +fail:
> +  if (dl > 0) dst[0] = '\0';
> +  return 0;
>   }
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/dns.c"
> +#line 1 "src/device_ch32v307.c"
>   #endif
>
>
>
> +#if MG_DEVICE == MG_DEVICE_CH32V307
> +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
>
> +#define FLASH_BASE 0x40022000
> +#define FLASH_ACTLR (FLASH_BASE + 0)
> +#define FLASH_KEYR (FLASH_BASE + 4)
> +#define FLASH_OBKEYR (FLASH_BASE + 8)
> +#define FLASH_STATR (FLASH_BASE + 12)
> +#define FLASH_CTLR (FLASH_BASE + 16)
> +#define FLASH_ADDR (FLASH_BASE + 20)
> +#define FLASH_OBR (FLASH_BASE + 28)
> +#define FLASH_WPR (FLASH_BASE + 32)
>
> -
> -
> -struct dns_data {
> -  struct dns_data *next;
> -  struct mg_connection *c;
> -  uint64_t expire;
> -  uint16_t txnid;
> -};
> -
> -static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
> -                         struct mg_dns *, bool);
> -
> -static void mg_dns_free(struct mg_connection *c, struct dns_data *d) {
> -  LIST_DELETE(struct dns_data,
> -              (struct dns_data **) &c->mgr->active_dns_requests, d);
> -  free(d);
> +void *mg_flash_start(void) {
> +  return (void *) 0x08000000;
>   }
> -
> -void mg_resolve_cancel(struct mg_connection *c) {
> -  struct dns_data *tmp, *d = (struct dns_data *) c->mgr->active_dns_requests;
> -  for (; d != NULL; d = tmp) {
> -    tmp = d->next;
> -    if (d->c == c) mg_dns_free(c, d);
> +size_t mg_flash_size(void) {
> +  return 480 * 1024;  // First 320k is 0-wait
> +}
> +size_t mg_flash_sector_size(void) {
> +  return 4096;
> +}
> +size_t mg_flash_write_align(void) {
> +  return 4;
> +}
> +int mg_flash_bank(void) {
> +  return 0;
> +}
> +void mg_device_reset(void) {
> +  *((volatile uint32_t *) 0xbeef0000) |= 1U << 7;  // NVIC_SystemReset()
> +}
> +static void flash_unlock(void) {
> +  static bool unlocked;
> +  if (unlocked == false) {
> +    MG_REG(FLASH_KEYR) = 0x45670123;
> +    MG_REG(FLASH_KEYR) = 0xcdef89ab;
> +    unlocked = true;
>     }
>   }
> +static void flash_wait(void) {
> +  while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
> +}
>
> -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
> -                                      char *to, size_t tolen, size_t j,
> -                                      int depth) {
> -  size_t i = 0;
> -  if (tolen > 0 && depth == 0) to[0] = '\0';
> -  if (depth > 5) return 0;
> -  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
> -  while (ofs + i + 1 < len) {
> -    size_t n = s[ofs + i];
> -    if (n == 0) {
> -      i++;
> -      break;
> -    }
> -    if (n & 0xc0) {
> -      size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
> -      // MG_INFO(("PTR %lx", (unsigned long) ptr));
> -      if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> -          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
> -        return 0;
> -      i += 2;
> -      break;
> -    }
> -    if (ofs + i + n + 1 >= len) return 0;
> -    if (j > 0) {
> -      if (j < tolen) to[j] = '.';
> -      j++;
> -    }
> -    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> -    j += n;
> -    i += n + 1;
> -    if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
> -    // MG_INFO(("--> [%s]", to));
> -  }
> -  if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
> -  return i;
> +bool mg_flash_erase(void *addr) {
> +  //MG_INFO(("%p", addr));
> +  flash_unlock();
> +  flash_wait();
> +  MG_REG(FLASH_ADDR) = (uint32_t) addr;
> +  MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6);  // PER | STRT;
> +  flash_wait();
> +  return true;
>   }
>
> -static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
> -                                char *dst, size_t dstlen) {
> -  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
> +static bool is_page_boundary(const void *addr) {
> +  uint32_t val = (uint32_t) addr;
> +  return (val & (mg_flash_sector_size() - 1)) == 0;
>   }
>
> -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> -                       bool is_question, struct mg_dns_rr *rr) {
> -  const uint8_t *s = buf + ofs, *e = &buf[len];
> +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> +  //MG_INFO(("%p %p %lu", addr, buf, len));
> +  //mg_hexdump(buf, len);
> +  flash_unlock();
> +  const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
> +  uint16_t *dst = (uint16_t *) addr;
> +  MG_REG(FLASH_CTLR) |= MG_BIT(0);  // Set PG
> +  //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
> +  while (src < end) {
> +    if (is_page_boundary(dst)) mg_flash_erase(dst);
> +    *dst++ = *src++;
> +    flash_wait();
> +  }
> +  MG_REG(FLASH_CTLR) &= ~MG_BIT(0);  // Clear PG
> +  return true;
> +}
> +#endif
>
> -  memset(rr, 0, sizeof(*rr));
> -  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
> -  if (len > 512) return 0;  //  Too large, we don't expect that
> -  if (s >= e) return 0;     //  Overflow
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/device_dummy.c"
> +#endif
>
> -  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
> -    return 0;
> -  s += rr->nlen + 4;
> -  if (s > e) return 0;
> -  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> -  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> -  if (is_question) return (size_t) (rr->nlen + 4);
>
> -  s += 6;
> -  if (s > e) return 0;
> -  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> -  if (s + rr->alen > e) return 0;
> -  return (size_t) (rr->nlen + rr->alen + 10);
> +#if MG_DEVICE == MG_DEVICE_NONE
> +void *mg_flash_start(void) {
> +  return NULL;
> +}
> +size_t mg_flash_size(void) {
> +  return 0;
> +}
> +size_t mg_flash_sector_size(void) {
> +  return 0;
> +}
> +size_t mg_flash_write_align(void) {
> +  return 0;
> +}
> +int mg_flash_bank(void) {
> +  return 0;
> +}
> +bool mg_flash_erase(void *location) {
> +  (void) location;
> +  return false;
> +}
> +bool mg_flash_swap_bank(void) {
> +  return true;
> +}
> +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> +  (void) addr, (void) buf, (void) len;
> +  return false;
> +}
> +void mg_device_reset(void) {
>   }
> +#endif
>
> -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
> -  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> -  struct mg_dns_rr rr;
> -  size_t i, n, ofs = sizeof(*h);
> -  memset(dm, 0, sizeof(*dm));
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/device_flash.c"
> +#endif
>
> -  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
> -  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
> -  if (mg_ntohs(h->num_answers) > 10) return 0;   // Sanity
> -  dm->txnid = mg_ntohs(h->txnid);
>
> -  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> -    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
> -    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> -    ofs += n;
> +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \
> +    MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> +// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1)
> +// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an
> +// object, we pad it at the end for alignment.
> +//
> +// Objects in the flash sector are stored sequentially:
> +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ......
> +//
> +// In order to get to the next object, read its size, then align up.
> +
> +// Traverse the list of saved objects
> +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) {
> +  size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p;
> +  uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
> +  if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
> +    if (size) *size = (size_t) p32[0];
> +    if (key) *key = p32[1];
> +    aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
> +    if (left < aligned_size) aligned_size = 0;  // Out of bounds, fail
>     }
> -  for (i = 0; i < mg_ntohs(h->num_answers); i++) {
> -    if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
> -    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
> -    // dm->name));
> -    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> -    ofs += n;
> -
> -    if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> -      dm->addr.is_ip6 = false;
> -      memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> -      dm->resolved = true;
> -      break;  // Return success
> -    } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> -      dm->addr.is_ip6 = true;
> -      memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
> -      dm->resolved = true;
> -      break;  // Return success
> +  return aligned_size;
> +}
> +
> +// Return the last sector of Bank 2
> +static char *flash_last_sector(void) {
> +  size_t ss = mg_flash_sector_size(), size = mg_flash_size();
> +  char *base = (char *) mg_flash_start(), *last = base + size - ss;
> +  if (mg_flash_bank() == 2) last -= size / 2;
> +  return last;
> +}
> +
> +// Find a saved object with a given key
> +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
> +  char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL;
> +  size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
> +  bool ok = false;
> +  if (s == NULL) s = flash_last_sector();
> +  if (s < base || s >= base + mg_flash_size()) {
> +    MG_ERROR(("%p is outsize of flash", sector));
> +  } else if (((s - base) % ss) != 0) {
> +    MG_ERROR(("%p is not a sector boundary", sector));
> +  } else {
> +    uint32_t k, scanned = 0;
> +    while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
> +      // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key));
> +      // mg_hexdump(s + ofs, n);
> +      if (k == key && sz == len) {
> +        res = s + ofs + sizeof(uint32_t) * 2;
> +        memcpy(buf, res, len);  // Copy object
> +        ok = true;              // Keep scanning for the newer versions of it
> +      }
> +      ofs += n, scanned++;
>       }
> +    MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res));
>     }
> -  return true;
> +  return ok;
>   }
>
> -static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
> -                   void *fn_data) {
> -  struct dns_data *d, *tmp;
> -  if (ev == MG_EV_POLL) {
> -    uint64_t now = *(uint64_t *) ev_data;
> -    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> -         d = tmp) {
> -      tmp = d->next;
> -      // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> -      if (now > d->expire) mg_error(d->c, "DNS timeout");
> +// For all saved objects in the sector, delete old versions of objects
> +static void mg_flash_sector_cleanup(char *sector) {
> +  // Buffer all saved objects into an IO buffer (backed by RAM)
> +  // erase sector, and re-save them.
> +  struct mg_iobuf io = {0, 0, 0, 2048};
> +  size_t ss = mg_flash_sector_size();
> +  size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
> +  uint32_t key;
> +  // Traverse all objects
> +  MG_DEBUG(("Cleaning up sector %p", sector));
> +  while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) {
> +    // Delete an old copy of this object in the cache
> +    for (size_t o = 0; o < io.len; o += size2 + hs) {
> +      uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
> +      size2 = *(uint32_t *) (io.buf + o);
> +      if (k == key) {
> +        mg_iobuf_del(&io, o, size2 + hs);
> +        break;
> +      }
>       }
> -  } else if (ev == MG_EV_READ) {
> -    struct mg_dns_message dm;
> -    int resolved = 0;
> -    if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> -      MG_ERROR(("Unexpected DNS response:"));
> -      mg_hexdump(c->recv.buf, c->recv.len);
> -    } else {
> -      // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
> -      for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> -           d = tmp) {
> -        tmp = d->next;
> -        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> -        if (dm.txnid != d->txnid) continue;
> -        if (d->c->is_resolving) {
> -          if (dm.resolved) {
> -            dm.addr.port = d->c->rem.port;  // Save port
> -            d->c->rem = dm.addr;            // Copy resolved address
> -            MG_DEBUG(
> -                ("%lu %s is %I", d->c->id, dm.name, d->c->rem.is_ip6 ? 16 : 4,
> -                 d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
> -            mg_connect_resolved(d->c);
> -#if MG_ENABLE_IPV6
> -          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
> -                     c->mgr->use_dns6 == false) {
> -            struct mg_str x = mg_str(dm.name);
> -            mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> -#endif
> -          } else {
> -            mg_error(d->c, "%s DNS lookup failed", dm.name);
> +    // And add the new copy
> +    mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
> +    ofs += n;
> +  }
> +  // All objects are cached in RAM now
> +  if (mg_flash_erase(sector)) {  // Erase sector. If successful,
> +    for (ofs = 0; ofs < io.len; ofs += size + hs) {  // Traverse cached objects
> +      size = *(uint32_t *) (io.buf + ofs);
> +      key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
> +      mg_flash_save(sector, key, io.buf + ofs + hs, size);  // Save to flash
> +    }
> +  }
> +  mg_iobuf_free(&io);
> +}
> +
> +// Save an object with a given key - append to the end of an object list
> +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
> +  char *base = (char *) mg_flash_start(), *s = (char *) sector;
> +  size_t ss = mg_flash_sector_size(), ofs = 0, n;
> +  bool ok = false;
> +  if (s == NULL) s = flash_last_sector();
> +  if (s < base || s >= base + mg_flash_size()) {
> +    MG_ERROR(("%p is outsize of flash", sector));
> +  } else if (((s - base) % ss) != 0) {
> +    MG_ERROR(("%p is not a sector boundary", sector));
> +  } else {
> +    char ab[mg_flash_write_align()];  // Aligned write block
> +    uint32_t hdr[2] = {(uint32_t) len, key};
> +    size_t needed = sizeof(hdr) + len;
> +    size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
> +    while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
> +
> +    // If there is not enough space left, cleanup sector and re-eval ofs
> +    if (ofs + needed_aligned >= ss) {
> +      mg_flash_sector_cleanup(s);
> +      ofs = 0;
> +      while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
> +    }
> +
> +    if (ofs + needed_aligned <= ss) {
> +      // Enough space to save this object
> +      if (sizeof(ab) < sizeof(hdr)) {
> +        // Flash write granularity is 32 bit or less, write with no buffering
> +        ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
> +        if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
> +      } else {
> +        // Flash granularity is sizeof(hdr) or more. We need to save in
> +        // 3 chunks: initial block, bulk, rest. This is because we have
> +        // two memory chunks to write: hdr and buf, on aligned boundaries.
> +        n = sizeof(ab) - sizeof(hdr);      // Initial chunk that we write
> +        if (n > len) n = len;              // is
> +        memset(ab, 0xff, sizeof(ab));      // initialized to all-one
> +        memcpy(ab, hdr, sizeof(hdr));      // contains the header (key + size)
> +        memcpy(ab + sizeof(hdr), buf, n);  // and an initial part of buf
> +        MG_INFO(("saving initial block of %lu", sizeof(ab)));
> +        ok = mg_flash_write(s + ofs, ab, sizeof(ab));
> +        if (ok && len > n) {
> +          size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
> +          if (n2 > 0) {
> +            MG_INFO(("saving bulk, %lu", n2));
> +            ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
> +          }
> +          if (ok && len > n) {
> +            size_t n3 = len - n - n2;
> +            if (n3 > sizeof(ab)) n3 = sizeof(ab);
> +            memset(ab, 0xff, sizeof(ab));
> +            memcpy(ab, (char *) buf + n + n2, n3);
> +            MG_INFO(("saving rest, %lu", n3));
> +            ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
>             }
> -        } else {
> -          MG_ERROR(("%lu already resolved", d->c->id));
>           }
> -        mg_dns_free(c, d);
> -        resolved = 1;
>         }
> -    }
> -    if (!resolved) MG_ERROR(("stray DNS reply"));
> -    c->recv.len = 0;
> -  } else if (ev == MG_EV_CLOSE) {
> -    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> -         d = tmp) {
> -      tmp = d->next;
> -      mg_error(d->c, "DNS error");
> -      mg_dns_free(c, d);
> +      MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
> +                s + ofs, key, ok));
> +      MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
> +    } else {
> +      MG_ERROR(("Sector is full"));
>       }
>     }
> -  (void) fn_data;
> +  return ok;
> +}
> +#else
> +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
> +  (void) sector, (void) key, (void) buf, (void) len;
> +  return false;
> +}
> +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
> +  (void) sector, (void) key, (void) buf, (void) len;
> +  return false;
>   }
> +#endif
>
> -static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
> -                        uint16_t txnid, bool ipv6) {
> -  struct {
> -    struct mg_dns_header header;
> -    uint8_t data[256];
> -  } pkt;
> -  size_t i, n;
> -  memset(&pkt, 0, sizeof(pkt));
> -  pkt.header.txnid = mg_htons(txnid);
> -  pkt.header.flags = mg_htons(0x100);
> -  pkt.header.num_questions = mg_htons(1);
> -  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> -    if (name->ptr[i] == '.' || i >= name->len) {
> -      pkt.data[n] = (uint8_t) (i - n);
> -      memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
> -      n = i + 1;
> -    }
> -    if (i >= name->len) break;
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/device_imxrt.c"
> +#endif
> +
> +
> +
> +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> +
> +struct mg_flexspi_lut_seq {
> +  uint8_t seqNum;
> +  uint8_t seqId;
> +  uint16_t reserved;
> +};
> +
> +struct mg_flexspi_mem_config {
> +  uint32_t tag;
> +  uint32_t version;
> +  uint32_t reserved0;
> +  uint8_t readSampleClkSrc;
> +  uint8_t csHoldTime;
> +  uint8_t csSetupTime;
> +  uint8_t columnAddressWidth;
> +  uint8_t deviceModeCfgEnable;
> +  uint8_t deviceModeType;
> +  uint16_t waitTimeCfgCommands;
> +  struct mg_flexspi_lut_seq deviceModeSeq;
> +  uint32_t deviceModeArg;
> +  uint8_t configCmdEnable;
> +  uint8_t configModeType[3];
> +  struct mg_flexspi_lut_seq configCmdSeqs[3];
> +  uint32_t reserved1;
> +  uint32_t configCmdArgs[3];
> +  uint32_t reserved2;
> +  uint32_t controllerMiscOption;
> +  uint8_t deviceType;
> +  uint8_t sflashPadType;
> +  uint8_t serialClkFreq;
> +  uint8_t lutCustomSeqEnable;
> +  uint32_t reserved3[2];
> +  uint32_t sflashA1Size;
> +  uint32_t sflashA2Size;
> +  uint32_t sflashB1Size;
> +  uint32_t sflashB2Size;
> +  uint32_t csPadSettingOverride;
> +  uint32_t sclkPadSettingOverride;
> +  uint32_t dataPadSettingOverride;
> +  uint32_t dqsPadSettingOverride;
> +  uint32_t timeoutInMs;
> +  uint32_t commandInterval;
> +  uint16_t dataValidTime[2];
> +  uint16_t busyOffset;
> +  uint16_t busyBitPolarity;
> +  uint32_t lookupTable[64];
> +  struct mg_flexspi_lut_seq lutCustomSeq[12];
> +  uint32_t reserved4[4];
> +};
> +
> +struct mg_flexspi_nor_config {
> +  struct mg_flexspi_mem_config memConfig;
> +  uint32_t pageSize;
> +  uint32_t sectorSize;
> +  uint8_t ipcmdSerialClkFreq;
> +  uint8_t isUniformBlockSize;
> +  uint8_t reserved0[2];
> +  uint8_t serialNorType;
> +  uint8_t needExitNoCmdMode;
> +  uint8_t halfClkForNonReadCmd;
> +  uint8_t needRestoreNoCmdMode;
> +  uint32_t blockSize;
> +  uint32_t reserve2[11];
> +};
> +
> +/* FLEXSPI memory config block related defintions */
> +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL)      // ascii "FCFB" Big Endian
> +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL)  // V1.4.0
> +
> +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1)                                      \
> +  (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
> +   MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1))
> +
> +#define MG_CMD_SDR 0x01
> +#define MG_CMD_DDR 0x21
> +#define MG_DUMMY_SDR 0x0C
> +#define MG_DUMMY_DDR 0x2C
> +#define MG_RADDR_SDR 0x02
> +#define MG_RADDR_DDR 0x22
> +#define MG_READ_SDR 0x09
> +#define MG_READ_DDR 0x29
> +#define MG_WRITE_SDR 0x08
> +#define MG_WRITE_DDR 0x28
> +#define MG_STOP 0
> +
> +#define MG_FLEXSPI_1PAD 0
> +#define MG_FLEXSPI_2PAD 1
> +#define MG_FLEXSPI_4PAD 2
> +#define MG_FLEXSPI_8PAD 3
> +
> +#define MG_FLEXSPI_QSPI_LUT                                                                        \
> +  {                                                                                                \
> +    [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD,     \
> +                             0x18),                                                                \
> +    [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD,    \
> +                             0x04),                                                                \
> +    [4 * 1 + 0] =                                                                                  \
> +        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \
> +    [4 * 3 + 0] =                                                                                  \
> +        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0),      \
> +    [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR,              \
> +                                     MG_FLEXSPI_1PAD, 0x18),                                       \
> +    [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR,              \
> +                                     MG_FLEXSPI_1PAD, 0x18),                                       \
> +    [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR,              \
> +                                     MG_FLEXSPI_1PAD, 0x18),                                       \
> +    [4 * 9 + 1] =                                                                                  \
> +        MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0),    \
> +    [4 * 11 + 0] =                                                                                 \
> +        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0),      \
>     }
> -  memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
> -  n += 5;
> -  if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
> -  // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
> -  // n += 6;
> -  return mg_send(c, &pkt, sizeof(pkt.header) + n);
> +
> +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU)
> +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U)
> +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U)
> +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U)
> +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U)
> +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U)
> +
> +#define FLEXSPI_NOR_INSTANCE 0
> +
> +#if MG_DEVICE == MG_DEVICE_RT1020
> +struct mg_flexspi_nor_driver_interface {
> +  uint32_t version;
> +  int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
> +  int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr,
> +                 const uint32_t *src);
> +  uint32_t reserved;
> +  int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start,
> +               uint32_t lengthInBytes);
> +  uint32_t reserved2;
> +  int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase,
> +                    uint32_t seqNumber);
> +  int (*xfer)(uint32_t instance, char *xfer);
> +  void (*clear_cache)(uint32_t instance);
> +};
> +#elif MG_DEVICE == MG_DEVICE_RT1060
> +struct mg_flexspi_nor_driver_interface {
> +  uint32_t version;
> +  int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
> +  int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr,
> +                 const uint32_t *src);
> +  int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config);
> +  int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start,
> +               uint32_t lengthInBytes);
> +  int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr,
> +              uint32_t lengthInBytes);
> +  void (*clear_cache)(uint32_t instance);
> +  int (*xfer)(uint32_t instance, char *xfer);
> +  int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase,
> +                    uint32_t seqNumber);
> +  int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option);
> +};
> +#endif
> +
> +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
> +                          (*(uint32_t*)0x0020001c + 16)))
> +
> +static bool s_flash_irq_disabled;
> +
> +MG_IRAM void *mg_flash_start(void) {
> +  return (void *) 0x60000000;
> +}
> +MG_IRAM size_t mg_flash_size(void) {
> +  return 8 * 1024 * 1024;
> +}
> +MG_IRAM size_t mg_flash_sector_size(void) {
> +  return 4 * 1024;  // 4k
> +}
> +MG_IRAM size_t mg_flash_write_align(void) {
> +  return 256;
> +}
> +MG_IRAM int mg_flash_bank(void) {
> +  return 0;
>   }
>
> -static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
> -                         struct mg_dns *dnsc, bool ipv6) {
> -  struct dns_data *d = NULL;
> -  if (dnsc->url == NULL) {
> -    mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> -  } else if (dnsc->c == NULL) {
> -    dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> -    if (dnsc->c != NULL) {
> -      dnsc->c->pfn = dns_cb;
> -      // dnsc->c->is_hexdumping = 1;
> -    }
> +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> +  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> +  volatile char *p = (char *) dst;
> +  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
> +}
> +
> +// Note: the get_config function below works both for RT1020 and 1060
> +#if MG_DEVICE == MG_DEVICE_RT1020
> +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) {
> +  struct mg_flexspi_nor_config default_config = {
> +      .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
> +                    .version = MG_FLEXSPI_CFG_BLK_VERSION,
> +                    .readSampleClkSrc = 1,  // ReadSampleClk_LoopbackFromDqsPad
> +                    .csHoldTime = 3,
> +                    .csSetupTime = 3,
> +                    .controllerMiscOption = MG_BIT(4),
> +                    .deviceType = 1,  // serial NOR
> +                    .sflashPadType = 4,
> +                    .serialClkFreq = 7,  // 133MHz
> +                    .sflashA1Size = 8 * 1024 * 1024,
> +                    .lookupTable = MG_FLEXSPI_QSPI_LUT},
> +      .pageSize = 256,
> +      .sectorSize = 4 * 1024,
> +      .ipcmdSerialClkFreq = 1,
> +      .blockSize = 64 * 1024,
> +      .isUniformBlockSize = false};
> +
> +  *config = default_config;
> +  return 0;
> +}
> +#else
> +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) {
> +  uint32_t options[] = {0xc0000000, 0x00};
> +
> +  MG_ARM_DISABLE_IRQ();
> +  uint32_t status =
> +      flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
> +  if (!s_flash_irq_disabled) {
> +    MG_ARM_ENABLE_IRQ();
>     }
> -  if (dnsc->c == NULL) {
> -    mg_error(c, "resolver");
> -  } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
> -    mg_error(c, "resolve OOM");
> -  } else {
> -    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
> -    d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> -    d->next = (struct dns_data *) c->mgr->active_dns_requests;
> -    c->mgr->active_dns_requests = d;
> -    d->expire = mg_millis() + (uint64_t) ms;
> -    d->c = c;
> -    c->is_resolving = 1;
> -    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
> -                name->ptr, &dnsc->url, d->txnid));
> -    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> -      mg_error(dnsc->c, "DNS send");
> -    }
> +  if (status) {
> +    MG_ERROR(("Failed to extract flash configuration: status %u", status));
>     }
> +  return status;
>   }
> +#endif
>
> -void mg_resolve(struct mg_connection *c, const char *url) {
> -  struct mg_str host = mg_url_host(url);
> -  c->rem.port = mg_htons(mg_url_port(url));
> -  if (mg_aton(host, &c->rem)) {
> -    // host is an IP address, do not fire name resolution
> -    mg_connect_resolved(c);
> -  } else {
> -    // host is not an IP, send DNS resolution request
> -    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
> -    mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> +MG_IRAM bool mg_flash_erase(void *addr) {
> +  struct mg_flexspi_nor_config config;
> +  if (flexspi_nor_get_config(&config) != 0) {
> +    return false;
> +  }
> +  if (flash_page_start(addr) == false) {
> +    MG_ERROR(("%p is not on a sector boundary", addr));
> +    return false;
> +  }
> +
> +  void *dst = (void *)((char *) addr - (char *) mg_flash_start());
> +
> +  // Note: Interrupts must be disabled before any call to the ROM API on RT1020
> +  // and 1060
> +  MG_ARM_DISABLE_IRQ();
> +  bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst,
> +                                mg_flash_sector_size()) == 0);
> +  if (!s_flash_irq_disabled) {
> +    MG_ARM_ENABLE_IRQ();  // Reenable them after the call
>     }
> +  MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail"));
> +  return ok;
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/event.c"
> -#endif
> +MG_IRAM bool mg_flash_swap_bank(void) {
> +  return true;
> +}
> +
> +static inline void spin(volatile uint32_t count) {
> +  while (count--) (void) 0;
> +}
> +
> +static inline void flash_wait(void) {
> +  while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0)
> +    spin(1);
> +}
>
> +MG_IRAM static void *flash_code_location(void) {
> +  return (void *) ((char *) mg_flash_start() + 0x2000);
> +}
>
> +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
> +  struct mg_flexspi_nor_config config;
> +  if (flexspi_nor_get_config(&config) != 0) {
> +    return false;
> +  }
> +  if ((len % mg_flash_write_align()) != 0) {
> +    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> +    return false;
> +  }
>
> +  if ((char *) addr < (char *) mg_flash_start()) {
> +    MG_ERROR(("Invalid flash write address: %p", addr));
> +    return false;
> +  }
>
> +  uint32_t *dst = (uint32_t *) addr;
> +  uint32_t *src = (uint32_t *) buf;
> +  uint32_t *end = (uint32_t *) ((char *) buf + len);
> +  bool ok = true;
> +
> +  // Note: If we overwrite the flash irq section of the image, we must also
> +  // make sure interrupts are disabled and are not reenabled until we write
> +  // this sector with another irq table.
> +  if ((char *) addr == (char *) flash_code_location()) {
> +    s_flash_irq_disabled = true;
> +    MG_ARM_DISABLE_IRQ();
> +  }
>
> -void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> -  // Run user-defined handler first, in order to give it an ability
> -  // to intercept processing (e.g. clean input buffer) before the
> -  // protocol handler kicks in
> -  if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
> -  if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
> +  while (ok && src < end) {
> +    if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
> +      break;
> +    }
> +    uint32_t status;
> +    uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
> +    if ((char *) buf >= (char *) mg_flash_start()) {
> +      // If we copy from FLASH to FLASH, then we first need to copy the source
> +      // to RAM
> +      size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
> +      uint32_t tmp[tmp_buf_size];
> +
> +      for (size_t i = 0; i < tmp_buf_size; i++) {
> +        flash_wait();
> +        tmp[i] = src[i];
> +      }
> +      MG_ARM_DISABLE_IRQ();
> +      status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> +                                    (uint32_t) dst_ofs, tmp);
> +    } else {
> +      MG_ARM_DISABLE_IRQ();
> +      status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> +                                    (uint32_t) dst_ofs, src);
> +    }
> +    if (!s_flash_irq_disabled) {
> +      MG_ARM_ENABLE_IRQ();
> +    }
> +    src = (uint32_t *) ((char *) src + mg_flash_write_align());
> +    dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
> +    if (status != 0) {
> +      ok = false;
> +    }
> +  }
> +  MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail"));
> +  return ok;
>   }
>
> -void mg_error(struct mg_connection *c, const char *fmt, ...) {
> -  char buf[64];
> -  va_list ap;
> -  va_start(ap, fmt);
> -  mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> -  va_end(ap);
> -  MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
> -  c->is_closing = 1;             // Set is_closing before sending MG_EV_CALL
> -  mg_call(c, MG_EV_ERROR, buf);  // Let user handler to override it
> +MG_IRAM void mg_device_reset(void) {
> +  MG_DEBUG(("Resetting device..."));
> +  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>   }
>
> +#endif
> +
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/fmt.c"
> +#line 1 "src/device_stm32h5.c"
>   #endif
>
>
>
> +#if MG_DEVICE == MG_DEVICE_STM32H5
>
> -static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
> -  struct mg_iobuf *io = (struct mg_iobuf *) param;
> -  if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
> -  if (io->len + 2 <= io->size) {
> -    io->buf[io->len++] = (uint8_t) ch;
> -    io->buf[io->len] = 0;
> -  } else if (io->len < io->size) {
> -    io->buf[io->len++] = 0;  // Guarantee to 0-terminate
> -  }
> -}
> +#define FLASH_BASE 0x40022000          // Base address of the flash controller
> +#define FLASH_KEYR (FLASH_BASE + 0x4)  // See RM0481 7.11
> +#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
> +#define FLASH_OPTCR (FLASH_BASE + 0x1c)
> +#define FLASH_NSSR (FLASH_BASE + 0x20)
> +#define FLASH_NSCR (FLASH_BASE + 0x28)
> +#define FLASH_NSCCR (FLASH_BASE + 0x30)
> +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
> +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
>
> -static void mg_putchar_iobuf_static(char ch, void *param) {
> -  mg_pfn_iobuf_private(ch, param, false);
> +void *mg_flash_start(void) {
> +  return (void *) 0x08000000;
>   }
> -
> -void mg_pfn_iobuf(char ch, void *param) {
> -  mg_pfn_iobuf_private(ch, param, true);
> +size_t mg_flash_size(void) {
> +  return 2 * 1024 * 1024;  // 2Mb
>   }
> -
> -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
> -  struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> -  size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> -  if (n < len) buf[n] = '\0';
> -  return n;
> +size_t mg_flash_sector_size(void) {
> +  return 8 * 1024;  // 8k
>   }
> -
> -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> -  va_list ap;
> -  size_t n;
> -  va_start(ap, fmt);
> -  n = mg_vsnprintf(buf, len, fmt, &ap);
> -  va_end(ap);
> -  return n;
> +size_t mg_flash_write_align(void) {
> +  return 16;  // 128 bit
>   }
> -
> -char *mg_vmprintf(const char *fmt, va_list *ap) {
> -  struct mg_iobuf io = {0, 0, 0, 256};
> -  mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> -  return (char *) io.buf;
> +int mg_flash_bank(void) {
> +  return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
>   }
>
> -char *mg_mprintf(const char *fmt, ...) {
> -  char *s;
> -  va_list ap;
> -  va_start(ap, fmt);
> -  s = mg_vmprintf(fmt, &ap);
> -  va_end(ap);
> -  return s;
> +static void flash_unlock(void) {
> +  static bool unlocked = false;
> +  if (unlocked == false) {
> +    MG_REG(FLASH_KEYR) = 0x45670123;
> +    MG_REG(FLASH_KEYR) = 0Xcdef89ab;
> +    MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
> +    MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
> +    unlocked = true;
> +  }
>   }
>
> -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
> -  size_t len = 0;
> -  va_list ap;
> -  va_start(ap, fmt);
> -  len = mg_vxprintf(out, ptr, fmt, &ap);
> -  va_end(ap);
> -  return len;
> +static int flash_page_start(volatile uint32_t *dst) {
> +  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> +  volatile char *p = (char *) dst;
> +  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
>   }
>
> -static bool is_digit(int c) {
> -  return c >= '0' && c <= '9';
> +static bool flash_is_err(void) {
> +  return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17);  // RM0481 7.11.9
>   }
>
> -static int addexp(char *buf, int e, int sign) {
> -  int n = 0;
> -  buf[n++] = 'e';
> -  buf[n++] = (char) sign;
> -  if (e > 400) return 0;
> -  if (e < 10) buf[n++] = '0';
> -  if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
> -  if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> -  buf[n++] = (char) (e + '0');
> -  return n;
> +static void flash_wait(void) {
> +  while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
> +         (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
> +    (void) 0;
> +  }
>   }
>
> -static int xisinf(double x) {
> -  union {
> -    double f;
> -    uint64_t u;
> -  } ieee754 = {x};
> -  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
> -         ((unsigned) ieee754.u == 0);
> +static void flash_clear_err(void) {
> +  flash_wait();                                    // Wait until ready
> +  MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U);  // Clear all errors
>   }
>
> -static int xisnan(double x) {
> -  union {
> -    double f;
> -    uint64_t u;
> -  } ieee754 = {x};
> -  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> -             ((unsigned) ieee754.u != 0) >
> -         0x7ff00000;
> +static bool flash_bank_is_swapped(void) {
> +  return MG_REG(FLASH_OPTCR) & MG_BIT(31);  // RM0481 7.11.8
>   }
>
> -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width) {
> -  char buf[40];
> -  int i, s = 0, n = 0, e = 0;
> -  double t, mul, saved;
> -  if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> -  if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf");
> -  if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> -  if (d < 0.0) d = -d, buf[s++] = '-';
> -
> -  // Round
> -  saved = d;
> -  mul = 1.0;
> -  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> -  while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> -  for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> -  d += t;
> -  // Calculate exponent, and 'mul' for scientific representation
> -  mul = 1.0;
> -  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> -  while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> -  // printf(" --> %g %d %g %g\n", saved, e, t, mul);
> -
> -  if (e >= width) {
> -    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> -    // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> -    n += addexp(buf + s + n, e, '+');
> -    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> -  } else if (e <= -width) {
> -    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> -    // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> -    n += addexp(buf + s + n, -e, '-');
> -    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> +bool mg_flash_erase(void *location) {
> +  bool ok = false;
> +  if (flash_page_start(location) == false) {
> +    MG_ERROR(("%p is not on a sector boundary"));
>     } else {
> -    for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> -      int ch = (int) (d / t);
> -      if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> -      d -= ch * t;
> -      t /= 10.0;
> -    }
> -    // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
> -    if (n == 0) buf[s++] = '0';
> -    while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
> -    if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> -    // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> -    for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
> -      int ch = (int) (d / t);
> -      buf[s + n++] = (char) (ch + '0');
> -      d -= ch * t;
> -      t /= 10.0;
> +    uintptr_t diff = (char *) location - (char *) mg_flash_start();
> +    uint32_t sector = diff / mg_flash_sector_size();
> +    uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
> +    flash_unlock();
> +    flash_clear_err();
> +    MG_REG(FLASH_NSCR) = 0;
> +    if ((sector < 128 && flash_bank_is_swapped()) ||
> +        (sector > 127 && !flash_bank_is_swapped())) {
> +      MG_REG(FLASH_NSCR) |= MG_BIT(31);  // Set FLASH_CR_BKSEL
>       }
> +    if (sector > 127) sector -= 128;
> +    MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6);  // Erase | sector_num
> +    MG_REG(FLASH_NSCR) |= MG_BIT(5);                  // Start erasing
> +    flash_wait();
> +    ok = !flash_is_err();
> +    MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location,
> +              ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
> +    // mg_hexdump(location, 32);
> +    MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
>     }
> -  while (n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeros
> -  if (n > 0 && buf[s + n - 1] == '.') n--;     // Trim trailing dot
> -  n += s;
> -  if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> -  buf[n] = '\0';
> -  return mg_snprintf(dst, dstlen, "%s", buf);
> +  return ok;
>   }
>
> -static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) {
> -  const char *letters = "0123456789abcdef";
> -  uint64_t v = (uint64_t) val;
> -  size_t s = 0, n, i;
> -  if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> -  // This loop prints a number in reverse order. I guess this is because we
> -  // write numbers from right to left: least significant digit comes last.
> -  // Maybe because we use Arabic numbers, and Arabs write RTL?
> -  if (is_hex) {
> -    for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> -  } else {
> -    for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
> +bool mg_flash_swap_bank(void) {
> +  uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
> +  flash_unlock();
> +  flash_clear_err();
> +  // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> +  MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
> +  // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> +  MG_REG(FLASH_OPTCR) |= MG_BIT(1);  // OPTSTART
> +  while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
> +  return true;
> +}
> +
> +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> +  if ((len % mg_flash_write_align()) != 0) {
> +    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> +    return false;
>     }
> -  // Reverse a string
> -  for (i = 0; i < n / 2; i++) {
> -    char t = buf[s + i];
> -    buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> +  uint32_t *dst = (uint32_t *) addr;
> +  uint32_t *src = (uint32_t *) buf;
> +  uint32_t *end = (uint32_t *) ((char *) buf + len);
> +  bool ok = true;
> +  flash_unlock();
> +  flash_clear_err();
> +  MG_ARM_DISABLE_IRQ();
> +  // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
> +  MG_REG(FLASH_NSCR) = MG_BIT(1);  // Set programming flag
> +  while (ok && src < end) {
> +    if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> +    *(volatile uint32_t *) dst++ = *src++;
> +    flash_wait();
> +    if (flash_is_err()) ok = false;
>     }
> -  if (val == 0) buf[n++] = '0';  // Handle special case
> -  return n + s;
> +  MG_ARM_ENABLE_IRQ();
> +  MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
> +            flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
> +            MG_REG(FLASH_NSSR)));
> +  MG_REG(FLASH_NSCR) = 0;  // Clear flags
> +  return ok;
>   }
>
> -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> -                   size_t len) {
> -  size_t i = 0;
> -  while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> -  return i;
> +void mg_device_reset(void) {
> +  // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> +  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>   }
> +#endif
>
> -static char mg_esc(int c, bool esc) {
> -  const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> -  for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> -    if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/device_stm32h7.c"
> +#endif
> +
> +
> +
> +#if MG_DEVICE == MG_DEVICE_STM32H7
> +
> +#define FLASH_BASE1 0x52002000  // Base address for bank1
> +#define FLASH_BASE2 0x52002100  // Base address for bank2
> +#define FLASH_KEYR 0x04         // See RM0433 4.9.2
> +#define FLASH_OPTKEYR 0x08
> +#define FLASH_OPTCR 0x18
> +#define FLASH_SR 0x10
> +#define FLASH_CR 0x0c
> +#define FLASH_CCR 0x14
> +#define FLASH_OPTSR_CUR 0x1c
> +#define FLASH_OPTSR_PRG 0x20
> +#define FLASH_SIZE_REG 0x1ff1e880
> +
> +MG_IRAM void *mg_flash_start(void) {
> +  return (void *) 0x08000000;
> +}
> +MG_IRAM size_t mg_flash_size(void) {
> +  return MG_REG(FLASH_SIZE_REG) * 1024;
> +}
> +MG_IRAM size_t mg_flash_sector_size(void) {
> +  return 128 * 1024;  // 128k
> +}
> +MG_IRAM size_t mg_flash_write_align(void) {
> +  return 32;  // 256 bit
> +}
> +MG_IRAM int mg_flash_bank(void) {
> +  if (mg_flash_size() < 2 * 1024 * 1024) return 0;  // No dual bank support
> +  return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
> +}
> +
> +MG_IRAM static void flash_unlock(void) {
> +  static bool unlocked = false;
> +  if (unlocked == false) {
> +    MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
> +    MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
> +    if (mg_flash_bank() > 0) {
> +      MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
> +      MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
> +    }
> +    MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b;  // opt reg is "shared"
> +    MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f;  // thus unlock once
> +    unlocked = true;
>     }
> -  return 0;
>   }
>
> -static char mg_escape(int c) {
> -  return mg_esc(c, true);
> +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> +  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> +  volatile char *p = (char *) dst;
> +  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
>   }
>
> -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> -                   size_t len) {
> -  size_t i = 0, extra = 0;
> -  for (i = 0; i < len && buf[i] != '\0'; i++) {
> -    char c = mg_escape(buf[i]);
> -    if (c) {
> -      out('\\', ptr), out(c, ptr), extra++;
> -    } else {
> -      out(buf[i], ptr);
> -    }
> -  }
> -  return i + extra;
> +MG_IRAM static bool flash_is_err(uint32_t bank) {
> +  return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17);  // RM0433 4.9.5
>   }
>
> -static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
> -                   size_t len) {
> -  size_t n = 2;
> -  out('"', ptr);
> -  n += qcpy(out, ptr, buf, len);
> -  out('"', ptr);
> -  return n;
> +MG_IRAM static void flash_wait(uint32_t bank) {
> +  while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0;
>   }
>
> -static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t *buf,
> -                   size_t len) {
> -  size_t i, n = 0;
> -  const char *t =
> -      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> -  out('"', ptr), n++;
> -  for (i = 0; i < len; i += 3) {
> -    uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> -            c3 = i + 2 < len ? buf[i + 2] : 0;
> -    char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
> -    if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> -    if (i + 2 < len) tmp[3] = t[c3 & 63];
> -    n += scpy(out, ptr, tmp, sizeof(tmp));
> -  }
> -  out('"', ptr), n++;
> -  return n;
> +MG_IRAM static void flash_clear_err(uint32_t bank) {
> +  flash_wait(bank);                                      // Wait until ready
> +  MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U);  // Clear all errors
>   }
>
> -size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
> -                   va_list *ap) {
> -  size_t i = 0, n = 0;
> -  while (fmt[i] != '\0') {
> -    if (fmt[i] == '%') {
> -      size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */;
> -      char pad = ' ', minus = 0, c = fmt[++i];
> -      if (c == '#') x++, c = fmt[++i];
> -      if (c == '-') minus++, c = fmt[++i];
> -      if (c == '0') pad = '0', c = fmt[++i];
> -      while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
> -      if (c == '.') {
> -        c = fmt[++i];
> -        if (c == '*') {
> -          pr = (size_t) va_arg(*ap, int);
> -          c = fmt[++i];
> -        } else {
> -          pr = 0;
> -          while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
> -        }
> -      }
> -      while (c == 'h') c = fmt[++i];  // Treat h and hh as int
> -      if (c == 'l') {
> -        is_long++, c = fmt[++i];
> -        if (c == 'l') is_long++, c = fmt[++i];
> -      }
> -      if (c == 'p') x = 1, is_long = 1;
> -      if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> -          c == 'g' || c == 'f') {
> -        bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> -        char tmp[40];
> -        size_t xl = x ? 2 : 0;
> -        if (c == 'g' || c == 'f') {
> -          double v = va_arg(*ap, double);
> -          if (pr == ~0U) pr = 6;
> -          k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
> -        } else if (is_long == 2) {
> -          int64_t v = va_arg(*ap, int64_t);
> -          k = mg_lld(tmp, v, s, h);
> -        } else if (is_long == 1) {
> -          long v = va_arg(*ap, long);
> -          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
> -        } else {
> -          int v = va_arg(*ap, int);
> -          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> -        }
> -        for (j = 0; j < xl && w > 0; j++) w--;
> -        for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> -          n += scpy(out, param, &pad, 1);
> -        n += scpy(out, param, (char *) "0x", xl);
> -        for (j = 0; pad == '0' && k < w && j + k < w; j++)
> -          n += scpy(out, param, &pad, 1);
> -        n += scpy(out, param, tmp, k);
> -        for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> -          n += scpy(out, param, &pad, 1);
> -      } else if (c == 'M') {
> -        mg_pm_t f = va_arg(*ap, mg_pm_t);
> -        n += f(out, param, ap);
> -      } else if (c == 'c') {
> -        int ch = va_arg(*ap, int);
> -        out((char) ch, param);
> -        n++;
> -      } else if (c == 'H') {
> -        // Print hex-encoded double-quoted string
> -        size_t bl = (size_t) va_arg(*ap, int);
> -        uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
> -        const char *hex = "0123456789abcdef";
> -        n += scpy(out, param, (char *) &dquote, 1);
> -        for (j = 0; j < bl; j++) {
> -          n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
> -          n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
> -        }
> -        n += scpy(out, param, (char *) &dquote, 1);
> -      } else if (c == 'I') {
> -        // Print IPv4 or IPv6 address
> -        size_t len = (size_t) va_arg(*ap, int);  // Length 16 means IPv6 address
> -        uint8_t *buf = va_arg(*ap, uint8_t *);   // Pointer to the IP address
> -        if (len == 6) {
> -          uint16_t *p = (uint16_t *) buf;
> -          n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]),
> -                          mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
> -                          mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
> -                          mg_htons(p[7]));
> -        } else {
> -          n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int) buf[1],
> -                          (int) buf[2], (int) buf[3]);
> -        }
> -      } else if (c == 'A') {
> -        // Print hardware addresses (currently Ethernet MAC)
> -        uint8_t *buf = va_arg(*ap, uint8_t *);  // Pointer to the hw address
> -        n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
> -                        (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
> -                        (int) buf[4], (int) buf[5]);
> -      } else if (c == 'V') {
> -        // Print base64-encoded double-quoted string
> -        size_t len = (size_t) va_arg(*ap, int);
> -        uint8_t *buf = va_arg(*ap, uint8_t *);
> -        n += bcpy(out, param, buf, len);
> -      } else if (c == 's' || c == 'Q' || c == 'q') {
> -        char *p = va_arg(*ap, char *);
> -        size_t (*f)(void (*)(char, void *), void *, char *, size_t) = scpy;
> -        if (c == 'Q') f = Qcpy;
> -        if (c == 'q') f = qcpy;
> -        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> -        for (j = 0; !minus && pr < w && j + pr < w; j++)
> -          n += f(out, param, &pad, 1);
> -        n += f(out, param, p, pr);
> -        for (j = 0; minus && pr < w && j + pr < w; j++)
> -          n += f(out, param, &pad, 1);
> -      } else if (c == '%') {
> -        out('%', param);
> -        n++;
> -      } else {
> -        out('%', param);
> -        out(c, param);
> -        n += 2;
> -      }
> -      i++;
> -    } else {
> -      out(fmt[i], param), n++, i++;
> -    }
> -  }
> -  return n;
> +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
> +  return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31);  // RM0433 4.9.7
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/fs.c"
> -#endif
> -
> -
> -
> -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) {
> -  struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> -  if (fd != NULL) {
> -    fd->fd = fs->op(path, flags);
> -    fd->fs = fs;
> -    if (fd->fd == NULL) {
> -      free(fd);
> -      fd = NULL;
> -    }
> -  }
> -  return fd;
> +// Figure out flash bank based on the address
> +MG_IRAM static uint32_t flash_bank(void *addr) {
> +  size_t ofs = (char *) addr - (char *) mg_flash_start();
> +  if (mg_flash_bank() == 0) return FLASH_BASE1;
> +  return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
>   }
>
> -void mg_fs_close(struct mg_fd *fd) {
> -  if (fd != NULL) {
> -    fd->fs->cl(fd->fd);
> -    free(fd);
> +MG_IRAM bool mg_flash_erase(void *addr) {
> +  bool ok = false;
> +  if (flash_page_start(addr) == false) {
> +    MG_ERROR(("%p is not on a sector boundary", addr));
> +  } else {
> +    uintptr_t diff = (char *) addr - (char *) mg_flash_start();
> +    uint32_t sector = diff / mg_flash_sector_size();
> +    uint32_t bank = flash_bank(addr);
> +    uint32_t saved_cr = MG_REG(bank + FLASH_CR);  // Save CR value
> +
> +    flash_unlock();
> +    if (sector > 7) sector -= 8;
> +
> +    flash_clear_err(bank);
> +    MG_REG(bank + FLASH_CR) = MG_BIT(5);             // 32-bit write parallelism
> +    MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U;  // Sector to erase
> +    MG_REG(bank + FLASH_CR) |= MG_BIT(2);            // Sector erase bit
> +    MG_REG(bank + FLASH_CR) |= MG_BIT(7);            // Start erasing
> +    ok = !flash_is_err(bank);
> +    MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
> +              ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> +              MG_REG(bank + FLASH_SR)));
> +    MG_REG(bank + FLASH_CR) = saved_cr;  // Restore CR
>     }
> +  return ok;
>   }
>
> -char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) {
> -  struct mg_fd *fd;
> -  char *data = NULL;
> -  size_t size = 0;
> -  fs->st(path, &size, NULL);
> -  if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
> -    data = (char *) calloc(1, size + 1);
> -    if (data != NULL) {
> -      if (fs->rd(fd->fd, data, size) != size) {
> -        free(data);
> -        data = NULL;
> -      } else {
> -        data[size] = '\0';
> -        if (sizep != NULL) *sizep = size;
> -      }
> -    }
> -    mg_fs_close(fd);
> -  }
> -  return data;
> +MG_IRAM bool mg_flash_swap_bank(void) {
> +  if (mg_flash_bank() == 0) return true;
> +  uint32_t bank = FLASH_BASE1;
> +  uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
> +  flash_unlock();
> +  flash_clear_err(bank);
> +  // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> +  MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
> +  // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> +  MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1);  // OPTSTART
> +  while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
> +  return true;
>   }
>
> -bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
> -                   size_t len) {
> -  bool result = false;
> -  struct mg_fd *fd;
> -  char tmp[MG_PATH_MAX];
> -  mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> -  if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> -    result = fs->wr(fd->fd, buf, len) == len;
> -    mg_fs_close(fd);
> -    if (result) {
> -      fs->rm(path);
> -      fs->mv(tmp, path);
> -    } else {
> -      fs->rm(tmp);
> -    }
> +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
> +  if ((len % mg_flash_write_align()) != 0) {
> +    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> +    return false;
>     }
> -  return result;
> +  uint32_t bank = flash_bank(addr);
> +  uint32_t *dst = (uint32_t *) addr;
> +  uint32_t *src = (uint32_t *) buf;
> +  uint32_t *end = (uint32_t *) ((char *) buf + len);
> +  bool ok = true;
> +  flash_unlock();
> +  flash_clear_err(bank);
> +  MG_REG(bank + FLASH_CR) = MG_BIT(1);   // Set programming flag
> +  MG_REG(bank + FLASH_CR) |= MG_BIT(5);  // 32-bit write parallelism
> +  MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
> +  MG_ARM_DISABLE_IRQ();
> +  while (ok && src < end) {
> +    if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> +    *(volatile uint32_t *) dst++ = *src++;
> +    flash_wait(bank);
> +    if (flash_is_err(bank)) ok = false;
> +  }
> +  MG_ARM_ENABLE_IRQ();
> +  MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
> +            ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> +            MG_REG(bank + FLASH_SR)));
> +  MG_REG(bank + FLASH_CR) &= ~MG_BIT(1);  // Clear programming flag
> +  return ok;
>   }
>
> -bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
> -  va_list ap;
> -  char *data;
> -  bool result = false;
> -  va_start(ap, fmt);
> -  data = mg_vmprintf(fmt, &ap);
> -  va_end(ap);
> -  result = mg_file_write(fs, path, data, strlen(data));
> -  free(data);
> -  return result;
> +MG_IRAM void mg_device_reset(void) {
> +  // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> +  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>   }
> +#endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/fs_fat.c"
> +#line 1 "src/dns.c"
>   #endif
>
>
>
> -#if MG_ENABLE_FATFS
> -#include <ff.h>
>
> -static int mg_days_from_epoch(int y, int m, int d) {
> -  y -= m <= 2;
> -  int era = y / 400;
> -  int yoe = y - era * 400;
> -  int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> -  int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> -  return era * 146097 + doe - 719468;
> -}
>
> -static time_t mg_timegm(const struct tm *t) {
> -  int year = t->tm_year + 1900;
> -  int month = t->tm_mon;  // 0-11
> -  if (month > 11) {
> -    year += month / 12;
> -    month %= 12;
> -  } else if (month < 0) {
> -    int years_diff = (11 - month) / 12;
> -    year -= years_diff;
> -    month += 12 * years_diff;
> -  }
> -  int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> -  return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
> -}
>
> -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> -  struct tm tm;
> -  memset(&tm, 0, sizeof(struct tm));
> -  tm.tm_sec = (ftime << 1) & 0x3e;
> -  tm.tm_min = ((ftime >> 5) & 0x3f);
> -  tm.tm_hour = ((ftime >> 11) & 0x1f);
> -  tm.tm_mday = (fdate & 0x1f);
> -  tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> -  tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> -  return mg_timegm(&tm);
> -}
>
> -static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> -  FILINFO fi;
> -  if (path[0] == '\0') {
> -    if (size) *size = 0;
> -    if (mtime) *mtime = 0;
> -    return MG_FS_DIR;
> -  } else if (f_stat(path, &fi) == 0) {
> -    if (size) *size = (size_t) fi.fsize;
> -    if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> -    return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0);
> -  } else {
> -    return 0;
> -  }
> -}
>
> -static void ff_list(const char *dir, void (*fn)(const char *, void *),
> -                    void *userdata) {
> -  DIR d;
> -  FILINFO fi;
> -  if (f_opendir(&d, dir) == FR_OK) {
> -    while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> -      if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> -      fn(fi.fname, userdata);
> -    }
> -    f_closedir(&d);
> -  }
> -}
> +struct dns_data {
> +  struct dns_data *next;
> +  struct mg_connection *c;
> +  uint64_t expire;
> +  uint16_t txnid;
> +};
>
> -static void *ff_open(const char *path, int flags) {
> -  FIL f;
> -  unsigned char mode = FA_READ;
> -  if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
> -  if (f_open(&f, path, mode) == 0) {
> -    FIL *fp = calloc(1, sizeof(*fp));
> -    memcpy(fp, &f, sizeof(*fp));
> -    return fp;
> -  } else {
> -    return NULL;
> -  }
> +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
> +                         struct mg_dns *, bool);
> +
> +static void mg_dns_free(struct dns_data **head, struct dns_data *d) {
> +  LIST_DELETE(struct dns_data, head, d);
> +  free(d);
>   }
>
> -static void ff_close(void *fp) {
> -  if (fp != NULL) {
> -    f_close((FIL *) fp);
> -    free(fp);
> +void mg_resolve_cancel(struct mg_connection *c) {
> +  struct dns_data *tmp, *d;
> +  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
> +  for (d = *head; d != NULL; d = tmp) {
> +    tmp = d->next;
> +    if (d->c == c) mg_dns_free(head, d);
>     }
>   }
>
> -static size_t ff_read(void *fp, void *buf, size_t len) {
> -  UINT n = 0, misalign = ((size_t) buf) & 3;
> -  if (misalign) {
> -    char aligned[4];
> -    f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> -    memcpy(buf, aligned, n);
> -  } else {
> -    f_read((FIL *) fp, buf, len, &n);
> +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
> +                                      char *to, size_t tolen, size_t j,
> +                                      int depth) {
> +  size_t i = 0;
> +  if (tolen > 0 && depth == 0) to[0] = '\0';
> +  if (depth > 5) return 0;
> +  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
> +  while (ofs + i + 1 < len) {
> +    size_t n = s[ofs + i];
> +    if (n == 0) {
> +      i++;
> +      break;
> +    }
> +    if (n & 0xc0) {
> +      size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
> +      // MG_INFO(("PTR %lx", (unsigned long) ptr));
> +      if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> +          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
> +        return 0;
> +      i += 2;
> +      break;
> +    }
> +    if (ofs + i + n + 1 >= len) return 0;
> +    if (j > 0) {
> +      if (j < tolen) to[j] = '.';
> +      j++;
> +    }
> +    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> +    j += n;
> +    i += n + 1;
> +    if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
> +    // MG_INFO(("--> [%s]", to));
>     }
> -  return n;
> +  if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
> +  return i;
>   }
>
> -static size_t ff_write(void *fp, const void *buf, size_t len) {
> -  UINT n = 0;
> -  return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
> +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
> +                                char *dst, size_t dstlen) {
> +  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
>   }
>
> -static size_t ff_seek(void *fp, size_t offset) {
> -  f_lseek((FIL *) fp, offset);
> -  return offset;
> +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> +                       bool is_question, struct mg_dns_rr *rr) {
> +  const uint8_t *s = buf + ofs, *e = &buf[len];
> +
> +  memset(rr, 0, sizeof(*rr));
> +  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
> +  if (len > 512) return 0;  //  Too large, we don't expect that
> +  if (s >= e) return 0;     //  Overflow
> +
> +  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
> +    return 0;
> +  s += rr->nlen + 4;
> +  if (s > e) return 0;
> +  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> +  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> +  if (is_question) return (size_t) (rr->nlen + 4);
> +
> +  s += 6;
> +  if (s > e) return 0;
> +  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> +  if (s + rr->alen > e) return 0;
> +  return (size_t) (rr->nlen + rr->alen + 10);
>   }
>
> -static bool ff_rename(const char *from, const char *to) {
> -  return f_rename(from, to) == FR_OK;
> +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
> +  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> +  struct mg_dns_rr rr;
> +  size_t i, n, num_answers, ofs = sizeof(*h);
> +  memset(dm, 0, sizeof(*dm));
> +
> +  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
> +  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
> +  num_answers = mg_ntohs(h->num_answers);
> +  if (num_answers > 10) {
> +    MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers));
> +    num_answers = 10;  // Sanity cap
> +  }
> +  dm->txnid = mg_ntohs(h->txnid);
> +
> +  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> +    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
> +    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> +    ofs += n;
> +  }
> +  for (i = 0; i < num_answers; i++) {
> +    if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
> +    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
> +    // dm->name));
> +    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> +    ofs += n;
> +
> +    if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> +      dm->addr.is_ip6 = false;
> +      memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> +      dm->resolved = true;
> +      break;  // Return success
> +    } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> +      dm->addr.is_ip6 = true;
> +      memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
> +      dm->resolved = true;
> +      break;  // Return success
> +    }
> +  }
> +  return true;
>   }
>
> -static bool ff_remove(const char *path) {
> -  return f_unlink(path) == FR_OK;
> +static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
> +  struct dns_data *d, *tmp;
> +  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
> +  if (ev == MG_EV_POLL) {
> +    uint64_t now = *(uint64_t *) ev_data;
> +    for (d = *head; d != NULL; d = tmp) {
> +      tmp = d->next;
> +      // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> +      if (now > d->expire) mg_error(d->c, "DNS timeout");
> +    }
> +  } else if (ev == MG_EV_READ) {
> +    struct mg_dns_message dm;
> +    int resolved = 0;
> +    if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> +      MG_ERROR(("Unexpected DNS response:"));
> +      mg_hexdump(c->recv.buf, c->recv.len);
> +    } else {
> +      // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
> +      for (d = *head; d != NULL; d = tmp) {
> +        tmp = d->next;
> +        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> +        if (dm.txnid != d->txnid) continue;
> +        if (d->c->is_resolving) {
> +          if (dm.resolved) {
> +            dm.addr.port = d->c->rem.port;  // Save port
> +            d->c->rem = dm.addr;            // Copy resolved address
> +            MG_DEBUG(
> +                ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem));
> +            mg_connect_resolved(d->c);
> +#if MG_ENABLE_IPV6
> +          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
> +                     c->mgr->use_dns6 == false) {
> +            struct mg_str x = mg_str(dm.name);
> +            mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> +#endif
> +          } else {
> +            mg_error(d->c, "%s DNS lookup failed", dm.name);
> +          }
> +        } else {
> +          MG_ERROR(("%lu already resolved", d->c->id));
> +        }
> +        mg_dns_free(head, d);
> +        resolved = 1;
> +      }
> +    }
> +    if (!resolved) MG_ERROR(("stray DNS reply"));
> +    c->recv.len = 0;
> +  } else if (ev == MG_EV_CLOSE) {
> +    for (d = *head; d != NULL; d = tmp) {
> +      tmp = d->next;
> +      mg_error(d->c, "DNS error");
> +      mg_dns_free(head, d);
> +    }
> +  }
>   }
>
> -static bool ff_mkdir(const char *path) {
> -  return f_mkdir(path) == FR_OK;
> +static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
> +                        uint16_t txnid, bool ipv6) {
> +  struct {
> +    struct mg_dns_header header;
> +    uint8_t data[256];
> +  } pkt;
> +  size_t i, n;
> +  memset(&pkt, 0, sizeof(pkt));
> +  pkt.header.txnid = mg_htons(txnid);
> +  pkt.header.flags = mg_htons(0x100);
> +  pkt.header.num_questions = mg_htons(1);
> +  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> +    if (name->buf[i] == '.' || i >= name->len) {
> +      pkt.data[n] = (uint8_t) (i - n);
> +      memcpy(&pkt.data[n + 1], name->buf + n, i - n);
> +      n = i + 1;
> +    }
> +    if (i >= name->len) break;
> +  }
> +  memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
> +  n += 5;
> +  if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
> +  // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
> +  // n += 6;
> +  return mg_send(c, &pkt, sizeof(pkt.header) + n);
>   }
>
> -struct mg_fs mg_fs_fat = {ff_stat,  ff_list, ff_open,   ff_close,  ff_read,
> -                          ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> -#endif
> +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
> +                         struct mg_dns *dnsc, bool ipv6) {
> +  struct dns_data *d = NULL;
> +  if (dnsc->url == NULL) {
> +    mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> +  } else if (dnsc->c == NULL) {
> +    dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> +    if (dnsc->c != NULL) {
> +      dnsc->c->pfn = dns_cb;
> +      // dnsc->c->is_hexdumping = 1;
> +    }
> +  }
> +  if (dnsc->c == NULL) {
> +    mg_error(c, "resolver");
> +  } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
> +    mg_error(c, "resolve OOM");
> +  } else {
> +    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
> +    d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> +    d->next = (struct dns_data *) c->mgr->active_dns_requests;
> +    c->mgr->active_dns_requests = d;
> +    d->expire = mg_millis() + (uint64_t) ms;
> +    d->c = c;
> +    c->is_resolving = 1;
> +    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
> +                name->buf, dnsc->url, d->txnid));
> +    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> +      mg_error(dnsc->c, "DNS send");
> +    }
> +  }
> +}
> +
> +void mg_resolve(struct mg_connection *c, const char *url) {
> +  struct mg_str host = mg_url_host(url);
> +  c->rem.port = mg_htons(mg_url_port(url));
> +  if (mg_aton(host, &c->rem)) {
> +    // host is an IP address, do not fire name resolution
> +    mg_connect_resolved(c);
> +  } else {
> +    // host is not an IP, send DNS resolution request
> +    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
> +    mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> +  }
> +}
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/fs_packed.c"
> +#line 1 "src/event.c"
>   #endif
>
>
>
>
> -struct packed_file {
> -  const char *data;
> -  size_t size;
> -  size_t pos;
> -};
>
> -const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
> -const char *mg_unlist(size_t no);
>
> -#if MG_ENABLE_PACKED_FS
> -#else
> -const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
> -  (void) path, (void) size, (void) mtime;
> -  return NULL;
> -}
> -const char *mg_unlist(size_t no) {
> -  (void) no;
> -  return NULL;
> -}
> +void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> +#if MG_ENABLE_PROFILE
> +  const char *names[] = {
> +      "EV_ERROR",    "EV_OPEN",      "EV_POLL",      "EV_RESOLVE",
> +      "EV_CONNECT",  "EV_ACCEPT",    "EV_TLS_HS",    "EV_READ",
> +      "EV_WRITE",    "EV_CLOSE",     "EV_HTTP_MSG",  "EV_HTTP_CHUNK",
> +      "EV_WS_OPEN",  "EV_WS_MSG",    "EV_WS_CTL",    "EV_MQTT_CMD",
> +      "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
> +  if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) {
> +    MG_PROF_ADD(c, names[ev]);
> +  }
>   #endif
> -
> -static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
> -  // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> -  return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> -         (n == 0 || path[n] == '/' || path[n - 1] == '/');
> +  // Fire protocol handler first, user handler second. See #2559
> +  if (c->pfn != NULL) c->pfn(c, ev, ev_data);
> +  if (c->fn != NULL) c->fn(c, ev, ev_data);
>   }
>
> -static int packed_stat(const char *path, size_t *size, time_t *mtime) {
> -  const char *p;
> -  size_t i, n = strlen(path);
> -  if (mg_unpack(path, size, mtime)) return MG_FS_READ;  // Regular file
> -  // Scan all files. If `path` is a dir prefix for any of them, it's a dir
> -  for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> -    if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
> -  }
> -  return 0;
> -}
> -
> -static void packed_list(const char *dir, void (*fn)(const char *, void *),
> -                        void *userdata) {
> -  char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> -  const char *path, *begin, *end;
> -  size_t i, n = strlen(dir);
> -  tmp[0] = '\0';  // Previously listed entry
> -  for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> -    if (!is_dir_prefix(dir, n, path)) continue;
> -    begin = &path[n + 1];
> -    end = strchr(begin, '/');
> -    if (end == NULL) end = begin + strlen(begin);
> -    mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> -    buf[sizeof(buf) - 1] = '\0';
> -    // If this entry has been already listed, skip
> -    // NOTE: we're assuming that file list is sorted alphabetically
> -    if (strcmp(buf, tmp) == 0) continue;
> -    fn(buf, userdata);  // Not yet listed, call user function
> -    strcpy(tmp, buf);   // And save this entry as listed
> -  }
> -}
> -
> -static void *packed_open(const char *path, int flags) {
> -  size_t size = 0;
> -  const char *data = mg_unpack(path, &size, NULL);
> -  struct packed_file *fp = NULL;
> -  if (data == NULL) return NULL;
> -  if (flags & MG_FS_WRITE) return NULL;
> -  fp = (struct packed_file *) calloc(1, sizeof(*fp));
> -  fp->size = size;
> -  fp->data = data;
> -  return (void *) fp;
> +void mg_error(struct mg_connection *c, const char *fmt, ...) {
> +  char buf[64];
> +  va_list ap;
> +  va_start(ap, fmt);
> +  mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> +  va_end(ap);
> +  MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
> +  c->is_closing = 1;             // Set is_closing before sending MG_EV_CALL
> +  mg_call(c, MG_EV_ERROR, buf);  // Let user handler override it
>   }
>
> -static void packed_close(void *fp) {
> -  if (fp != NULL) free(fp);
> -}
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/fmt.c"
> +#endif
>
> -static size_t packed_read(void *fd, void *buf, size_t len) {
> -  struct packed_file *fp = (struct packed_file *) fd;
> -  if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> -  memcpy(buf, &fp->data[fp->pos], len);
> -  fp->pos += len;
> -  return len;
> -}
>
> -static size_t packed_write(void *fd, const void *buf, size_t len) {
> -  (void) fd, (void) buf, (void) len;
> -  return 0;
> -}
>
> -static size_t packed_seek(void *fd, size_t offset) {
> -  struct packed_file *fp = (struct packed_file *) fd;
> -  fp->pos = offset;
> -  if (fp->pos > fp->size) fp->pos = fp->size;
> -  return fp->pos;
> -}
>
> -static bool packed_rename(const char *from, const char *to) {
> -  (void) from, (void) to;
> -  return false;
> +static bool is_digit(int c) {
> +  return c >= '0' && c <= '9';
>   }
>
> -static bool packed_remove(const char *path) {
> -  (void) path;
> -  return false;
> +static int addexp(char *buf, int e, int sign) {
> +  int n = 0;
> +  buf[n++] = 'e';
> +  buf[n++] = (char) sign;
> +  if (e > 400) return 0;
> +  if (e < 10) buf[n++] = '0';
> +  if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
> +  if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> +  buf[n++] = (char) (e + '0');
> +  return n;
>   }
>
> -static bool packed_mkdir(const char *path) {
> -  (void) path;
> -  return false;
> +static int xisinf(double x) {
> +  union {
> +    double f;
> +    uint64_t u;
> +  } ieee754 = {x};
> +  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
> +         ((unsigned) ieee754.u == 0);
>   }
>
> -struct mg_fs mg_fs_packed = {
> -    packed_stat,  packed_list, packed_open,   packed_close,  packed_read,
> -    packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
> -
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/fs_posix.c"
> -#endif
> -
> -
> -#if MG_ENABLE_FILE
> -
> -#ifndef MG_STAT_STRUCT
> -#define MG_STAT_STRUCT stat
> -#endif
> -
> -#ifndef MG_STAT_FUNC
> -#define MG_STAT_FUNC stat
> -#endif
> -
> -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> -#if !defined(S_ISDIR)
> -  MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
> -  return 0;
> -#else
> -#if MG_ARCH == MG_ARCH_WIN32
> -  struct _stati64 st;
> -  wchar_t tmp[MG_PATH_MAX];
> -  MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
> -  if (_wstati64(tmp, &st) != 0) return 0;
> -#else
> -  struct MG_STAT_STRUCT st;
> -  if (MG_STAT_FUNC(path, &st) != 0) return 0;
> -#endif
> -  if (size) *size = (size_t) st.st_size;
> -  if (mtime) *mtime = st.st_mtime;
> -  return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
> -#endif
> +static int xisnan(double x) {
> +  union {
> +    double f;
> +    uint64_t u;
> +  } ieee754 = {x};
> +  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> +             ((unsigned) ieee754.u != 0) >
> +         0x7ff00000;
>   }
>
> -#if MG_ARCH == MG_ARCH_WIN32
> -struct dirent {
> -  char d_name[MAX_PATH];
> -};
> -
> -typedef struct win32_dir {
> -  HANDLE handle;
> -  WIN32_FIND_DATAW info;
> -  struct dirent result;
> -} DIR;
> +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) {
> +  char buf[40];
> +  int i, s = 0, n = 0, e = 0;
> +  double t, mul, saved;
> +  if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> +  if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf");
> +  if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> +  if (d < 0.0) d = -d, buf[s++] = '-';
>
> -int gettimeofday(struct timeval *tv, void *tz) {
> -  FILETIME ft;
> -  unsigned __int64 tmpres = 0;
> +  // Round
> +  saved = d;
> +  mul = 1.0;
> +  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> +  while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> +  for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> +  d += t;
> +  // Calculate exponent, and 'mul' for scientific representation
> +  mul = 1.0;
> +  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> +  while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> +  // printf(" --> %g %d %g %g\n", saved, e, t, mul);
>
> -  if (tv != NULL) {
> -    GetSystemTimeAsFileTime(&ft);
> -    tmpres |= ft.dwHighDateTime;
> -    tmpres <<= 32;
> -    tmpres |= ft.dwLowDateTime;
> -    tmpres /= 10;  // convert into microseconds
> -    tmpres -= (int64_t) 11644473600000000;
> -    tv->tv_sec = (long) (tmpres / 1000000UL);
> -    tv->tv_usec = (long) (tmpres % 1000000UL);
> +  if (e >= width && width > 1) {
> +    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> +    // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> +    n += addexp(buf + s + n, e, '+');
> +    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> +  } else if (e <= -width && width > 1) {
> +    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> +    // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> +    n += addexp(buf + s + n, -e, '-');
> +    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> +  } else {
> +    for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> +      int ch = (int) (d / t);
> +      if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> +      d -= ch * t;
> +      t /= 10.0;
> +    }
> +    // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
> +    if (n == 0) buf[s++] = '0';
> +    while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
> +    if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> +    // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> +    for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
> +      int ch = (int) (d / t);
> +      buf[s + n++] = (char) (ch + '0');
> +      d -= ch * t;
> +      t /= 10.0;
> +    }
>     }
> -  (void) tz;
> -  return 0;
> +  while (tz && n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeroes
> +  if (n > 0 && buf[s + n - 1] == '.') n--;           // Trim trailing dot
> +  n += s;
> +  if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> +  buf[n] = '\0';
> +  return mg_snprintf(dst, dstlen, "%s", buf);
>   }
>
> -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
> -  int ret;
> -  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> -  strncpy(buf, path, sizeof(buf));
> -  buf[sizeof(buf) - 1] = '\0';
> -  // Trim trailing slashes. Leave backslash for paths like "X:\"
> -  p = buf + strlen(buf) - 1;
> -  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
> -  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> -  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
> -  // Convert back to Unicode. If doubly-converted string does not match the
> -  // original, something is fishy, reject.
> -  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
> -                      NULL, NULL);
> -  if (strcmp(buf, buf2) != 0) {
> -    wbuf[0] = L'\0';
> -    ret = 0;
> +static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) {
> +  const char *letters = "0123456789abcdef";
> +  uint64_t v = (uint64_t) val;
> +  size_t s = 0, n, i;
> +  if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> +  // This loop prints a number in reverse order. I guess this is because we
> +  // write numbers from right to left: least significant digit comes last.
> +  // Maybe because we use Arabic numbers, and Arabs write RTL?
> +  if (is_hex) {
> +    for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> +  } else {
> +    for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
>     }
> -  return ret;
> +  // Reverse a string
> +  for (i = 0; i < n / 2; i++) {
> +    char t = buf[s + i];
> +    buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> +  }
> +  if (val == 0) buf[n++] = '0';  // Handle special case
> +  return n + s;
>   }
>
> -DIR *opendir(const char *name) {
> -  DIR *d = NULL;
> -  wchar_t wpath[MAX_PATH];
> -  DWORD attrs;
> +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> +                          size_t len) {
> +  size_t i = 0;
> +  while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> +  return i;
> +}
>
> -  if (name == NULL) {
> -    SetLastError(ERROR_BAD_ARGUMENTS);
> -  } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> -    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
> -  } else {
> -    to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> -    attrs = GetFileAttributesW(wpath);
> -    if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> -      (void) wcscat(wpath, L"\\*");
> -      d->handle = FindFirstFileW(wpath, &d->info);
> -      d->result.d_name[0] = '\0';
> +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
> +  size_t len = 0;
> +  va_list ap;
> +  va_start(ap, fmt);
> +  len = mg_vxprintf(out, ptr, fmt, &ap);
> +  va_end(ap);
> +  return len;
> +}
> +
> +size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
> +                   va_list *ap) {
> +  size_t i = 0, n = 0;
> +  while (fmt[i] != '\0') {
> +    if (fmt[i] == '%') {
> +      size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */;
> +      char pad = ' ', minus = 0, c = fmt[++i];
> +      if (c == '#') x++, c = fmt[++i];
> +      if (c == '-') minus++, c = fmt[++i];
> +      if (c == '0') pad = '0', c = fmt[++i];
> +      while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
> +      if (c == '.') {
> +        c = fmt[++i];
> +        if (c == '*') {
> +          pr = (size_t) va_arg(*ap, int);
> +          c = fmt[++i];
> +        } else {
> +          pr = 0;
> +          while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
> +        }
> +      }
> +      while (c == 'h') c = fmt[++i];  // Treat h and hh as int
> +      if (c == 'l') {
> +        is_long++, c = fmt[++i];
> +        if (c == 'l') is_long++, c = fmt[++i];
> +      }
> +      if (c == 'p') x = 1, is_long = 1;
> +      if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> +          c == 'g' || c == 'f') {
> +        bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> +        char tmp[40];
> +        size_t xl = x ? 2 : 0;
> +        if (c == 'g' || c == 'f') {
> +          double v = va_arg(*ap, double);
> +          if (pr == ~0U) pr = 6;
> +          k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
> +        } else if (is_long == 2) {
> +          int64_t v = va_arg(*ap, int64_t);
> +          k = mg_lld(tmp, v, s, h);
> +        } else if (is_long == 1) {
> +          long v = va_arg(*ap, long);
> +          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
> +        } else {
> +          int v = va_arg(*ap, int);
> +          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> +        }
> +        for (j = 0; j < xl && w > 0; j++) w--;
> +        for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> +          n += scpy(out, param, &pad, 1);
> +        n += scpy(out, param, (char *) "0x", xl);
> +        for (j = 0; pad == '0' && k < w && j + k < w; j++)
> +          n += scpy(out, param, &pad, 1);
> +        n += scpy(out, param, tmp, k);
> +        for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> +          n += scpy(out, param, &pad, 1);
> +      } else if (c == 'm' || c == 'M') {
> +        mg_pm_t f = va_arg(*ap, mg_pm_t);
> +        if (c == 'm') out('"', param);
> +        n += f(out, param, ap);
> +        if (c == 'm') n += 2, out('"', param);
> +      } else if (c == 'c') {
> +        int ch = va_arg(*ap, int);
> +        out((char) ch, param);
> +        n++;
> +      } else if (c == 's') {
> +        char *p = va_arg(*ap, char *);
> +        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> +        for (j = 0; !minus && pr < w && j + pr < w; j++)
> +          n += scpy(out, param, &pad, 1);
> +        n += scpy(out, param, p, pr);
> +        for (j = 0; minus && pr < w && j + pr < w; j++)
> +          n += scpy(out, param, &pad, 1);
> +      } else if (c == '%') {
> +        out('%', param);
> +        n++;
> +      } else {
> +        out('%', param);
> +        out(c, param);
> +        n += 2;
> +      }
> +      i++;
>       } else {
> -      free(d);
> -      d = NULL;
> +      out(fmt[i], param), n++, i++;
>       }
>     }
> -  return d;
> +  return n;
>   }
>
> -int closedir(DIR *d) {
> -  int result = 0;
> -  if (d != NULL) {
> -    if (d->handle != INVALID_HANDLE_VALUE)
> -      result = FindClose(d->handle) ? 0 : -1;
> -    free(d);
> -  } else {
> -    result = -1;
> -    SetLastError(ERROR_BAD_ARGUMENTS);
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/fs.c"
> +#endif
> +
> +
> +
> +
> +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) {
> +  struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> +  if (fd != NULL) {
> +    fd->fd = fs->op(path, flags);
> +    fd->fs = fs;
> +    if (fd->fd == NULL) {
> +      free(fd);
> +      fd = NULL;
> +    }
>     }
> -  return result;
> +  return fd;
>   }
>
> -struct dirent *readdir(DIR *d) {
> -  struct dirent *result = NULL;
> -  if (d != NULL) {
> -    memset(&d->result, 0, sizeof(d->result));
> -    if (d->handle != INVALID_HANDLE_VALUE) {
> -      result = &d->result;
> -      WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
> -                          sizeof(result->d_name), NULL, NULL);
> -      if (!FindNextFileW(d->handle, &d->info)) {
> -        FindClose(d->handle);
> -        d->handle = INVALID_HANDLE_VALUE;
> -      }
> -    } else {
> -      SetLastError(ERROR_FILE_NOT_FOUND);
> -    }
> -  } else {
> -    SetLastError(ERROR_BAD_ARGUMENTS);
> +void mg_fs_close(struct mg_fd *fd) {
> +  if (fd != NULL) {
> +    fd->fs->cl(fd->fd);
> +    free(fd);
>     }
> -  return result;
>   }
> -#endif
>
> -static void p_list(const char *dir, void (*fn)(const char *, void *),
> -                   void *userdata) {
> -#if MG_ENABLE_DIRLIST
> -  struct dirent *dp;
> -  DIR *dirp;
> -  if ((dirp = (opendir(dir))) == NULL) return;
> -  while ((dp = readdir(dirp)) != NULL) {
> -    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
> -    fn(dp->d_name, userdata);
> +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
> +  struct mg_str result = {NULL, 0};
> +  void *fp;
> +  fs->st(path, &result.len, NULL);
> +  if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
> +    result.buf = (char *) calloc(1, result.len + 1);
> +    if (result.buf != NULL &&
> +        fs->rd(fp, (void *) result.buf, result.len) != result.len) {
> +      free((void *) result.buf);
> +      result.buf = NULL;
> +    }
> +    fs->cl(fp);
>     }
> -  closedir(dirp);
> -#else
> -  (void) dir, (void) fn, (void) userdata;
> -#endif
> +  if (result.buf == NULL) result.len = 0;
> +  return result;
>   }
>
> -static void *p_open(const char *path, int flags) {
> -  const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> -#if MG_ARCH == MG_ARCH_WIN32
> -  wchar_t b1[MG_PATH_MAX], b2[10];
> -  MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
> -  MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
> -  return (void *) _wfopen(b1, b2);
> -#else
> -  return (void *) fopen(path, mode);
> -#endif
> +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
> +                   size_t len) {
> +  bool result = false;
> +  struct mg_fd *fd;
> +  char tmp[MG_PATH_MAX];
> +  mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> +  if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> +    result = fs->wr(fd->fd, buf, len) == len;
> +    mg_fs_close(fd);
> +    if (result) {
> +      fs->rm(path);
> +      fs->mv(tmp, path);
> +    } else {
> +      fs->rm(tmp);
> +    }
> +  }
> +  return result;
>   }
>
> -static void p_close(void *fp) {
> -  fclose((FILE *) fp);
> +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
> +  va_list ap;
> +  char *data;
> +  bool result = false;
> +  va_start(ap, fmt);
> +  data = mg_vmprintf(fmt, &ap);
> +  va_end(ap);
> +  result = mg_file_write(fs, path, data, strlen(data));
> +  free(data);
> +  return result;
>   }
>
> -static size_t p_read(void *fp, void *buf, size_t len) {
> -  return fread(buf, 1, len, (FILE *) fp);
> +// This helper function allows to scan a filesystem in a sequential way,
> +// without using callback function:
> +//      char buf[100] = "";
> +//      while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
> +//        ...
> +static void mg_fs_ls_fn(const char *filename, void *param) {
> +  struct mg_str *s = (struct mg_str *) param;
> +  if (s->buf[0] == '\0') {
> +    mg_snprintf((char *) s->buf, s->len, "%s", filename);
> +  } else if (strcmp(s->buf, filename) == 0) {
> +    ((char *) s->buf)[0] = '\0';  // Fetch next file
> +  }
>   }
>
> -static size_t p_write(void *fp, const void *buf, size_t len) {
> -  return fwrite(buf, 1, len, (FILE *) fp);
> +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) {
> +  struct mg_str s = {buf, len};
> +  fs->ls(path, mg_fs_ls_fn, &s);
> +  return buf[0] != '\0';
>   }
>
> -static size_t p_seek(void *fp, size_t offset) {
> -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) ||  \
> -    (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> -    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> -  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> -#else
> -  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/fs_fat.c"
>   #endif
> -  return (size_t) ftell((FILE *) fp);
> -}
>
> -static bool p_rename(const char *from, const char *to) {
> -  return rename(from, to) == 0;
> -}
>
> -static bool p_remove(const char *path) {
> -  return remove(path) == 0;
> -}
>
> -static bool p_mkdir(const char *path) {
> -  return mkdir(path, 0775) == 0;
> -}
> +#if MG_ENABLE_FATFS
> +#include <ff.h>
>
> -#else
> +static int mg_days_from_epoch(int y, int m, int d) {
> +  y -= m <= 2;
> +  int era = y / 400;
> +  int yoe = y - era * 400;
> +  int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> +  int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> +  return era * 146097 + doe - 719468;
> +}
>
> -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> -  (void) path, (void) size, (void) mtime;
> -  return 0;
> +static time_t mg_timegm(const struct tm *t) {
> +  int year = t->tm_year + 1900;
> +  int month = t->tm_mon;  // 0-11
> +  if (month > 11) {
> +    year += month / 12;
> +    month %= 12;
> +  } else if (month < 0) {
> +    int years_diff = (11 - month) / 12;
> +    year -= years_diff;
> +    month += 12 * years_diff;
> +  }
> +  int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> +  return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
>   }
> -static void p_list(const char *path, void (*fn)(const char *, void *),
> -                   void *userdata) {
> -  (void) path, (void) fn, (void) userdata;
> -}
> -static void *p_open(const char *path, int flags) {
> -  (void) path, (void) flags;
> -  return NULL;
> -}
> -static void p_close(void *fp) {
> -  (void) fp;
> -}
> -static size_t p_read(void *fd, void *buf, size_t len) {
> -  (void) fd, (void) buf, (void) len;
> -  return 0;
> -}
> -static size_t p_write(void *fd, const void *buf, size_t len) {
> -  (void) fd, (void) buf, (void) len;
> -  return 0;
> -}
> -static size_t p_seek(void *fd, size_t offset) {
> -  (void) fd, (void) offset;
> -  return (size_t) ~0;
> -}
> -static bool p_rename(const char *from, const char *to) {
> -  (void) from, (void) to;
> -  return false;
> -}
> -static bool p_remove(const char *path) {
> -  (void) path;
> -  return false;
> -}
> -static bool p_mkdir(const char *path) {
> -  (void) path;
> -  return false;
> -}
> -#endif
> -
> -struct mg_fs mg_fs_posix = {p_stat,  p_list, p_open,   p_close,  p_read,
> -                            p_write, p_seek, p_rename, p_remove, p_mkdir};
> -
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/http.c"
> -#endif
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -
> -// Chunk deletion marker is the MSB in the "processed" counter
> -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> -
> -// Multipart POST example:
> -// --xyz
> -// Content-Disposition: form-data; name="val"
> -//
> -// abcdef
> -// --xyz
> -// Content-Disposition: form-data; name="foo"; filename="a.txt"
> -// Content-Type: text/plain
> -//
> -// hello world
> -//
> -// --xyz--
> -size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> -                              struct mg_http_part *part) {
> -  struct mg_str cd = mg_str_n("Content-Disposition", 19);
> -  const char *s = body.ptr;
> -  size_t b = ofs, h1, h2, b1, b2, max = body.len;
> -
> -  // Init part params
> -  if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
> -
> -  // Skip boundary
> -  while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> -  if (b <= ofs || b + 2 >= max) return 0;
> -  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
> -
> -  // Skip headers
> -  h1 = h2 = b + 2;
> -  for (;;) {
> -    while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> -    if (h2 == h1) break;
> -    if (h2 + 2 >= max) return 0;
> -    // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> -    if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
> -        mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
> -      struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
> -      part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> -      part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
> -    }
> -    h1 = h2 = h2 + 2;
> -  }
> -  b1 = b2 = h2 + 2;
> -  while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
> -                                           memcmp(&s[b2 + 2], s, b - ofs) == 0))
> -    b2++;
>
> -  if (b2 + 2 >= max) return 0;
> -  if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> -  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> -  return b2 + 2;
> +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> +  struct tm tm;
> +  memset(&tm, 0, sizeof(struct tm));
> +  tm.tm_sec = (ftime << 1) & 0x3e;
> +  tm.tm_min = ((ftime >> 5) & 0x3f);
> +  tm.tm_hour = ((ftime >> 11) & 0x1f);
> +  tm.tm_mday = (fdate & 0x1f);
> +  tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> +  tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> +  return mg_timegm(&tm);
>   }
>
> -void mg_http_bauth(struct mg_connection *c, const char *user,
> -                   const char *pass) {
> -  struct mg_str u = mg_str(user), p = mg_str(pass);
> -  size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> -  if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> -  if (c->send.size >= need) {
> -    int i, n = 0;
> -    char *buf = (char *) &c->send.buf[c->send.len];
> -    memcpy(buf, "Authorization: Basic ", 21);  // DON'T use mg_send!
> -    for (i = 0; i < (int) u.len; i++) {
> -      n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
> -    }
> -    if (p.len > 0) {
> -      n = mg_base64_update(':', buf + 21, n);
> -      for (i = 0; i < (int) p.len; i++) {
> -        n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
> -      }
> -    }
> -    n = mg_base64_final(buf + 21, n);
> -    c->send.len += 21 + (size_t) n + 2;
> -    memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> +static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> +  FILINFO fi;
> +  if (path[0] == '\0') {
> +    if (size) *size = 0;
> +    if (mtime) *mtime = 0;
> +    return MG_FS_DIR;
> +  } else if (f_stat(path, &fi) == 0) {
> +    if (size) *size = (size_t) fi.fsize;
> +    if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> +    return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0);
>     } else {
> -    MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
> +    return 0;
>     }
>   }
>
> -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> -  struct mg_str k, v, result = mg_str_n(NULL, 0);
> -  while (mg_split(&buf, &k, &v, '&')) {
> -    if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) {
> -      result = v;
> -      break;
> +static void ff_list(const char *dir, void (*fn)(const char *, void *),
> +                    void *userdata) {
> +  DIR d;
> +  FILINFO fi;
> +  if (f_opendir(&d, dir) == FR_OK) {
> +    while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> +      if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> +      fn(fi.fname, userdata);
>       }
> +    f_closedir(&d);
>     }
> -  return result;
>   }
>
> -int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
> -                    size_t dst_len) {
> -  int len;
> -  if (dst == NULL || dst_len == 0) {
> -    len = -2;  // Bad destination
> -  } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
> -    len = -1;  // Bad source
> -    dst[0] = '\0';
> -  } else {
> -    struct mg_str v = mg_http_var(*buf, mg_str(name));
> -    if (v.ptr == NULL) {
> -      len = -4;  // Name does not exist
> -    } else {
> -      len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
> -      if (len < 0) len = -3;  // Failed to decode
> +static void *ff_open(const char *path, int flags) {
> +  FIL f;
> +  unsigned char mode = FA_READ;
> +  if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
> +  if (f_open(&f, path, mode) == 0) {
> +    FIL *fp;
> +    if ((fp = calloc(1, sizeof(*fp))) != NULL) {
> +      memcpy(fp, &f, sizeof(*fp));
> +      return fp;
>       }
>     }
> -  return len;
> +  return NULL;
>   }
>
> -static bool isx(int c) {
> -  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> -         (c >= 'A' && c <= 'F');
> +static void ff_close(void *fp) {
> +  if (fp != NULL) {
> +    f_close((FIL *) fp);
> +    free(fp);
> +  }
>   }
>
> -int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
> -                  int is_form_url_encoded) {
> -  size_t i, j;
> -  for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> -    if (src[i] == '%') {
> -      // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
> -      if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> -        mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
> -        i += 2;
> -      } else {
> -        return -1;
> -      }
> -    } else if (is_form_url_encoded && src[i] == '+') {
> -      dst[j] = ' ';
> -    } else {
> -      dst[j] = src[i];
> -    }
> +static size_t ff_read(void *fp, void *buf, size_t len) {
> +  UINT n = 0, misalign = ((size_t) buf) & 3;
> +  if (misalign) {
> +    char aligned[4];
> +    f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> +    memcpy(buf, aligned, n);
> +  } else {
> +    f_read((FIL *) fp, buf, len, &n);
>     }
> -  if (j < dst_len) dst[j] = '\0';  // Null-terminate the destination
> -  return i >= src_len && j < dst_len ? (int) j : -1;
> +  return n;
>   }
>
> -static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' '; }
> -
> -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
> -  size_t i;
> -  for (i = 0; i < buf_len; i++) {
> -    if (!isok(buf[i])) return -1;
> -    if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> -        (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
> -      return (int) i + 1;
> -  }
> -  return 0;
> +static size_t ff_write(void *fp, const void *buf, size_t len) {
> +  UINT n = 0;
> +  return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
>   }
>
> -static const char *skip(const char *s, const char *e, const char *d,
> -                        struct mg_str *v) {
> -  v->ptr = s;
> -  while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
> -  v->len = (size_t) (s - v->ptr);
> -  while (s < e && strchr(d, *s) != NULL) s++;
> -  return s;
> +static size_t ff_seek(void *fp, size_t offset) {
> +  f_lseek((FIL *) fp, offset);
> +  return offset;
>   }
>
> -struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
> -  size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
> -  for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> -    struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> -    if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
> -  }
> -  return NULL;
> +static bool ff_rename(const char *from, const char *to) {
> +  return f_rename(from, to) == FR_OK;
>   }
>
> -static void mg_http_parse_headers(const char *s, const char *end,
> -                                  struct mg_http_header *h, int max_headers) {
> -  int i;
> -  for (i = 0; i < max_headers; i++) {
> -    struct mg_str k, v, tmp;
> -    const char *he = skip(s, end, "\n", &tmp);
> -    s = skip(s, he, ": \r\n", &k);
> -    s = skip(s, he, "\r\n", &v);
> -    if (k.len == tmp.len) continue;
> -    while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--;  // Trim spaces
> -    if (k.len == 0) break;
> -    // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
> -    //(int) k.len, k.ptr, (int) v.len, v.ptr));
> -    h[i].name = k;
> -    h[i].value = v;
> -  }
> +static bool ff_remove(const char *path) {
> +  return f_unlink(path) == FR_OK;
>   }
>
> -int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
> -  int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
> -  const char *end = s == NULL ? NULL : s + req_len, *qs;  // Cannot add to NULL
> -  struct mg_str *cl;
> +static bool ff_mkdir(const char *path) {
> +  return f_mkdir(path) == FR_OK;
> +}
>
> -  memset(hm, 0, sizeof(*hm));
> -  if (req_len <= 0) return req_len;
> +struct mg_fs mg_fs_fat = {ff_stat,  ff_list, ff_open,   ff_close,  ff_read,
> +                          ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> +#endif
>
> -  hm->message.ptr = hm->head.ptr = s;
> -  hm->body.ptr = end;
> -  hm->head.len = (size_t) req_len;
> -  hm->chunk.ptr = end;
> -  hm->message.len = hm->body.len = (size_t) ~0;  // Set body length to infinite
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/fs_packed.c"
> +#endif
>
> -  // Parse request line
> -  s = skip(s, end, " ", &hm->method);
> -  s = skip(s, end, " ", &hm->uri);
> -  s = skip(s, end, "\r\n", &hm->proto);
>
> -  // Sanity check. Allow protocol/reason to be empty
> -  if (hm->method.len == 0 || hm->uri.len == 0) return -1;
>
> -  // If URI contains '?' character, setup query string
> -  if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) {
> -    hm->query.ptr = qs + 1;
> -    hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
> -    hm->uri.len = (size_t) (qs - hm->uri.ptr);
> -  }
>
> -  mg_http_parse_headers(s, end, hm->headers,
> -                        sizeof(hm->headers) / sizeof(hm->headers[0]));
> -  if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> -    hm->body.len = (size_t) mg_to64(*cl);
> -    hm->message.len = (size_t) req_len + hm->body.len;
> -  }
> +struct packed_file {
> +  const char *data;
> +  size_t size;
> +  size_t pos;
> +};
>
> -  // mg_http_parse() is used to parse both HTTP requests and HTTP
> -  // responses. If HTTP response does not have Content-Length set, then
> -  // body is read until socket is closed, i.e. body.len is infinite (~0).
> -  //
> -  // For HTTP requests though, according to
> -  // http://tools.ietf.org/html/rfc7231#section-8.1.3,
> -  // only POST and PUT methods have defined body semantics.
> -  // Therefore, if Content-Length is not specified and methods are
> -  // not one of PUT or POST, set body length to 0.
> -  //
> -  // So, if it is HTTP request, and Content-Length is not set,
> -  // and method is not (PUT or POST) then reset body length to zero.
> -  is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
> -  if (hm->body.len == (size_t) ~0 && !is_response &&
> -      mg_vcasecmp(&hm->method, "PUT") != 0 &&
> -      mg_vcasecmp(&hm->method, "POST") != 0) {
> -    hm->body.len = 0;
> -    hm->message.len = (size_t) req_len;
> -  }
> +#if MG_ENABLE_PACKED_FS
> +#else
> +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
> +  *size = 0, *mtime = 0;
> +  (void) path;
> +  return NULL;
> +}
> +const char *mg_unlist(size_t no) {
> +  (void) no;
> +  return NULL;
> +}
> +#endif
>
> -  // The 204 (No content) responses also have 0 body length
> -  if (hm->body.len == (size_t) ~0 && is_response &&
> -      mg_vcasecmp(&hm->uri, "204") == 0) {
> -    hm->body.len = 0;
> -    hm->message.len = (size_t) req_len;
> -  }
> +struct mg_str mg_unpacked(const char *path) {
> +  size_t len = 0;
> +  const char *buf = mg_unpack(path, &len, NULL);
> +  return mg_str_n(buf, len);
> +}
>
> -  return req_len;
> +static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
> +  // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> +  return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> +         (n == 0 || path[n] == '/' || path[n - 1] == '/');
>   }
>
> -static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
> -                                  va_list *ap) {
> -  size_t len = c->send.len;
> -  mg_send(c, "        \r\n", 10);
> -  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> -  if (c->send.len >= len + 10) {
> -    mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
> -    c->send.buf[len + 8] = '\r';
> -    if (c->send.len == len + 10) c->is_resp = 0;  // Last chunk, reset marker
> +static int packed_stat(const char *path, size_t *size, time_t *mtime) {
> +  const char *p;
> +  size_t i, n = strlen(path);
> +  if (mg_unpack(path, size, mtime)) return MG_FS_READ;  // Regular file
> +  // Scan all files. If `path` is a dir prefix for any of them, it's a dir
> +  for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> +    if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
>     }
> -  mg_send(c, "\r\n", 2);
> +  return 0;
>   }
>
> -void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
> -  va_list ap;
> -  va_start(ap, fmt);
> -  mg_http_vprintf_chunk(c, fmt, &ap);
> -  va_end(ap);
> +static void packed_list(const char *dir, void (*fn)(const char *, void *),
> +                        void *userdata) {
> +  char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> +  const char *path, *begin, *end;
> +  size_t i, n = strlen(dir);
> +  tmp[0] = '\0';  // Previously listed entry
> +  for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> +    if (!is_dir_prefix(dir, n, path)) continue;
> +    begin = &path[n + 1];
> +    end = strchr(begin, '/');
> +    if (end == NULL) end = begin + strlen(begin);
> +    mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> +    buf[sizeof(buf) - 1] = '\0';
> +    // If this entry has been already listed, skip
> +    // NOTE: we're assuming that file list is sorted alphabetically
> +    if (strcmp(buf, tmp) == 0) continue;
> +    fn(buf, userdata);  // Not yet listed, call user function
> +    strcpy(tmp, buf);   // And save this entry as listed
> +  }
>   }
>
> -void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
> -  mg_printf(c, "%lx\r\n", (unsigned long) len);
> -  mg_send(c, buf, len);
> -  mg_send(c, "\r\n", 2);
> -  if (len == 0) c->is_resp = 0;
> +static void *packed_open(const char *path, int flags) {
> +  size_t size = 0;
> +  const char *data = mg_unpack(path, &size, NULL);
> +  struct packed_file *fp = NULL;
> +  if (data == NULL) return NULL;
> +  if (flags & MG_FS_WRITE) return NULL;
> +  if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) {
> +    fp->size = size;
> +    fp->data = data;
> +  }
> +  return (void *) fp;
>   }
>
> -// clang-format off
> -static const char *mg_http_status_code_str(int status_code) {
> -  switch (status_code) {
> -    case 100: return "Continue";
> -    case 201: return "Created";
> -    case 202: return "Accepted";
> -    case 204: return "No Content";
> -    case 206: return "Partial Content";
> -    case 301: return "Moved Permanently";
> -    case 302: return "Found";
> -    case 304: return "Not Modified";
> -    case 400: return "Bad Request";
> -    case 401: return "Unauthorized";
> -    case 403: return "Forbidden";
> -    case 404: return "Not Found";
> -    case 418: return "I'm a teapot";
> -    case 500: return "Internal Server Error";
> -    case 501: return "Not Implemented";
> -    default: return "OK";
> -  }
> +static void packed_close(void *fp) {
> +  if (fp != NULL) free(fp);
>   }
> -// clang-format on
>
> -void mg_http_reply(struct mg_connection *c, int code, const char *headers,
> -                   const char *fmt, ...) {
> -  va_list ap;
> -  size_t len;
> -  mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length:           \r\n\r\n", code,
> -            mg_http_status_code_str(code), headers == NULL ? "" : headers);
> -  len = c->send.len;
> -  va_start(ap, fmt);
> -  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> -  va_end(ap);
> -  if (c->send.len > 15) {
> -    mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
> -                (unsigned long) (c->send.len - len));
> -    c->is_resp = 0;
> -    c->send.buf[len - 4] = '\r';  // Change ending 0 to space
> -  }
> -  c->is_resp = 0;
> +static size_t packed_read(void *fd, void *buf, size_t len) {
> +  struct packed_file *fp = (struct packed_file *) fd;
> +  if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> +  memcpy(buf, &fp->data[fp->pos], len);
> +  fp->pos += len;
> +  return len;
>   }
>
> -static void http_cb(struct mg_connection *, int, void *, void *);
> -static void restore_http_cb(struct mg_connection *c) {
> -  mg_fs_close((struct mg_fd *) c->pfn_data);
> -  c->pfn_data = NULL;
> -  c->pfn = http_cb;
> -  c->is_resp = 0;
> +static size_t packed_write(void *fd, const void *buf, size_t len) {
> +  (void) fd, (void) buf, (void) len;
> +  return 0;
>   }
>
> -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
> -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
> -  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
> -  return buf;
> +static size_t packed_seek(void *fd, size_t offset) {
> +  struct packed_file *fp = (struct packed_file *) fd;
> +  fp->pos = offset;
> +  if (fp->pos > fp->size) fp->pos = fp->size;
> +  return fp->pos;
>   }
>
> -static void static_cb(struct mg_connection *c, int ev, void *ev_data,
> -                      void *fn_data) {
> -  if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> -    struct mg_fd *fd = (struct mg_fd *) fn_data;
> -    // Read to send IO buffer directly, avoid extra on-stack buffer
> -    size_t n, max = MG_IO_SIZE, space;
> -    size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> -                                     sizeof(size_t) * sizeof(size_t)];
> -    if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> -    if (c->send.len >= c->send.size) return;  // Rate limit
> -    if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> -    n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> -    c->send.len += n;
> -    *cl -= n;
> -    if (n == 0) restore_http_cb(c);
> -  } else if (ev == MG_EV_CLOSE) {
> -    restore_http_cb(c);
> -  }
> -  (void) ev_data;
> +static bool packed_rename(const char *from, const char *to) {
> +  (void) from, (void) to;
> +  return false;
>   }
>
> -// Known mime types. Keep it outside guess_content_type() function, since
> -// some environments don't like it defined there.
> -// clang-format off
> -static struct mg_str s_known_types[] = {
> -    MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> -    MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> -    MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> -    MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> -    MG_C_STR("gif"), MG_C_STR("image/gif"),
> -    MG_C_STR("png"), MG_C_STR("image/png"),
> -    MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> -    MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> -    MG_C_STR("woff"), MG_C_STR("font/woff"),
> -    MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> -    MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> -    MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> -    MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> -    MG_C_STR("csv"), MG_C_STR("text/csv"),
> -    MG_C_STR("doc"), MG_C_STR("application/msword"),
> -    MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> -    MG_C_STR("gz"), MG_C_STR("application/gzip"),
> -    MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> -    MG_C_STR("json"), MG_C_STR("application/json"),
> -    MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> -    MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> -    MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> -    MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> -    MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> -    MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> -    MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> -    MG_C_STR("wav"), MG_C_STR("audio/wav"),
> -    MG_C_STR("webp"), MG_C_STR("image/webp"),
> -    MG_C_STR("zip"), MG_C_STR("application/zip"),
> -    MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> -    {0, 0},
> -};
> -// clang-format on
> +static bool packed_remove(const char *path) {
> +  (void) path;
> +  return false;
> +}
>
> -static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
> -  struct mg_str k, v, s = mg_str(extra);
> -  size_t i = 0;
> +static bool packed_mkdir(const char *path) {
> +  (void) path;
> +  return false;
> +}
>
> -  // Shrink path to its extension only
> -  while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
> -  path.ptr += path.len - i;
> -  path.len = i;
> +struct mg_fs mg_fs_packed = {
> +    packed_stat,  packed_list, packed_open,   packed_close,  packed_read,
> +    packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
>
> -  // Process user-provided mime type overrides, if any
> -  while (mg_commalist(&s, &k, &v)) {
> -    if (mg_strcmp(path, k) == 0) return v;
> -  }
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/fs_posix.c"
> +#endif
>
> -  // Process built-in mime types
> -  for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
> -    if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1];
> -  }
>
> -  return mg_str("text/plain; charset=utf-8");
> -}
> +#if MG_ENABLE_POSIX_FS
>
> -static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
> -  size_t i, numparsed = 0;
> -  // MG_INFO(("%.*s", (int) s->len, s->ptr));
> -  for (i = 0; i + 6 < s->len; i++) {
> -    if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
> -      struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
> -      if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> -      *a = mg_to64(p);
> -      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> -      while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
> -      if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
> -      *b = mg_to64(p);
> -      if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> -      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> -      break;
> -    }
> -  }
> -  return (int) numparsed;
> -}
> +#ifndef MG_STAT_STRUCT
> +#define MG_STAT_STRUCT stat
> +#endif
>
> -void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
> -                        const char *path,
> -                        const struct mg_http_serve_opts *opts) {
> -  char etag[64], tmp[MG_PATH_MAX];
> -  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> -  struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path, MG_FS_READ);
> -  size_t size = 0;
> -  time_t mtime = 0;
> -  struct mg_str *inm = NULL;
> -  struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
> -  bool gzip = false;
> +#ifndef MG_STAT_FUNC
> +#define MG_STAT_FUNC stat
> +#endif
>
> -  // If file does not exist, we try to open file PATH.gz - and if such
> -  // pre-compressed .gz file exists, serve it with the Content-Encoding: gzip
> -  // Note - we ignore Accept-Encoding, cause we don't have a choice
> -  if (fd == NULL) {
> -    MG_DEBUG(("NULL [%s]", path));
> -    mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> -    if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
> -      gzip = true;
> -      path = tmp;
> -    } else if (opts->page404 != NULL) {
> -      // No precompressed file, serve 404
> -      fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> -      mime = guess_content_type(mg_str(path), opts->mime_types);
> -      path = opts->page404;
> +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> +#if !defined(S_ISDIR)
> +  MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
> +  return 0;
> +#else
> +#if MG_ARCH == MG_ARCH_WIN32
> +  struct _stati64 st;
> +  wchar_t tmp[MG_PATH_MAX];
> +  MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
> +  if (_wstati64(tmp, &st) != 0) return 0;
> +  // If path is a symlink, windows reports 0 in st.st_size.
> +  // Get a real file size by opening it and jumping to the end
> +  if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
> +    FILE *fp = _wfopen(tmp, L"rb");
> +    if (fp != NULL) {
> +      fseek(fp, 0, SEEK_END);
> +      if (ftell(fp) > 0) st.st_size = ftell(fp);  // Use _ftelli64 on win10+
> +      fclose(fp);
>       }
>     }
> +#else
> +  struct MG_STAT_STRUCT st;
> +  if (MG_STAT_FUNC(path, &st) != 0) return 0;
> +#endif
> +  if (size) *size = (size_t) st.st_size;
> +  if (mtime) *mtime = st.st_mtime;
> +  return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
> +#endif
> +}
>
> -  if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> -    mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> -    mg_fs_close(fd);
> -    // NOTE: mg_http_etag() call should go first!
> -  } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
> -             (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> -             mg_vcasecmp(inm, etag) == 0) {
> -    mg_fs_close(fd);
> -    mg_http_reply(c, 304, opts->extra_headers, "");
> -  } else {
> -    int n, status = 200;
> -    char range[100];
> -    int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
> +#if MG_ARCH == MG_ARCH_WIN32
> +struct dirent {
> +  char d_name[MAX_PATH];
> +};
>
> -    // Handle Range header
> -    struct mg_str *rh = mg_http_get_header(hm, "Range");
> -    range[0] = '\0';
> -    if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
> -      // If range is specified like "400-", set second limit to content len
> -      if (n == 1) r2 = cl - 1;
> -      if (r1 > r2 || r2 >= cl) {
> -        status = 416;
> -        cl = 0;
> -        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
> -                    (int64_t) size);
> -      } else {
> -        status = 206;
> -        cl = r2 - r1 + 1;
> -        mg_snprintf(range, sizeof(range),
> -                    "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
> -                    (int64_t) size);
> -        fs->sk(fd->fd, (size_t) r1);
> -      }
> -    }
> -    mg_printf(c,
> -              "HTTP/1.1 %d %s\r\n"
> -              "Content-Type: %.*s\r\n"
> -              "Etag: %s\r\n"
> -              "Content-Length: %llu\r\n"
> -              "%s%s%s\r\n",
> -              status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
> -              etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
> -              opts->extra_headers ? opts->extra_headers : "");
> -    if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
> -      c->is_draining = 1;
> -      c->is_resp = 0;
> -      mg_fs_close(fd);
> -    } else {
> -      // Track to-be-sent content length at the end of c->data, aligned
> -      size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> -                                        sizeof(size_t) * sizeof(size_t)];
> -      c->pfn = static_cb;
> -      c->pfn_data = fd;
> -      *clp = (size_t) cl;
> -    }
> +typedef struct win32_dir {
> +  HANDLE handle;
> +  WIN32_FIND_DATAW info;
> +  struct dirent result;
> +} DIR;
> +
> +#if 0
> +int gettimeofday(struct timeval *tv, void *tz) {
> +  FILETIME ft;
> +  unsigned __int64 tmpres = 0;
> +
> +  if (tv != NULL) {
> +    GetSystemTimeAsFileTime(&ft);
> +    tmpres |= ft.dwHighDateTime;
> +    tmpres <<= 32;
> +    tmpres |= ft.dwLowDateTime;
> +    tmpres /= 10;  // convert into microseconds
> +    tmpres -= (int64_t) 11644473600000000;
> +    tv->tv_sec = (long) (tmpres / 1000000UL);
> +    tv->tv_usec = (long) (tmpres % 1000000UL);
>     }
> +  (void) tz;
> +  return 0;
>   }
> +#endif
>
> -struct printdirentrydata {
> -  struct mg_connection *c;
> -  struct mg_http_message *hm;
> -  const struct mg_http_serve_opts *opts;
> -  const char *dir;
> -};
> +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
> +  int ret;
> +  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> +  strncpy(buf, path, sizeof(buf));
> +  buf[sizeof(buf) - 1] = '\0';
> +  // Trim trailing slashes. Leave backslash for paths like "X:\"
> +  p = buf + strlen(buf) - 1;
> +  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
> +  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> +  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
> +  // Convert back to Unicode. If doubly-converted string does not match the
> +  // original, something is fishy, reject.
> +  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
> +                      NULL, NULL);
> +  if (strcmp(buf, buf2) != 0) {
> +    wbuf[0] = L'\0';
> +    ret = 0;
> +  }
> +  return ret;
> +}
>
> -static void printdirentry(const char *name, void *userdata) {
> -  struct printdirentrydata *d = (struct printdirentrydata *) userdata;
> -  struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
> -  size_t size = 0;
> -  time_t t = 0;
> -  char path[MG_PATH_MAX], sz[40], mod[40];
> -  int flags, n = 0;
> +DIR *opendir(const char *name) {
> +  DIR *d = NULL;
> +  wchar_t wpath[MAX_PATH];
> +  DWORD attrs;
>
> -  // MG_DEBUG(("[%s] [%s]", d->dir, name));
> -  if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> -      sizeof(path)) {
> -    MG_ERROR(("%s truncated", name));
> -  } else if ((flags = fs->st(path, &size, &t)) == 0) {
> -    MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> +  if (name == NULL) {
> +    SetLastError(ERROR_BAD_ARGUMENTS);
> +  } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> +    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
>     } else {
> -    const char *slash = flags & MG_FS_DIR ? "/" : "";
> -    if (flags & MG_FS_DIR) {
> -      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> +    to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> +    attrs = GetFileAttributesW(wpath);
> +    if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> +      (void) wcscat(wpath, L"\\*");
> +      d->handle = FindFirstFileW(wpath, &d->info);
> +      d->result.d_name[0] = '\0';
>       } else {
> -      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> +      free(d);
> +      d = NULL;
>       }
> -#if defined(MG_HTTP_DIRLIST_TIME)
> -    char time_str[30];
> -    struct tm *time_info = localtime(&t);
> -    strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> -    mg_snprintf(mod, sizeof(mod), "%s", time_str);
> -#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
> -    char time_str[30];
> -    struct tm *time_info = gmtime(&t);
> -    strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> -    mg_snprintf(mod, sizeof(mod), "%s", time_str);
> -#else
> -    mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
> -#endif
> -    n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> -    mg_printf(d->c,
> -              "  <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> -              "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> -              n, path, slash, name, slash, (unsigned long) t, mod,
> -              flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
>     }
> +  return d;
>   }
>
> -static void listdir(struct mg_connection *c, struct mg_http_message *hm,
> -                    const struct mg_http_serve_opts *opts, char *dir) {
> -  const char *sort_js_code =
> -      "<script>function srt(tb, sc, so, d) {"
> -      "var tr = Array.prototype.slice.call(tb.rows, 0),"
> -      "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
> -      "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> -      "t1 = a.cells[2].getAttribute('name'), "
> -      "t2 = b.cells[2].getAttribute('name'); "
> -      "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> -      "n1 ? parseInt(n2) - parseInt(n1) : "
> -      "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
> -  const char *sort_js_code2 =
> -      "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> -      "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> -      "};"
> -      "window.onload = function() {"
> -      "var tb = document.getElementById('tb');"
> -      "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
> -      "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> -      "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
> -      "sc = c; ev.preventDefault();}};"
> -      "srt(tb, sc, so, true);"
> -      "}"
> -      "</script>";
> -  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> -  struct printdirentrydata d = {c, hm, opts, dir};
> -  char tmp[10], buf[MG_PATH_MAX];
> -  size_t off, n;
> -  int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0);
> -  struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
> -
> -  mg_printf(c,
> -            "HTTP/1.1 200 OK\r\n"
> -            "Content-Type: text/html; charset=utf-8\r\n"
> -            "%s"
> -            "Content-Length:         \r\n\r\n",
> -            opts->extra_headers == NULL ? "" : opts->extra_headers);
> -  off = c->send.len;  // Start of body
> -  mg_printf(c,
> -            "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> -            "<style>th,td {text-align: left; padding-right: 1em; "
> -            "font-family: monospace; }</style></head>"
> -            "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> -            "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> -            "<a href=\"#\" rel=\"1\">Modified</a></th>"
> -            "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> -            "<tr><td colspan=\"3\"><hr></td></tr>"
> -            "</thead>"
> -            "<tbody id=\"tb\">\n",
> -            (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len,
> -            uri.ptr);
> -  mg_printf(c, "%s",
> -            "  <tr><td><a href=\"..\">..</a></td>"
> -            "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> -
> -  fs->ls(dir, printdirentry, &d);
> -  mg_printf(c,
> -            "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> -            "</table><address>Mongoose v.%s</address></body></html>\n",
> -            MG_VERSION);
> -  n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
> -  if (n > sizeof(tmp)) n = 0;
> -  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
> -  c->is_resp = 0;                          // Mark response end
> +int closedir(DIR *d) {
> +  int result = 0;
> +  if (d != NULL) {
> +    if (d->handle != INVALID_HANDLE_VALUE)
> +      result = FindClose(d->handle) ? 0 : -1;
> +    free(d);
> +  } else {
> +    result = -1;
> +    SetLastError(ERROR_BAD_ARGUMENTS);
> +  }
> +  return result;
>   }
>
> -// Resolve requested file into `path` and return its fs->st() result
> -static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
> -                        struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> -                        char *path, size_t path_size) {
> -  int flags, tmp;
> -  // Append URI to the root_dir, and sanitize it
> -  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr);
> -  if (n > path_size) n = path_size;
> -  path[path_size - 1] = '\0';
> -  if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
> -  mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n,
> -                path_size - n, 0);
> -  path[path_size - 1] = '\0';  // Double-check
> -  mg_remove_double_dots(path);
> -  n = strlen(path);
> -  while (n > 1 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
> -  flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL);
> -  MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path,
> -              flags));
> -  if (flags == 0) {
> -    // Do nothing - let's caller decide
> -  } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> -             hm->uri.ptr[hm->uri.len - 1] != '/') {
> -    mg_printf(c,
> -              "HTTP/1.1 301 Moved\r\n"
> -              "Location: %.*s/\r\n"
> -              "Content-Length: 0\r\n"
> +struct dirent *readdir(DIR *d) {
> +  struct dirent *result = NULL;
> +  if (d != NULL) {
> +    memset(&d->result, 0, sizeof(d->result));
> +    if (d->handle != INVALID_HANDLE_VALUE) {
> +      result = &d->result;
> +      WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
> +                          sizeof(result->d_name), NULL, NULL);
> +      if (!FindNextFileW(d->handle, &d->info)) {
> +        FindClose(d->handle);
> +        d->handle = INVALID_HANDLE_VALUE;
> +      }
> +    } else {
> +      SetLastError(ERROR_FILE_NOT_FOUND);
> +    }
> +  } else {
> +    SetLastError(ERROR_BAD_ARGUMENTS);
> +  }
> +  return result;
> +}
> +#endif
> +
> +static void p_list(const char *dir, void (*fn)(const char *, void *),
> +                   void *userdata) {
> +#if MG_ENABLE_DIRLIST
> +  struct dirent *dp;
> +  DIR *dirp;
> +  if ((dirp = (opendir(dir))) == NULL) return;
> +  while ((dp = readdir(dirp)) != NULL) {
> +    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
> +    fn(dp->d_name, userdata);
> +  }
> +  closedir(dirp);
> +#else
> +  (void) dir, (void) fn, (void) userdata;
> +#endif
> +}
> +
> +static void *p_open(const char *path, int flags) {
> +#if MG_ARCH == MG_ARCH_WIN32
> +  const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> +  wchar_t b1[MG_PATH_MAX], b2[10];
> +  MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
> +  MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
> +  return (void *) _wfopen(b1, b2);
> +#else
> +  const char *mode = flags == MG_FS_READ ? "rbe" : "a+be";  // e for CLOEXEC
> +  return (void *) fopen(path, mode);
> +#endif
> +}
> +
> +static void p_close(void *fp) {
> +  fclose((FILE *) fp);
> +}
> +
> +static size_t p_read(void *fp, void *buf, size_t len) {
> +  return fread(buf, 1, len, (FILE *) fp);
> +}
> +
> +static size_t p_write(void *fp, const void *buf, size_t len) {
> +  return fwrite(buf, 1, len, (FILE *) fp);
> +}
> +
> +static size_t p_seek(void *fp, size_t offset) {
> +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) ||  \
> +    (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> +    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> +  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> +#else
> +  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> +#endif
> +  return (size_t) ftell((FILE *) fp);
> +}
> +
> +static bool p_rename(const char *from, const char *to) {
> +  return rename(from, to) == 0;
> +}
> +
> +static bool p_remove(const char *path) {
> +  return remove(path) == 0;
> +}
> +
> +static bool p_mkdir(const char *path) {
> +  return mkdir(path, 0775) == 0;
> +}
> +
> +#else
> +
> +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> +  (void) path, (void) size, (void) mtime;
> +  return 0;
> +}
> +static void p_list(const char *path, void (*fn)(const char *, void *),
> +                   void *userdata) {
> +  (void) path, (void) fn, (void) userdata;
> +}
> +static void *p_open(const char *path, int flags) {
> +  (void) path, (void) flags;
> +  return NULL;
> +}
> +static void p_close(void *fp) {
> +  (void) fp;
> +}
> +static size_t p_read(void *fd, void *buf, size_t len) {
> +  (void) fd, (void) buf, (void) len;
> +  return 0;
> +}
> +static size_t p_write(void *fd, const void *buf, size_t len) {
> +  (void) fd, (void) buf, (void) len;
> +  return 0;
> +}
> +static size_t p_seek(void *fd, size_t offset) {
> +  (void) fd, (void) offset;
> +  return (size_t) ~0;
> +}
> +static bool p_rename(const char *from, const char *to) {
> +  (void) from, (void) to;
> +  return false;
> +}
> +static bool p_remove(const char *path) {
> +  (void) path;
> +  return false;
> +}
> +static bool p_mkdir(const char *path) {
> +  (void) path;
> +  return false;
> +}
> +#endif
> +
> +struct mg_fs mg_fs_posix = {p_stat,  p_list, p_open,   p_close,  p_read,
> +                            p_write, p_seek, p_rename, p_remove, p_mkdir};
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/http.c"
> +#endif
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +static int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
> +  int diff = 0;
> +  if (len > 0) do {
> +      int c = *s1++, d = *s2++;
> +      if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> +      if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
> +      diff = c - d;
> +    } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> +  return diff;
> +}
> +
> +bool mg_to_size_t(struct mg_str str, size_t *val);
> +bool mg_to_size_t(struct mg_str str, size_t *val) {
> +  size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0;
> +  while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
> +  if (i < str.len && str.buf[i] == '-') return false;
> +  while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> +    size_t digit = (size_t) (str.buf[i] - '0');
> +    if (result > max2) return false;  // Overflow
> +    result *= 10;
> +    if (result > max - digit) return false;  // Overflow
> +    result += digit;
> +    i++, ndigits++;
> +  }
> +  while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
> +  if (ndigits == 0) return false;  // #2322: Content-Length = 1 * DIGIT
> +  if (i != str.len) return false;  // Ditto
> +  *val = (size_t) result;
> +  return true;
> +}
> +
> +// Chunk deletion marker is the MSB in the "processed" counter
> +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> +
> +// Multipart POST example:
> +// --xyz
> +// Content-Disposition: form-data; name="val"
> +//
> +// abcdef
> +// --xyz
> +// Content-Disposition: form-data; name="foo"; filename="a.txt"
> +// Content-Type: text/plain
> +//
> +// hello world
> +//
> +// --xyz--
> +size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> +                              struct mg_http_part *part) {
> +  struct mg_str cd = mg_str_n("Content-Disposition", 19);
> +  const char *s = body.buf;
> +  size_t b = ofs, h1, h2, b1, b2, max = body.len;
> +
> +  // Init part params
> +  if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
> +
> +  // Skip boundary
> +  while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> +  if (b <= ofs || b + 2 >= max) return 0;
> +  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
> +
> +  // Skip headers
> +  h1 = h2 = b + 2;
> +  for (;;) {
> +    while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> +    if (h2 == h1) break;
> +    if (h2 + 2 >= max) return 0;
> +    // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> +    if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
> +        mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
> +      struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
> +      part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> +      part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
> +    }
> +    h1 = h2 = h2 + 2;
> +  }
> +  b1 = b2 = h2 + 2;
> +  while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
> +                                           memcmp(&s[b2 + 2], s, b - ofs) == 0))
> +    b2++;
> +
> +  if (b2 + 2 >= max) return 0;
> +  if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> +  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> +  return b2 + 2;
> +}
> +
> +void mg_http_bauth(struct mg_connection *c, const char *user,
> +                   const char *pass) {
> +  struct mg_str u = mg_str(user), p = mg_str(pass);
> +  size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> +  if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> +  if (c->send.size >= need) {
> +    size_t i, n = 0;
> +    char *buf = (char *) &c->send.buf[c->send.len];
> +    memcpy(buf, "Authorization: Basic ", 21);  // DON'T use mg_send!
> +    for (i = 0; i < u.len; i++) {
> +      n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
> +    }
> +    if (p.len > 0) {
> +      n = mg_base64_update(':', buf + 21, n);
> +      for (i = 0; i < p.len; i++) {
> +        n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
> +      }
> +    }
> +    n = mg_base64_final(buf + 21, n);
> +    c->send.len += 21 + (size_t) n + 2;
> +    memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> +  } else {
> +    MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
> +  }
> +}
> +
> +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> +  struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
> +  while (mg_span(buf, &entry, &buf, '&')) {
> +    if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
> +        mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
> +      result = v;
> +      break;
> +    }
> +  }
> +  return result;
> +}
> +
> +int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
> +                    size_t dst_len) {
> +  int len;
> +  if (dst != NULL && dst_len > 0) {
> +    dst[0] = '\0';  // If destination buffer is valid, always nul-terminate it
> +  }
> +  if (dst == NULL || dst_len == 0) {
> +    len = -2;  // Bad destination
> +  } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
> +    len = -1;  // Bad source
> +  } else {
> +    struct mg_str v = mg_http_var(*buf, mg_str(name));
> +    if (v.buf == NULL) {
> +      len = -4;  // Name does not exist
> +    } else {
> +      len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
> +      if (len < 0) len = -3;  // Failed to decode
> +    }
> +  }
> +  return len;
> +}
> +
> +static bool isx(int c) {
> +  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> +         (c >= 'A' && c <= 'F');
> +}
> +
> +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
> +                  int is_form_url_encoded) {
> +  size_t i, j;
> +  for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> +    if (src[i] == '%') {
> +      // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
> +      if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> +        mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t));
> +        i += 2;
> +      } else {
> +        return -1;
> +      }
> +    } else if (is_form_url_encoded && src[i] == '+') {
> +      dst[j] = ' ';
> +    } else {
> +      dst[j] = src[i];
> +    }
> +  }
> +  if (j < dst_len) dst[j] = '\0';  // Null-terminate the destination
> +  return i >= src_len && j < dst_len ? (int) j : -1;
> +}
> +
> +static bool isok(uint8_t c) {
> +  return c == '\n' || c == '\r' || c >= ' ';
> +}
> +
> +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
> +  size_t i;
> +  for (i = 0; i < buf_len; i++) {
> +    if (!isok(buf[i])) return -1;
> +    if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> +        (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
> +      return (int) i + 1;
> +  }
> +  return 0;
> +}
> +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
> +  size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
> +  for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> +    struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> +    if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
> +  }
> +  return NULL;
> +}
> +
> +// Is it a valid utf-8 continuation byte
> +static bool vcb(uint8_t c) {
> +  return (c & 0xc0) == 0x80;
> +}
> +
> +// Get character length (valid utf-8). Used to parse method, URI, headers
> +static size_t clen(const char *s, const char *end) {
> +  const unsigned char *u = (unsigned char *) s, c = *u;
> +  long n = (long) (end - s);
> +  if (c > ' ' && c < '~') return 1;  // Usual ascii printed char
> +  if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2;  // 2-byte UTF8
> +  if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3;
> +  if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3]))
> +    return 4;
> +  return 0;
> +}
> +
> +// Skip until the newline. Return advanced `s`, or NULL on error
> +static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
> +  v->buf = (char *) s;
> +  while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;  // To newline
> +  if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL;    // Stray \r
> +  if (s < end && s[0] == '\r') s++;                               // Skip \r
> +  if (s >= end || *s++ != '\n') return NULL;                      // Skip \n
> +  return s;
> +}
> +
> +static bool mg_http_parse_headers(const char *s, const char *end,
> +                                  struct mg_http_header *h, size_t max_hdrs) {
> +  size_t i, n;
> +  for (i = 0; i < max_hdrs; i++) {
> +    struct mg_str k = {NULL, 0}, v = {NULL, 0};
> +    if (s >= end) return false;
> +    if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
> +    k.buf = (char *) s;
> +    while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n;
> +    if (k.len == 0) return false;                     // Empty name
> +    if (s >= end || clen(s, end) == 0) return false;  // Invalid UTF-8
> +    if (*s++ != ':') return false;  // Invalid, not followed by :
> +    // if (clen(s, end) == 0) return false;        // Invalid UTF-8
> +    while (s < end && s[0] == ' ') s++;  // Skip spaces
> +    if ((s = skiptorn(s, end, &v)) == NULL) return false;
> +    while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--;  // Trim spaces
> +    // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf));
> +    h[i].name = k, h[i].value = v;  // Success. Assign values
> +  }
> +  return true;
> +}
> +
> +int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
> +  int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
> +  const char *end = s == NULL ? NULL : s + req_len, *qs;  // Cannot add to NULL
> +  const struct mg_str *cl;
> +  size_t n;
> +
> +  memset(hm, 0, sizeof(*hm));
> +  if (req_len <= 0) return req_len;
> +
> +  hm->message.buf = hm->head.buf = (char *) s;
> +  hm->body.buf = (char *) end;
> +  hm->head.len = (size_t) req_len;
> +  hm->message.len = hm->body.len = (size_t) -1;  // Set body length to infinite
> +
> +  // Parse request line
> +  hm->method.buf = (char *) s;
> +  while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n;
> +  while (s < end && s[0] == ' ') s++;  // Skip spaces
> +  hm->uri.buf = (char *) s;
> +  while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n;
> +  while (s < end && s[0] == ' ') s++;  // Skip spaces
> +  if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
> +
> +  // If URI contains '?' character, setup query string
> +  if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) {
> +    hm->query.buf = (char *) qs + 1;
> +    hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
> +    hm->uri.len = (size_t) (qs - hm->uri.buf);
> +  }
> +
> +  // Sanity check. Allow protocol/reason to be empty
> +  // Do this check after hm->method.len and hm->uri.len are finalised
> +  if (hm->method.len == 0 || hm->uri.len == 0) return -1;
> +
> +  if (!mg_http_parse_headers(s, end, hm->headers,
> +                             sizeof(hm->headers) / sizeof(hm->headers[0])))
> +    return -1;  // error when parsing
> +  if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> +    if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
> +    hm->message.len = (size_t) req_len + hm->body.len;
> +  }
> +
> +  // mg_http_parse() is used to parse both HTTP requests and HTTP
> +  // responses. If HTTP response does not have Content-Length set, then
> +  // body is read until socket is closed, i.e. body.len is infinite (~0).
> +  //
> +  // For HTTP requests though, according to
> +  // http://tools.ietf.org/html/rfc7231#section-8.1.3,
> +  // only POST and PUT methods have defined body semantics.
> +  // Therefore, if Content-Length is not specified and methods are
> +  // not one of PUT or POST, set body length to 0.
> +  //
> +  // So, if it is HTTP request, and Content-Length is not set,
> +  // and method is not (PUT or POST) then reset body length to zero.
> +  is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
> +  if (hm->body.len == (size_t) ~0 && !is_response &&
> +      mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
> +      mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
> +    hm->body.len = 0;
> +    hm->message.len = (size_t) req_len;
> +  }
> +
> +  // The 204 (No content) responses also have 0 body length
> +  if (hm->body.len == (size_t) ~0 && is_response &&
> +      mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
> +    hm->body.len = 0;
> +    hm->message.len = (size_t) req_len;
> +  }
> +  if (hm->message.len < (size_t) req_len) return -1;  // Overflow protection
> +
> +  return req_len;
> +}
> +
> +static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
> +                                  va_list *ap) {
> +  size_t len = c->send.len;
> +  mg_send(c, "        \r\n", 10);
> +  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> +  if (c->send.len >= len + 10) {
> +    mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
> +    c->send.buf[len + 8] = '\r';
> +    if (c->send.len == len + 10) c->is_resp = 0;  // Last chunk, reset marker
> +  }
> +  mg_send(c, "\r\n", 2);
> +}
> +
> +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
> +  va_list ap;
> +  va_start(ap, fmt);
> +  mg_http_vprintf_chunk(c, fmt, &ap);
> +  va_end(ap);
> +}
> +
> +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
> +  mg_printf(c, "%lx\r\n", (unsigned long) len);
> +  mg_send(c, buf, len);
> +  mg_send(c, "\r\n", 2);
> +  if (len == 0) c->is_resp = 0;
> +}
> +
> +// clang-format off
> +static const char *mg_http_status_code_str(int status_code) {
> +  switch (status_code) {
> +    case 100: return "Continue";
> +    case 101: return "Switching Protocols";
> +    case 102: return "Processing";
> +    case 200: return "OK";
> +    case 201: return "Created";
> +    case 202: return "Accepted";
> +    case 203: return "Non-authoritative Information";
> +    case 204: return "No Content";
> +    case 205: return "Reset Content";
> +    case 206: return "Partial Content";
> +    case 207: return "Multi-Status";
> +    case 208: return "Already Reported";
> +    case 226: return "IM Used";
> +    case 300: return "Multiple Choices";
> +    case 301: return "Moved Permanently";
> +    case 302: return "Found";
> +    case 303: return "See Other";
> +    case 304: return "Not Modified";
> +    case 305: return "Use Proxy";
> +    case 307: return "Temporary Redirect";
> +    case 308: return "Permanent Redirect";
> +    case 400: return "Bad Request";
> +    case 401: return "Unauthorized";
> +    case 402: return "Payment Required";
> +    case 403: return "Forbidden";
> +    case 404: return "Not Found";
> +    case 405: return "Method Not Allowed";
> +    case 406: return "Not Acceptable";
> +    case 407: return "Proxy Authentication Required";
> +    case 408: return "Request Timeout";
> +    case 409: return "Conflict";
> +    case 410: return "Gone";
> +    case 411: return "Length Required";
> +    case 412: return "Precondition Failed";
> +    case 413: return "Payload Too Large";
> +    case 414: return "Request-URI Too Long";
> +    case 415: return "Unsupported Media Type";
> +    case 416: return "Requested Range Not Satisfiable";
> +    case 417: return "Expectation Failed";
> +    case 418: return "I'm a teapot";
> +    case 421: return "Misdirected Request";
> +    case 422: return "Unprocessable Entity";
> +    case 423: return "Locked";
> +    case 424: return "Failed Dependency";
> +    case 426: return "Upgrade Required";
> +    case 428: return "Precondition Required";
> +    case 429: return "Too Many Requests";
> +    case 431: return "Request Header Fields Too Large";
> +    case 444: return "Connection Closed Without Response";
> +    case 451: return "Unavailable For Legal Reasons";
> +    case 499: return "Client Closed Request";
> +    case 500: return "Internal Server Error";
> +    case 501: return "Not Implemented";
> +    case 502: return "Bad Gateway";
> +    case 503: return "Service Unavailable";
> +    case 504: return "Gateway Timeout";
> +    case 505: return "HTTP Version Not Supported";
> +    case 506: return "Variant Also Negotiates";
> +    case 507: return "Insufficient Storage";
> +    case 508: return "Loop Detected";
> +    case 510: return "Not Extended";
> +    case 511: return "Network Authentication Required";
> +    case 599: return "Network Connect Timeout Error";
> +    default: return "";
> +  }
> +}
> +// clang-format on
> +
> +void mg_http_reply(struct mg_connection *c, int code, const char *headers,
> +                   const char *fmt, ...) {
> +  va_list ap;
> +  size_t len;
> +  mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length:            \r\n\r\n", code,
> +            mg_http_status_code_str(code), headers == NULL ? "" : headers);
> +  len = c->send.len;
> +  va_start(ap, fmt);
> +  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> +  va_end(ap);
> +  if (c->send.len > 16) {
> +    size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu",
> +                           (unsigned long) (c->send.len - len));
> +    c->send.buf[len - 15 + n] = ' ';  // Change ending 0 to space
> +  }
> +  c->is_resp = 0;
> +}
> +
> +static void http_cb(struct mg_connection *, int, void *);
> +static void restore_http_cb(struct mg_connection *c) {
> +  mg_fs_close((struct mg_fd *) c->pfn_data);
> +  c->pfn_data = NULL;
> +  c->pfn = http_cb;
> +  c->is_resp = 0;
> +}
> +
> +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
> +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
> +  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
> +  return buf;
> +}
> +
> +static void static_cb(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> +    struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
> +    // Read to send IO buffer directly, avoid extra on-stack buffer
> +    size_t n, max = MG_IO_SIZE, space;
> +    size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> +                                     sizeof(size_t) * sizeof(size_t)];
> +    if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> +    if (c->send.len >= c->send.size) return;  // Rate limit
> +    if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> +    n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> +    c->send.len += n;
> +    *cl -= n;
> +    if (n == 0) restore_http_cb(c);
> +  } else if (ev == MG_EV_CLOSE) {
> +    restore_http_cb(c);
> +  }
> +  (void) ev_data;
> +}
> +
> +// Known mime types. Keep it outside guess_content_type() function, since
> +// some environments don't like it defined there.
> +// clang-format off
> +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
> +static struct mg_str s_known_types[] = {
> +    MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> +    MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> +    MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> +    MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> +    MG_C_STR("gif"), MG_C_STR("image/gif"),
> +    MG_C_STR("png"), MG_C_STR("image/png"),
> +    MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> +    MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> +    MG_C_STR("woff"), MG_C_STR("font/woff"),
> +    MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> +    MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> +    MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> +    MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> +    MG_C_STR("csv"), MG_C_STR("text/csv"),
> +    MG_C_STR("doc"), MG_C_STR("application/msword"),
> +    MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> +    MG_C_STR("gz"), MG_C_STR("application/gzip"),
> +    MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> +    MG_C_STR("json"), MG_C_STR("application/json"),
> +    MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> +    MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> +    MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> +    MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> +    MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> +    MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> +    MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> +    MG_C_STR("wav"), MG_C_STR("audio/wav"),
> +    MG_C_STR("webp"), MG_C_STR("image/webp"),
> +    MG_C_STR("zip"), MG_C_STR("application/zip"),
> +    MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> +    {0, 0},
> +};
> +// clang-format on
> +
> +static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
> +  struct mg_str entry, k, v, s = mg_str(extra);
> +  size_t i = 0;
> +
> +  // Shrink path to its extension only
> +  while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
> +  path.buf += path.len - i;
> +  path.len = i;
> +
> +  // Process user-provided mime type overrides, if any
> +  while (mg_span(s, &entry, &s, ',')) {
> +    if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0) return v;
> +  }
> +
> +  // Process built-in mime types
> +  for (i = 0; s_known_types[i].buf != NULL; i += 2) {
> +    if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1];
> +  }
> +
> +  return mg_str("text/plain; charset=utf-8");
> +}
> +
> +static int getrange(struct mg_str *s, size_t *a, size_t *b) {
> +  size_t i, numparsed = 0;
> +  for (i = 0; i + 6 < s->len; i++) {
> +    struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
> +    if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
> +    if (mg_span(v, &k, &v, '-')) {
> +      if (mg_to_size_t(k, a)) numparsed++;
> +      if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
> +    } else {
> +      if (mg_to_size_t(v, a)) numparsed++;
> +    }
> +    break;
> +  }
> +  return (int) numparsed;
> +}
> +
> +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
> +                        const char *path,
> +                        const struct mg_http_serve_opts *opts) {
> +  char etag[64], tmp[MG_PATH_MAX];
> +  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> +  struct mg_fd *fd = NULL;
> +  size_t size = 0;
> +  time_t mtime = 0;
> +  struct mg_str *inm = NULL;
> +  struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
> +  bool gzip = false;
> +
> +  if (path != NULL) {
> +    // If a browser sends us "Accept-Encoding: gzip", try to open .gz first
> +    struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
> +    if (ae != NULL) {
> +      char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
> +      if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
> +        mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> +        fd = mg_fs_open(fs, tmp, MG_FS_READ);
> +        if (fd != NULL) gzip = true, path = tmp;
> +      }
> +      free(ae_);
> +    }
> +    // No luck opening .gz? Open what we've told to open
> +    if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
> +  }
> +
> +  // Failed to open, and page404 is configured? Open it, then
> +  if (fd == NULL && opts->page404 != NULL) {
> +    fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> +    path = opts->page404;
> +    mime = guess_content_type(mg_str(path), opts->mime_types);
> +  }
> +
> +  if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> +    mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> +    mg_fs_close(fd);
> +    // NOTE: mg_http_etag() call should go first!
> +  } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
> +             (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> +             mg_strcasecmp(*inm, mg_str(etag)) == 0) {
> +    mg_fs_close(fd);
> +    mg_http_reply(c, 304, opts->extra_headers, "");
> +  } else {
> +    int n, status = 200;
> +    char range[100];
> +    size_t r1 = 0, r2 = 0, cl = size;
> +
> +    // Handle Range header
> +    struct mg_str *rh = mg_http_get_header(hm, "Range");
> +    range[0] = '\0';
> +    if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
> +      // If range is specified like "400-", set second limit to content len
> +      if (n == 1) r2 = cl - 1;
> +      if (r1 > r2 || r2 >= cl) {
> +        status = 416;
> +        cl = 0;
> +        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
> +                    (int64_t) size);
> +      } else {
> +        status = 206;
> +        cl = r2 - r1 + 1;
> +        mg_snprintf(range, sizeof(range),
> +                    "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
> +                    (uint64_t) (r1 + cl - 1), (uint64_t) size);
> +        fs->sk(fd->fd, r1);
> +      }
> +    }
> +    mg_printf(c,
> +              "HTTP/1.1 %d %s\r\n"
> +              "Content-Type: %.*s\r\n"
> +              "Etag: %s\r\n"
> +              "Content-Length: %llu\r\n"
> +              "%s%s%s\r\n",
> +              status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
> +              etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
> +              range, opts->extra_headers ? opts->extra_headers : "");
> +    if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
> +      c->is_draining = 1;
> +      c->is_resp = 0;
> +      mg_fs_close(fd);
> +    } else {
> +      // Track to-be-sent content length at the end of c->data, aligned
> +      size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> +                                        sizeof(size_t) * sizeof(size_t)];
> +      c->pfn = static_cb;
> +      c->pfn_data = fd;
> +      *clp = cl;
> +    }
> +  }
> +}
> +
> +struct printdirentrydata {
> +  struct mg_connection *c;
> +  struct mg_http_message *hm;
> +  const struct mg_http_serve_opts *opts;
> +  const char *dir;
> +};
> +
> +#if MG_ENABLE_DIRLIST
> +static void printdirentry(const char *name, void *userdata) {
> +  struct printdirentrydata *d = (struct printdirentrydata *) userdata;
> +  struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
> +  size_t size = 0;
> +  time_t t = 0;
> +  char path[MG_PATH_MAX], sz[40], mod[40];
> +  int flags, n = 0;
> +
> +  // MG_DEBUG(("[%s] [%s]", d->dir, name));
> +  if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> +      sizeof(path)) {
> +    MG_ERROR(("%s truncated", name));
> +  } else if ((flags = fs->st(path, &size, &t)) == 0) {
> +    MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> +  } else {
> +    const char *slash = flags & MG_FS_DIR ? "/" : "";
> +    if (flags & MG_FS_DIR) {
> +      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> +    } else {
> +      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> +    }
> +#if defined(MG_HTTP_DIRLIST_TIME_FMT)
> +    {
> +      char time_str[40];
> +      struct tm *time_info = localtime(&t);
> +      strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> +      mg_snprintf(mod, sizeof(mod), "%s", time_str);
> +    }
> +#else
> +    mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
> +#endif
> +    n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> +    mg_printf(d->c,
> +              "  <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> +              "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> +              n, path, slash, name, slash, (unsigned long) t, mod,
> +              flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
> +  }
> +}
> +
> +static void listdir(struct mg_connection *c, struct mg_http_message *hm,
> +                    const struct mg_http_serve_opts *opts, char *dir) {
> +  const char *sort_js_code =
> +      "<script>function srt(tb, sc, so, d) {"
> +      "var tr = Array.prototype.slice.call(tb.rows, 0),"
> +      "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
> +      "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> +      "t1 = a.cells[2].getAttribute('name'), "
> +      "t2 = b.cells[2].getAttribute('name'); "
> +      "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> +      "n1 ? parseInt(n2) - parseInt(n1) : "
> +      "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
> +  const char *sort_js_code2 =
> +      "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> +      "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> +      "};"
> +      "window.onload = function() {"
> +      "var tb = document.getElementById('tb');"
> +      "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
> +      "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> +      "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
> +      "sc = c; ev.preventDefault();}};"
> +      "srt(tb, sc, so, true);"
> +      "}"
> +      "</script>";
> +  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> +  struct printdirentrydata d = {c, hm, opts, dir};
> +  char tmp[10], buf[MG_PATH_MAX];
> +  size_t off, n;
> +  int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0);
> +  struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
> +
> +  mg_printf(c,
> +            "HTTP/1.1 200 OK\r\n"
> +            "Content-Type: text/html; charset=utf-8\r\n"
> +            "%s"
> +            "Content-Length:         \r\n\r\n",
> +            opts->extra_headers == NULL ? "" : opts->extra_headers);
> +  off = c->send.len;  // Start of body
> +  mg_printf(c,
> +            "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> +            "<style>th,td {text-align: left; padding-right: 1em; "
> +            "font-family: monospace; }</style></head>"
> +            "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> +            "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> +            "<a href=\"#\" rel=\"1\">Modified</a></th>"
> +            "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> +            "<tr><td colspan=\"3\"><hr></td></tr>"
> +            "</thead>"
> +            "<tbody id=\"tb\">\n",
> +            (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int) uri.len,
> +            uri.buf);
> +  mg_printf(c, "%s",
> +            "  <tr><td><a href=\"..\">..</a></td>"
> +            "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> +
> +  fs->ls(dir, printdirentry, &d);
> +  mg_printf(c,
> +            "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> +            "</table><address>Mongoose v.%s</address></body></html>\n",
> +            MG_VERSION);
> +  n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
> +  if (n > sizeof(tmp)) n = 0;
> +  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
> +  c->is_resp = 0;                          // Mark response end
> +}
> +#endif
> +
> +// Resolve requested file into `path` and return its fs->st() result
> +static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
> +                        struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> +                        char *path, size_t path_size) {
> +  int flags, tmp;
> +  // Append URI to the root_dir, and sanitize it
> +  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf);
> +  if (n + 2 >= path_size) {
> +    mg_http_reply(c, 400, "", "Exceeded path size");
> +    return -1;
> +  }
> +  path[path_size - 1] = '\0';
> +  // Terminate root dir with slash
> +  if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
> +  if (url.len < hm->uri.len) {
> +    mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n,
> +                  path_size - n, 0);
> +  }
> +  path[path_size - 1] = '\0';  // Double-check
> +  if (!mg_path_is_sane(mg_str_n(path, path_size))) {
> +    mg_http_reply(c, 400, "", "Invalid path");
> +    return -1;
> +  }
> +  n = strlen(path);
> +  while (n > 1 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
> +  flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
> +                                               : fs->st(path, NULL, NULL);
> +  MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path,
> +              flags));
> +  if (flags == 0) {
> +    // Do nothing - let's caller decide
> +  } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> +             hm->uri.buf[hm->uri.len - 1] != '/') {
> +    mg_printf(c,
> +              "HTTP/1.1 301 Moved\r\n"
> +              "Location: %.*s/\r\n"
> +              "Content-Length: 0\r\n"
>                 "\r\n",
> -              (int) hm->uri.len, hm->uri.ptr);
> +              (int) hm->uri.len, hm->uri.buf);
>       c->is_resp = 0;
>       flags = -1;
>     } else if (flags & MG_FS_DIR) {
> @@ -2057,3318 +3036,11754 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
>         path[n + 1 + strlen(MG_HTTP_INDEX)] =
>             '\0';  // Remove appended .gz in index file name
>       } else {
> -      path[n] = '\0';  // Remove appended index file name
> +      path[n] = '\0';  // Remove appended index file name
> +    }
> +  }
> +  return flags;
> +}
> +
> +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
> +                       const struct mg_http_serve_opts *opts, char *path,
> +                       size_t path_size) {
> +  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> +  struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u;
> +  while (mg_span(s, &part, &s, ',')) {
> +    if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
> +    if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
> +    if (hm->uri.len < k.len) continue;
> +    if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
> +    u = k, p = v;
> +  }
> +  return uri_to_path2(c, hm, fs, u, p, path, path_size);
> +}
> +
> +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
> +                       const struct mg_http_serve_opts *opts) {
> +  char path[MG_PATH_MAX];
> +  const char *sp = opts->ssi_pattern;
> +  int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> +  if (flags < 0) {
> +    // Do nothing: the response has already been sent by uri_to_path()
> +  } else if (flags & MG_FS_DIR) {
> +#if MG_ENABLE_DIRLIST
> +    listdir(c, hm, opts, path);
> +#else
> +    mg_http_reply(c, 403, "", "Forbidden\n");
> +#endif
> +  } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) {
> +    mg_http_serve_ssi(c, opts->root_dir, path);
> +  } else {
> +    mg_http_serve_file(c, hm, path, opts);
> +  }
> +}
> +
> +static bool mg_is_url_safe(int c) {
> +  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> +         (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
> +}
> +
> +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
> +  size_t i, n = 0;
> +  for (i = 0; i < sl; i++) {
> +    int c = *(unsigned char *) &s[i];
> +    if (n + 4 >= len) return 0;
> +    if (mg_is_url_safe(c)) {
> +      buf[n++] = s[i];
> +    } else {
> +      mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
> +      n += 3;
> +    }
> +  }
> +  if (len > 0 && n < len - 1) buf[n] = '\0';  // Null-terminate the destination
> +  if (len > 0) buf[len - 1] = '\0';           // Always.
> +  return n;
> +}
> +
> +void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
> +                   char *pass, size_t passlen) {
> +  struct mg_str *v = mg_http_get_header(hm, "Authorization");
> +  user[0] = pass[0] = '\0';
> +  if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
> +    char buf[256];
> +    size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf));
> +    const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
> +    if (p != NULL) {
> +      mg_snprintf(user, userlen, "%.*s", p - buf, buf);
> +      mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1);
> +    }
> +  } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) {
> +    mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
> +  } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> +    struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12));
> +    if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf);
> +  } else {
> +    mg_http_get_var(&hm->query, "access_token", pass, passlen);
> +  }
> +}
> +
> +static struct mg_str stripquotes(struct mg_str s) {
> +  return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
> +             ? mg_str_n(s.buf + 1, s.len - 2)
> +             : s;
> +}
> +
> +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
> +  size_t i;
> +  for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> +    if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) {
> +      const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
> +      int q = p < x && *p == '"' ? 1 : 0;
> +      while (p < x &&
> +             (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> +        p++;
> +      // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len,
> +      // v.buf, (int) (p - b), b));
> +      return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> +    }
> +  }
> +  return mg_str_n(NULL, 0);
> +}
> +
> +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> +                    struct mg_fs *fs, const char *dir, size_t max_size) {
> +  char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
> +  long res = 0, offset;
> +  mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> +  mg_http_get_var(&hm->query, "file", file, sizeof(file));
> +  offset = strtol(buf, NULL, 0);
> +  mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
> +  if (hm->body.len == 0) {
> +    mg_http_reply(c, 200, "", "%ld", res);  // Nothing to write
> +  } else if (file[0] == '\0') {
> +    mg_http_reply(c, 400, "", "file required");
> +    res = -1;
> +  } else if (mg_path_is_sane(mg_str(file)) == false) {
> +    mg_http_reply(c, 400, "", "%s: invalid file", file);
> +    res = -2;
> +  } else if (offset < 0) {
> +    mg_http_reply(c, 400, "", "offset required");
> +    res = -3;
> +  } else if ((size_t) offset + hm->body.len > max_size) {
> +    mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> +                  (unsigned long) max_size);
> +    res = -4;
> +  } else {
> +    struct mg_fd *fd;
> +    size_t current_size = 0;
> +    MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
> +    if (offset == 0) fs->rm(path);  // If offset if 0, truncate file
> +    fs->st(path, &current_size, NULL);
> +    if (offset > 0 && current_size != (size_t) offset) {
> +      mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> +      res = -5;
> +    } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> +      mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> +      res = -6;
> +    } else {
> +      res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
> +      mg_fs_close(fd);
> +      mg_http_reply(c, 200, "", "%ld", res);
> +    }
> +  }
> +  return res;
> +}
> +
> +int mg_http_status(const struct mg_http_message *hm) {
> +  return atoi(hm->uri.buf);
> +}
> +
> +static bool is_hex_digit(int c) {
> +  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> +         (c >= 'A' && c <= 'F');
> +}
> +
> +static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
> +  int i = 0, n = 0;
> +  if (len < 3) return 0;
> +  while (i < len && is_hex_digit(buf[i])) i++;
> +  if (i == 0) return -1;                     // Error, no length specified
> +  if (i > (int) sizeof(int) * 2) return -1;  // Chunk length is too big
> +  if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1;  // Error
> +  if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false)
> +    return -1;                    // Decode chunk length, overflow
> +  if (n < 0) return -1;           // Error. TODO(): some checks now redundant
> +  if (n > len - i - 4) return 0;  // Chunk not yet fully buffered
> +  if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1;  // Error
> +  *pl = i + 2, *dl = n;
> +  return i + 2 + n + 2;
> +}
> +
> +static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> +    struct mg_http_message hm;
> +    size_t ofs = 0;  // Parsing offset
> +    while (c->is_resp == 0 && ofs < c->recv.len) {
> +      const char *buf = (char *) c->recv.buf + ofs;
> +      int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
> +      struct mg_str *te;  // Transfer - encoding header
> +      bool is_chunked = false;
> +      if (n < 0) {
> +        // We don't use mg_error() here, to avoid closing pipelined requests
> +        // prematurely, see #2592
> +        MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
> +        c->is_draining = 1;
> +        mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
> +        c->recv.len = 0;
> +        return;
> +      }
> +      if (n == 0) break;                 // Request is not buffered yet
> +      mg_call(c, MG_EV_HTTP_HDRS, &hm);  // Got all HTTP headers
> +      if (ev == MG_EV_CLOSE) {           // If client did not set Content-Length
> +        hm.message.len = c->recv.len - ofs;  // and closes now, deliver MSG
> +        hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf);
> +      }
> +      if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
> +        if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
> +          is_chunked = true;
> +        } else {
> +          mg_error(c, "Invalid Transfer-Encoding");  // See #2460
> +          return;
> +        }
> +      } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
> +        // #2593: HTTP packets must contain either Transfer-Encoding or
> +        // Content-length
> +        bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
> +        bool require_content_len = false;
> +        if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 ||
> +                             mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
> +          // POST and PUT should include an entity body. Therefore, they should
> +          // contain a Content-length header. Other requests can also contain a
> +          // body, but their content has no defined semantics (RFC 7231)
> +          require_content_len = true;
> +        } else if (is_response) {
> +          // HTTP spec 7.2 Entity body: All other responses must include a body
> +          // or Content-Length header field defined with a value of 0.
> +          int status = mg_http_status(&hm);
> +          require_content_len = status >= 200 && status != 204 && status != 304;
> +        }
> +        if (require_content_len) {
> +          mg_http_reply(c, 411, "", "");
> +          MG_ERROR(("%s", "Content length missing from request"));
> +        }
> +      }
> +
> +      if (is_chunked) {
> +        // For chunked data, strip off prefixes and suffixes from chunks
> +        // and relocate them right after the headers, then report a message
> +        char *s = (char *) c->recv.buf + ofs + n;
> +        int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n);
> +
> +        // Find zero-length chunk (the end of the body)
> +        while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl;
> +        if (cl == 0) break;  // No zero-len chunk, buffer more data
> +        if (cl < 0) {
> +          mg_error(c, "Invalid chunk");
> +          break;
> +        }
> +
> +        // Zero chunk found. Second pass: strip + relocate
> +        o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
> +        while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
> +          memmove(s + hm.body.len, s + o + pl, (size_t) dl);
> +          o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl;
> +          if (dl == 0) break;
> +        }
> +        ofs += (size_t) (n + o);
> +      } else {  // Normal, non-chunked data
> +        size_t len = c->recv.len - ofs - (size_t) n;
> +        if (hm.body.len > len) break;  // Buffer more data
> +        ofs += (size_t) n + hm.body.len;
> +      }
> +
> +      if (c->is_accepted) c->is_resp = 1;  // Start generating response
> +      mg_call(c, MG_EV_HTTP_MSG, &hm);     // User handler can clear is_resp
> +    }
> +    if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs);  // Delete processed data
> +  }
> +  (void) ev_data;
> +}
> +
> +static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_HTTP_MSG) {
> +    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> +    if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
> +      mg_http_reply(c, 200, "", "ok\n");
> +      c->is_draining = 1;
> +      c->data[0] = 'X';
> +    } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
> +      int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
> +      mg_log_set(level);
> +      mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
> +    } else {
> +      mg_http_reply(c, 200, "", "hi\n");
> +    }
> +  } else if (ev == MG_EV_CLOSE) {
> +    if (c->data[0] == 'X') *(bool *) c->fn_data = true;
> +  }
> +}
> +
> +void mg_hello(const char *url) {
> +  struct mg_mgr mgr;
> +  bool done = false;
> +  mg_mgr_init(&mgr);
> +  if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
> +  while (done == false) mg_mgr_poll(&mgr, 100);
> +  mg_mgr_free(&mgr);
> +}
> +
> +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url,
> +                                      mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> +  if (c != NULL) c->pfn = http_cb;
> +  return c;
> +}
> +
> +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
> +                                     mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> +  if (c != NULL) c->pfn = http_cb;
> +  return c;
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/iobuf.c"
> +#endif
> +
> +
> +
> +
> +
> +static size_t roundup(size_t size, size_t align) {
> +  return align == 0 ? size : (size + align - 1) / align * align;
> +}
> +
> +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> +  int ok = 1;
> +  new_size = roundup(new_size, io->align);
> +  if (new_size == 0) {
> +    mg_bzero(io->buf, io->size);
> +    free(io->buf);
> +    io->buf = NULL;
> +    io->len = io->size = 0;
> +  } else if (new_size != io->size) {
> +    // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
> +    // porting to some obscure platforms like FreeRTOS
> +    void *p = calloc(1, new_size);
> +    if (p != NULL) {
> +      size_t len = new_size < io->len ? new_size : io->len;
> +      if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> +      mg_bzero(io->buf, io->size);
> +      free(io->buf);
> +      io->buf = (unsigned char *) p;
> +      io->size = new_size;
> +    } else {
> +      ok = 0;
> +      MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
> +    }
> +  }
> +  return ok;
> +}
> +
> +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> +  io->buf = NULL;
> +  io->align = align;
> +  io->size = io->len = 0;
> +  return mg_iobuf_resize(io, size);
> +}
> +
> +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
> +                    size_t len) {
> +  size_t new_size = roundup(io->len + len, io->align);
> +  mg_iobuf_resize(io, new_size);      // Attempt to resize
> +  if (new_size != io->size) len = 0;  // Resize failure, append nothing
> +  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
> +  if (buf != NULL) memmove(io->buf + ofs, buf, len);
> +  if (ofs > io->len) io->len += ofs - io->len;
> +  io->len += len;
> +  return len;
> +}
> +
> +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> +  if (ofs > io->len) ofs = io->len;
> +  if (ofs + len > io->len) len = io->len - ofs;
> +  if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
> +  if (io->buf) mg_bzero(io->buf + io->len - len, len);
> +  io->len -= len;
> +  return len;
> +}
> +
> +void mg_iobuf_free(struct mg_iobuf *io) {
> +  mg_iobuf_resize(io, 0);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/json.c"
> +#endif
> +
> +
> +
> +
> +static const char *escapeseq(int esc) {
> +  return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> +}
> +
> +static char json_esc(int c, int esc) {
> +  const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> +  for (p = esc1; *p != '\0'; p++) {
> +    if (*p == c) return esc2[p - esc1];
> +  }
> +  return 0;
> +}
> +
> +static int mg_pass_string(const char *s, int len) {
> +  int i;
> +  for (i = 0; i < len; i++) {
> +    if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> +      i++;
> +    } else if (s[i] == '\0') {
> +      return MG_JSON_INVALID;
> +    } else if (s[i] == '"') {
> +      return i;
> +    }
> +  }
> +  return MG_JSON_INVALID;
> +}
> +
> +static double mg_atod(const char *p, int len, int *numlen) {
> +  double d = 0.0;
> +  int i = 0, sign = 1;
> +
> +  // Sign
> +  if (i < len && *p == '-') {
> +    sign = -1, i++;
> +  } else if (i < len && *p == '+') {
> +    i++;
> +  }
> +
> +  // Decimal
> +  for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> +    d *= 10.0;
> +    d += p[i] - '0';
> +  }
> +  d *= sign;
> +
> +  // Fractional
> +  if (i < len && p[i] == '.') {
> +    double frac = 0.0, base = 0.1;
> +    i++;
> +    for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> +      frac += base * (p[i] - '0');
> +      base /= 10.0;
> +    }
> +    d += frac * sign;
> +  }
> +
> +  // Exponential
> +  if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> +    int j, exp = 0, minus = 0;
> +    i++;
> +    if (i < len && p[i] == '-') minus = 1, i++;
> +    if (i < len && p[i] == '+') i++;
> +    while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> +      exp = exp * 10 + (p[i++] - '0');
> +    if (minus) exp = -exp;
> +    for (j = 0; j < exp; j++) d *= 10.0;
> +    for (j = 0; j < -exp; j++) d /= 10.0;
> +  }
> +
> +  if (numlen != NULL) *numlen = i;
> +  return d;
> +}
> +
> +// Iterate over object or array elements
> +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
> +                    struct mg_str *val) {
> +  if (ofs >= obj.len) {
> +    ofs = 0;  // Out of boundaries, stop scanning
> +  } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
> +    ofs = 0;  // Not an array or object, stop
> +  } else {
> +    struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
> +    if (ofs == 0) ofs++, sub.buf++, sub.len--;
> +    if (*obj.buf == '[') {  // Iterate over an array
> +      int n = 0, o = mg_json_get(sub, "$", &n);
> +      if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> +        ofs = 0;  // Error parsing key, stop scanning
> +      } else {
> +        if (key) *key = mg_str_n(NULL, 0);
> +        if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> +        ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> +      }
> +    } else {  // Iterate over an object
> +      int n = 0, o = mg_json_get(sub, "$", &n);
> +      if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> +        ofs = 0;  // Error parsing key, stop scanning
> +      } else {
> +        if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
> +        sub.buf += o + n, sub.len -= (size_t) (o + n);
> +        while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
> +        if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
> +        n = 0, o = mg_json_get(sub, "$", &n);
> +        if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> +          ofs = 0;  // Error parsing value, stop scanning
> +        } else {
> +          if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> +          ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> +        }
> +      }
> +    }
> +    // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
> +    while (ofs && ofs < obj.len &&
> +           (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
> +            obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
> +      ofs++;
> +    }
> +    if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
> +    if (ofs > obj.len) ofs = 0;
> +  }
> +  return ofs;
> +}
> +
> +int mg_json_get(struct mg_str json, const char *path, int *toklen) {
> +  const char *s = json.buf;
> +  int len = (int) json.len;
> +  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
> +  unsigned char nesting[MG_JSON_MAX_DEPTH];
> +  int i = 0;             // Current offset in `s`
> +  int j = 0;             // Offset in `s` we're looking for (return value)
> +  int depth = 0;         // Current depth (nesting level)
> +  int ed = 0;            // Expected depth
> +  int pos = 1;           // Current position in `path`
> +  int ci = -1, ei = -1;  // Current and expected index in array
> +
> +  if (toklen) *toklen = 0;
> +  if (path[0] != '$') return MG_JSON_INVALID;
> +
> +#define MG_CHECKRET(x)                                  \
> +  do {                                                  \
> +    if (depth == ed && path[pos] == '\0' && ci == ei) { \
> +      if (toklen) *toklen = i - j + 1;                  \
> +      return j;                                         \
> +    }                                                   \
> +  } while (0)
> +
> +// In the ascii table, the distance between `[` and `]` is 2.
> +// Ditto for `{` and `}`. Hence +2 in the code below.
> +#define MG_EOO(x)                                            \
> +  do {                                                       \
> +    if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND;   \
> +    if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> +    depth--;                                                 \
> +    MG_CHECKRET(x);                                          \
> +  } while (0)
> +
> +  for (i = 0; i < len; i++) {
> +    unsigned char c = ((unsigned char *) s)[i];
> +    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> +    switch (expecting) {
> +      case S_VALUE:
> +        // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
> +        if (depth == ed) j = i;
> +        if (c == '{') {
> +          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> +          if (depth == ed && path[pos] == '.' && ci == ei) {
> +            // If we start the object, reset array indices
> +            ed++, pos++, ci = ei = -1;
> +          }
> +          nesting[depth++] = c;
> +          expecting = S_KEY;
> +          break;
> +        } else if (c == '[') {
> +          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> +          if (depth == ed && path[pos] == '[' && ei == ci) {
> +            ed++, pos++, ci = 0;
> +            for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> +              ei *= 10;
> +              ei += path[pos] - '0';
> +            }
> +            if (path[pos] != 0) pos++;
> +          }
> +          nesting[depth++] = c;
> +          break;
> +        } else if (c == ']' && depth > 0) {  // Empty array
> +          MG_EOO(']');
> +        } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
> +          i += 3;
> +        } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
> +          i += 3;
> +        } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
> +          i += 4;
> +        } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> +          int numlen = 0;
> +          mg_atod(&s[i], len - i, &numlen);
> +          i += numlen - 1;
> +        } else if (c == '"') {
> +          int n = mg_pass_string(&s[i + 1], len - i - 1);
> +          if (n < 0) return n;
> +          i += n + 1;
> +        } else {
> +          return MG_JSON_INVALID;
> +        }
> +        MG_CHECKRET('V');
> +        if (depth == ed && ei >= 0) ci++;
> +        expecting = S_COMMA_OR_EOO;
> +        break;
> +
> +      case S_KEY:
> +        if (c == '"') {
> +          int n = mg_pass_string(&s[i + 1], len - i - 1);
> +          if (n < 0) return n;
> +          if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> +          if (depth < ed) return MG_JSON_NOT_FOUND;
> +          if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> +          // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n,
> +          //        &s[i + 1], n, depth, ed, ci, ei);
> +          //  NOTE(cpq): in the check sequence below is important.
> +          //  strncmp() must go first: it fails fast if the remaining length
> +          //  of the path is smaller than `n`.
> +          if (depth == ed && path[pos - 1] == '.' &&
> +              strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> +              (path[pos + n] == '\0' || path[pos + n] == '.' ||
> +               path[pos + n] == '[')) {
> +            pos += n;
> +          }
> +          i += n + 1;
> +          expecting = S_COLON;
> +        } else if (c == '}') {  // Empty object
> +          MG_EOO('}');
> +          expecting = S_COMMA_OR_EOO;
> +          if (depth == ed && ei >= 0) ci++;
> +        } else {
> +          return MG_JSON_INVALID;
> +        }
> +        break;
> +
> +      case S_COLON:
> +        if (c == ':') {
> +          expecting = S_VALUE;
> +        } else {
> +          return MG_JSON_INVALID;
> +        }
> +        break;
> +
> +      case S_COMMA_OR_EOO:
> +        if (depth <= 0) {
> +          return MG_JSON_INVALID;
> +        } else if (c == ',') {
> +          expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> +        } else if (c == ']' || c == '}') {
> +          if (depth == ed && c == '}' && path[pos - 1] == '.')
> +            return MG_JSON_NOT_FOUND;
> +          if (depth == ed && c == ']' && path[pos - 1] == ',')
> +            return MG_JSON_NOT_FOUND;
> +          MG_EOO('O');
> +          if (depth == ed && ei >= 0) ci++;
> +        } else {
> +          return MG_JSON_INVALID;
> +        }
> +        break;
> +    }
> +  }
> +  return MG_JSON_NOT_FOUND;
> +}
> +
> +struct mg_str mg_json_get_tok(struct mg_str json, const char *path) {
> +  int len = 0, ofs = mg_json_get(json, path, &len);
> +  return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
> +                  (size_t) (len < 0 ? 0 : len));
> +}
> +
> +bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
> +  int n, toklen, found = 0;
> +  if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> +      (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) {
> +    if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
> +    found = 1;
> +  }
> +  return found;
> +}
> +
> +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
> +  int found = 0, off = mg_json_get(json, path, NULL);
> +  if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
> +    if (v != NULL) *v = json.buf[off] == 't';
> +    found = 1;
> +  }
> +  return found;
> +}
> +
> +bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
> +  size_t i, j;
> +  for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
> +    if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
> +      //  \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
> +      //  range. More complex chars would require dragging in a UTF8 library,
> +      //  which is too much for us
> +      if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
> +                        sizeof(uint8_t)) == false)
> +        return false;
> +      i += 5;
> +    } else if (s.buf[i] == '\\' && i + 1 < s.len) {
> +      char c = json_esc(s.buf[i + 1], 0);
> +      if (c == 0) return false;
> +      to[j] = c;
> +      i++;
> +    } else {
> +      to[j] = s.buf[i];
> +    }
> +  }
> +  if (j >= n) return false;
> +  if (n > 0) to[j] = '\0';
> +  return true;
> +}
> +
> +char *mg_json_get_str(struct mg_str json, const char *path) {
> +  char *result = NULL;
> +  int len = 0, off = mg_json_get(json, path, &len);
> +  if (off >= 0 && len > 1 && json.buf[off] == '"') {
> +    if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> +        !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)),
> +                          result, (size_t) len)) {
> +      free(result);
> +      result = NULL;
> +    }
> +  }
> +  return result;
> +}
> +
> +char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
> +  char *result = NULL;
> +  int len = 0, off = mg_json_get(json, path, &len);
> +  if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> +      (result = (char *) calloc(1, (size_t) len)) != NULL) {
> +    size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result,
> +                                (size_t) len);
> +    if (slen != NULL) *slen = (int) k;
> +  }
> +  return result;
> +}
> +
> +char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
> +  char *result = NULL;
> +  int len = 0, off = mg_json_get(json, path, &len);
> +  if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> +      (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> +    int i;
> +    for (i = 0; i < len - 2; i += 2) {
> +      mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1],
> +                    sizeof(uint8_t));
> +    }
> +    result[len / 2 - 1] = '\0';
> +    if (slen != NULL) *slen = len / 2 - 1;
> +  }
> +  return result;
> +}
> +
> +long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
> +  double dv;
> +  long result = dflt;
> +  if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> +  return result;
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/log.c"
> +#endif
> +
> +
> +
> +
> +
> +int mg_log_level = MG_LL_INFO;
> +static mg_pfn_t s_log_func = mg_pfn_stdout;
> +static void *s_log_func_param = NULL;
> +
> +void mg_log_set_fn(mg_pfn_t fn, void *param) {
> +  s_log_func = fn;
> +  s_log_func_param = param;
> +}
> +
> +static void logc(unsigned char c) {
> +  s_log_func((char) c, s_log_func_param);
> +}
> +
> +static void logs(const char *buf, size_t len) {
> +  size_t i;
> +  for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> +}
> +
> +#if MG_ENABLE_CUSTOM_LOG
> +// Let user define their own mg_log_prefix() and mg_log()
> +#else
> +void mg_log_prefix(int level, const char *file, int line, const char *fname) {
> +  const char *p = strrchr(file, '/');
> +  char buf[41];
> +  size_t n;
> +  if (p == NULL) p = strrchr(file, '\\');
> +  n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,
> +                  p == NULL ? file : p + 1, line, fname);
> +  if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> +  while (n < sizeof(buf)) buf[n++] = ' ';
> +  logs(buf, n - 1);
> +}
> +
> +void mg_log(const char *fmt, ...) {
> +  va_list ap;
> +  va_start(ap, fmt);
> +  mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> +  va_end(ap);
> +  logs("\r\n", 2);
> +}
> +#endif
> +
> +static unsigned char nibble(unsigned c) {
> +  return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> +}
> +
> +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> +void mg_hexdump(const void *buf, size_t len) {
> +  const unsigned char *p = (const unsigned char *) buf;
> +  unsigned char ascii[16], alen = 0;
> +  size_t i;
> +  for (i = 0; i < len; i++) {
> +    if ((i % 16) == 0) {
> +      // Print buffered ascii chars
> +      if (i > 0) logs("  ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0;
> +      // Print hex address, then \t
> +      logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> +          logc(nibble((i >> 4) & 15)), logc('0'), logs("   ", 3);
> +    }
> +    logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15));  // Two nibbles, e.g. c5
> +    logc(' ');                                         // Space after hex number
> +    ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.';        // Add to the ascii buf
> +  }
> +  while (alen < 16) logs("   ", 3), ascii[alen++] = ' ';
> +  logs("  ", 2), logs((char *) ascii, 16), logc('\n');
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/md5.c"
> +#endif
> +
> +
> +
> +//  This code implements the MD5 message-digest algorithm.
> +//  The algorithm is due to Ron Rivest.  This code was
> +//  written by Colin Plumb in 1993, no copyright is claimed.
> +//  This code is in the public domain; do with it what you wish.
> +//
> +//  Equivalent code is available from RSA Data Security, Inc.
> +//  This code has been tested against that, and is equivalent,
> +//  except that you don't need to include two pages of legalese
> +//  with every copy.
> +//
> +//  To compute the message digest of a chunk of bytes, declare an
> +//  MD5Context structure, pass it to MD5Init, call MD5Update as
> +//  needed on buffers full of bytes, and then call MD5Final, which
> +//  will fill a supplied 16-byte array with the digest.
> +
> +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> +
> +static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> +  if (MG_BIG_ENDIAN) {
> +    do {
> +      uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> +                   ((unsigned) buf[1] << 8 | buf[0]);
> +      *(uint32_t *) buf = t;
> +      buf += 4;
> +    } while (--longs);
> +  } else {
> +    (void) buf, (void) longs;  // Little endian. Do nothing
> +  }
> +}
> +
> +#define F1(x, y, z) (z ^ (x & (y ^ z)))
> +#define F2(x, y, z) F1(z, x, y)
> +#define F3(x, y, z) (x ^ y ^ z)
> +#define F4(x, y, z) (y ^ (x | ~z))
> +
> +#define MD5STEP(f, w, x, y, z, data, s) \
> +  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> +
> +/*
> + * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
> + * initialization constants.
> + */
> +void mg_md5_init(mg_md5_ctx *ctx) {
> +  ctx->buf[0] = 0x67452301;
> +  ctx->buf[1] = 0xefcdab89;
> +  ctx->buf[2] = 0x98badcfe;
> +  ctx->buf[3] = 0x10325476;
> +
> +  ctx->bits[0] = 0;
> +  ctx->bits[1] = 0;
> +}
> +
> +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
> +  uint32_t a, b, c, d;
> +
> +  a = buf[0];
> +  b = buf[1];
> +  c = buf[2];
> +  d = buf[3];
> +
> +  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> +  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> +  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> +  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> +  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> +  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> +  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> +  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> +  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> +  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> +  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> +  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> +  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> +  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> +  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> +  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> +
> +  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> +  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> +  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> +  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> +  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> +  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> +  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> +  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> +  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> +  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> +  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> +  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> +  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> +  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> +  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> +  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> +
> +  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> +  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> +  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> +  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> +  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> +  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> +  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> +  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> +  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> +  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> +  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> +  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> +  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> +  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> +  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> +  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> +
> +  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> +  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> +  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> +  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> +  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> +  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> +  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> +  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> +  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> +  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> +  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> +  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> +  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> +  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> +  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> +  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> +
> +  buf[0] += a;
> +  buf[1] += b;
> +  buf[2] += c;
> +  buf[3] += d;
> +}
> +
> +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) {
> +  uint32_t t;
> +
> +  t = ctx->bits[0];
> +  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
> +  ctx->bits[1] += (uint32_t) len >> 29;
> +
> +  t = (t >> 3) & 0x3f;
> +
> +  if (t) {
> +    unsigned char *p = (unsigned char *) ctx->in + t;
> +
> +    t = 64 - t;
> +    if (len < t) {
> +      memcpy(p, buf, len);
> +      return;
> +    }
> +    memcpy(p, buf, t);
> +    mg_byte_reverse(ctx->in, 16);
> +    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> +    buf += t;
> +    len -= t;
> +  }
> +
> +  while (len >= 64) {
> +    memcpy(ctx->in, buf, 64);
> +    mg_byte_reverse(ctx->in, 16);
> +    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> +    buf += 64;
> +    len -= 64;
> +  }
> +
> +  memcpy(ctx->in, buf, len);
> +}
> +
> +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> +  unsigned count;
> +  unsigned char *p;
> +  uint32_t *a;
> +
> +  count = (ctx->bits[0] >> 3) & 0x3F;
> +
> +  p = ctx->in + count;
> +  *p++ = 0x80;
> +  count = 64 - 1 - count;
> +  if (count < 8) {
> +    memset(p, 0, count);
> +    mg_byte_reverse(ctx->in, 16);
> +    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> +    memset(ctx->in, 0, 56);
> +  } else {
> +    memset(p, 0, count - 8);
> +  }
> +  mg_byte_reverse(ctx->in, 14);
> +
> +  a = (uint32_t *) ctx->in;
> +  a[14] = ctx->bits[0];
> +  a[15] = ctx->bits[1];
> +
> +  mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> +  mg_byte_reverse((unsigned char *) ctx->buf, 4);
> +  memcpy(digest, ctx->buf, 16);
> +  memset((char *) ctx, 0, sizeof(*ctx));
> +}
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/mqtt.c"
> +#endif
> +
> +
> +
> +
> +
> +
> +
> +
> +#define MQTT_CLEAN_SESSION 0x02
> +#define MQTT_HAS_WILL 0x04
> +#define MQTT_WILL_RETAIN 0x20
> +#define MQTT_HAS_PASSWORD 0x40
> +#define MQTT_HAS_USER_NAME 0x80
> +
> +struct mg_mqtt_pmap {
> +  uint8_t id;
> +  uint8_t type;
> +};
> +
> +static const struct mg_mqtt_pmap s_prop_map[] = {
> +    {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> +    {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> +    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
> +    {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> +    {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
> +    {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> +    {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
> +    {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
> +    {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> +    {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> +    {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
> +    {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
> +    {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
> +    {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> +    {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
> +
> +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags,
> +                         uint32_t len) {
> +  uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> +  buf[0] = (uint8_t) ((cmd << 4) | flags);
> +  do {
> +    *vlen = len % 0x80;
> +    len /= 0x80;
> +    if (len > 0) *vlen |= 0x80;
> +    vlen++;
> +  } while (len > 0 && vlen < &buf[sizeof(buf)]);
> +  mg_send(c, buf, (size_t) (vlen - buf));
> +}
> +
> +static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> +  mg_send(c, &value, sizeof(value));
> +}
> +
> +static void mg_send_u32(struct mg_connection *c, uint32_t value) {
> +  mg_send(c, &value, sizeof(value));
> +}
> +
> +static uint8_t varint_size(size_t length) {
> +  uint8_t bytes_needed = 0;
> +  do {
> +    bytes_needed++;
> +    length /= 0x80;
> +  } while (length > 0);
> +  return bytes_needed;
> +}
> +
> +static size_t encode_varint(uint8_t *buf, size_t value) {
> +  size_t len = 0;
> +
> +  do {
> +    uint8_t b = (uint8_t) (value % 128);
> +    value /= 128;
> +    if (value > 0) b |= 0x80;
> +    buf[len++] = b;
> +  } while (value > 0);
> +
> +  return len;
> +}
> +
> +static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) {
> +  size_t multiplier = 1, offset;
> +  *value = 0;
> +
> +  for (offset = 0; offset < 4 && offset < len; offset++) {
> +    uint8_t encoded_byte = buf[offset];
> +    *value += (encoded_byte & 0x7f) * multiplier;
> +    multiplier *= 128;
> +
> +    if ((encoded_byte & 0x80) == 0) return offset + 1;
> +  }
> +
> +  return 0;
> +}
> +
> +static int mqtt_prop_type_by_id(uint8_t prop_id) {
> +  size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]);
> +  for (i = 0; i < num_properties; ++i) {
> +    if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
> +  }
> +  return -1;  // Property ID not found
> +}
> +
> +// Returns the size of the properties section, without the
> +// size of the content's length
> +static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) {
> +  size_t i, size = 0;
> +  for (i = 0; i < count; i++) {
> +    size++;  // identifier
> +    switch (mqtt_prop_type_by_id(props[i].id)) {
> +      case MQTT_PROP_TYPE_STRING_PAIR:
> +        size += (uint32_t) (props[i].val.len + props[i].key.len +
> +                            2 * sizeof(uint16_t));
> +        break;
> +      case MQTT_PROP_TYPE_STRING:
> +        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> +        break;
> +      case MQTT_PROP_TYPE_BINARY_DATA:
> +        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> +        break;
> +      case MQTT_PROP_TYPE_VARIABLE_INT:
> +        size += varint_size((uint32_t) props[i].iv);
> +        break;
> +      case MQTT_PROP_TYPE_INT:
> +        size += (uint32_t) sizeof(uint32_t);
> +        break;
> +      case MQTT_PROP_TYPE_SHORT:
> +        size += (uint32_t) sizeof(uint16_t);
> +        break;
> +      case MQTT_PROP_TYPE_BYTE:
> +        size += (uint32_t) sizeof(uint8_t);
> +        break;
> +      default:
> +        return size;  // cannot parse further down
> +    }
> +  }
> +
> +  return size;
> +}
> +
> +// returns the entire size of the properties section, including the
> +// size of the variable length of the content
> +static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) {
> +  size_t size = get_properties_length(props, count);
> +  size += varint_size(size);
> +  return size;
> +}
> +
> +static void mg_send_mqtt_properties(struct mg_connection *c,
> +                                    struct mg_mqtt_prop *props, size_t nprops) {
> +  size_t total_size = get_properties_length(props, nprops);
> +  uint8_t buf_v[4] = {0, 0, 0, 0};
> +  uint8_t buf[4] = {0, 0, 0, 0};
> +  size_t i, len = encode_varint(buf, total_size);
> +
> +  mg_send(c, buf, (size_t) len);
> +  for (i = 0; i < nprops; i++) {
> +    mg_send(c, &props[i].id, sizeof(props[i].id));
> +    switch (mqtt_prop_type_by_id(props[i].id)) {
> +      case MQTT_PROP_TYPE_STRING_PAIR:
> +        mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
> +        mg_send(c, props[i].key.buf, props[i].key.len);
> +        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> +        mg_send(c, props[i].val.buf, props[i].val.len);
> +        break;
> +      case MQTT_PROP_TYPE_BYTE:
> +        mg_send(c, &props[i].iv, sizeof(uint8_t));
> +        break;
> +      case MQTT_PROP_TYPE_SHORT:
> +        mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
> +        break;
> +      case MQTT_PROP_TYPE_INT:
> +        mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
> +        break;
> +      case MQTT_PROP_TYPE_STRING:
> +        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> +        mg_send(c, props[i].val.buf, props[i].val.len);
> +        break;
> +      case MQTT_PROP_TYPE_BINARY_DATA:
> +        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> +        mg_send(c, props[i].val.buf, props[i].val.len);
> +        break;
> +      case MQTT_PROP_TYPE_VARIABLE_INT:
> +        len = encode_varint(buf_v, props[i].iv);
> +        mg_send(c, buf_v, (size_t) len);
> +        break;
> +    }
> +  }
> +}
> +
> +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop,
> +                         size_t ofs) {
> +  uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
> +  uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
> +  size_t new_pos = ofs, len;
> +  prop->id = i[0];
> +
> +  if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size)
> +    return 0;
> +  i++, new_pos++;
> +
> +  switch (mqtt_prop_type_by_id(prop->id)) {
> +    case MQTT_PROP_TYPE_STRING_PAIR:
> +      prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> +      prop->key.buf = (char *) i + 2;
> +      i += 2 + prop->key.len;
> +      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> +      prop->val.buf = (char *) i + 2;
> +      new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
> +      break;
> +    case MQTT_PROP_TYPE_BYTE:
> +      prop->iv = (uint8_t) i[0];
> +      new_pos++;
> +      break;
> +    case MQTT_PROP_TYPE_SHORT:
> +      prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> +      new_pos += sizeof(uint16_t);
> +      break;
> +    case MQTT_PROP_TYPE_INT:
> +      prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
> +                 ((uint32_t) i[2] << 8) | i[3];
> +      new_pos += sizeof(uint32_t);
> +      break;
> +    case MQTT_PROP_TYPE_STRING:
> +      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> +      prop->val.buf = (char *) i + 2;
> +      new_pos += 2 + prop->val.len;
> +      break;
> +    case MQTT_PROP_TYPE_BINARY_DATA:
> +      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> +      prop->val.buf = (char *) i + 2;
> +      new_pos += 2 + prop->val.len;
> +      break;
> +    case MQTT_PROP_TYPE_VARIABLE_INT:
> +      len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
> +      new_pos = (!len) ? 0 : new_pos + len;
> +      break;
> +    default:
> +      new_pos = 0;
> +  }
> +
> +  return new_pos;
> +}
> +
> +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
> +  char client_id[21];
> +  struct mg_str cid = opts->client_id;
> +  size_t total_len = 7 + 1 + 2 + 2;
> +  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> +
> +  if (cid.len == 0) {
> +    mg_random_str(client_id, sizeof(client_id) - 1);
> +    client_id[sizeof(client_id) - 1] = '\0';
> +    cid = mg_str(client_id);
> +  }
> +
> +  if (hdr[6] == 0) hdr[6] = 4;  // If version is not set, use 4 (3.1.1)
> +  c->is_mqtt5 = hdr[6] == 5;    // Set version 5 flag
> +  hdr[7] = (uint8_t) ((opts->qos & 3) << 3);  // Connection flags
> +  if (opts->user.len > 0) {
> +    total_len += 2 + (uint32_t) opts->user.len;
> +    hdr[7] |= MQTT_HAS_USER_NAME;
> +  }
> +  if (opts->pass.len > 0) {
> +    total_len += 2 + (uint32_t) opts->pass.len;
> +    hdr[7] |= MQTT_HAS_PASSWORD;
> +  }
> +  if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t
> +    total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len;
> +    hdr[7] |= MQTT_HAS_WILL;
> +  }
> +  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> +  if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
> +  total_len += (uint32_t) cid.len;
> +  if (c->is_mqtt5) {
> +    total_len += get_props_size(opts->props, opts->num_props);
> +    if (hdr[7] & MQTT_HAS_WILL)
> +      total_len += get_props_size(opts->will_props, opts->num_will_props);
> +  }
> +
> +  mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
> +  mg_send(c, hdr, sizeof(hdr));
> +  // keepalive == 0 means "do not disconnect us!"
> +  mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> +
> +  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
> +
> +  mg_send_u16(c, mg_htons((uint16_t) cid.len));
> +  mg_send(c, cid.buf, cid.len);
> +
> +  if (hdr[7] & MQTT_HAS_WILL) {
> +    if (c->is_mqtt5)
> +      mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props);
> +
> +    mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> +    mg_send(c, opts->topic.buf, opts->topic.len);
> +    mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
> +    mg_send(c, opts->message.buf, opts->message.len);
> +  }
> +  if (opts->user.len > 0) {
> +    mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> +    mg_send(c, opts->user.buf, opts->user.len);
> +  }
> +  if (opts->pass.len > 0) {
> +    mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> +    mg_send(c, opts->pass.buf, opts->pass.len);
> +  }
> +}
> +
> +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
> +  uint16_t id = opts->retransmit_id;
> +  uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0));
> +  size_t len = 2 + opts->topic.len + opts->message.len;
> +  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
> +            (char *) opts->topic.buf, (int) opts->message.len,
> +            (char *) opts->message.buf));
> +  if (opts->qos > 0) len += 2;
> +  if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props);
> +
> +  if (opts->qos > 0 && id != 0) flags |= 1 << 3;
> +  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
> +  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> +  mg_send(c, opts->topic.buf, opts->topic.len);
> +  if (opts->qos > 0) {    // need to send 'id' field
> +    if (id == 0) {  // generate new one if not resending
> +      if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> +      id = c->mgr->mqtt_id;
> +    }
> +    mg_send_u16(c, mg_htons(id));
> +  }
> +
> +  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
> +
> +  if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len);
> +  return id;
> +}
> +
> +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
> +  uint8_t qos_ = opts->qos & 3;
> +  size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0;
> +  size_t len = 2 + opts->topic.len + 2 + 1 + plen;
> +
> +  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
> +  if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> +  mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> +  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
> +
> +  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> +  mg_send(c, opts->topic.buf, opts->topic.len);
> +  mg_send(c, &qos_, sizeof(qos_));
> +}
> +
> +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> +                  struct mg_mqtt_message *m) {
> +  uint8_t lc = 0, *p, *end;
> +  uint32_t n = 0, len_len = 0;
> +
> +  memset(m, 0, sizeof(*m));
> +  m->dgram.buf = (char *) buf;
> +  if (len < 2) return MQTT_INCOMPLETE;
> +  m->cmd = (uint8_t) (buf[0] >> 4);
> +  m->qos = (buf[0] >> 1) & 3;
> +
> +  n = len_len = 0;
> +  p = (uint8_t *) buf + 1;
> +  while ((size_t) (p - buf) < len) {
> +    lc = *((uint8_t *) p++);
> +    n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> +    len_len++;
> +    if (!(lc & 0x80)) break;
> +    if (len_len >= 4) return MQTT_MALFORMED;
> +  }
> +  end = p + n;
> +  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> +  m->dgram.len = (size_t) (end - buf);
> +
> +  switch (m->cmd) {
> +    case MQTT_CMD_CONNACK:
> +      if (end - p < 2) return MQTT_MALFORMED;
> +      m->ack = p[1];
> +      break;
> +    case MQTT_CMD_PUBACK:
> +    case MQTT_CMD_PUBREC:
> +    case MQTT_CMD_PUBREL:
> +    case MQTT_CMD_PUBCOMP:
> +    case MQTT_CMD_SUBSCRIBE:
> +    case MQTT_CMD_SUBACK:
> +    case MQTT_CMD_UNSUBSCRIBE:
> +    case MQTT_CMD_UNSUBACK:
> +      if (p + 2 > end) return MQTT_MALFORMED;
> +      m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> +      p += 2;
> +      break;
> +    case MQTT_CMD_PUBLISH: {
> +      if (p + 2 > end) return MQTT_MALFORMED;
> +      m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> +      m->topic.buf = (char *) p + 2;
> +      p += 2 + m->topic.len;
> +      if (p > end) return MQTT_MALFORMED;
> +      if (m->qos > 0) {
> +        if (p + 2 > end) return MQTT_MALFORMED;
> +        m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> +        p += 2;
> +      }
> +      if (p > end) return MQTT_MALFORMED;
> +      if (version == 5 && p + 2 < end) {
> +        len_len =
> +            (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
> +        if (!len_len) return MQTT_MALFORMED;
> +        m->props_start = (size_t) (p + len_len - buf);
> +        p += len_len + m->props_size;
> +      }
> +      if (p > end) return MQTT_MALFORMED;
> +      m->data.buf = (char *) p;
> +      m->data.len = (size_t) (end - p);
> +      break;
> +    }
> +    default:
> +      break;
> +  }
> +  return MQTT_OK;
> +}
> +
> +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_READ) {
> +    for (;;) {
> +      uint8_t version = c->is_mqtt5 ? 5 : 4;
> +      struct mg_mqtt_message mm;
> +      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> +      if (rc == MQTT_MALFORMED) {
> +        MG_ERROR(("%lu MQTT malformed message", c->id));
> +        c->is_closing = 1;
> +        break;
> +      } else if (rc == MQTT_OK) {
> +        MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> +                    (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
> +        switch (mm.cmd) {
> +          case MQTT_CMD_CONNACK:
> +            mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> +            if (mm.ack == 0) {
> +              MG_DEBUG(("%lu Connected", c->id));
> +            } else {
> +              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> +              c->is_closing = 1;
> +            }
> +            break;
> +          case MQTT_CMD_PUBLISH: {
> +            /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> +                      mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
> +            if (mm.qos > 0) {
> +              uint16_t id = mg_ntohs(mm.id);
> +              uint32_t remaining_len = sizeof(id);
> +              if (c->is_mqtt5) remaining_len += 2;  // 3.4.2
> +
> +              mg_mqtt_send_header(
> +                  c,
> +                  (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
> +                  0, remaining_len);
> +              mg_send(c, &id, sizeof(id));
> +
> +              if (c->is_mqtt5) {
> +                uint16_t zero = 0;
> +                mg_send(c, &zero, sizeof(zero));
> +              }
> +            }
> +            mg_call(c, MG_EV_MQTT_MSG, &mm);  // let the app handle qos stuff
> +            break;
> +          }
> +          case MQTT_CMD_PUBREC: {  // MQTT5: 3.5.2-1 TODO(): variable header rc
> +            uint16_t id = mg_ntohs(mm.id);
> +            uint32_t remaining_len = sizeof(id);  // MQTT5 3.6.2-1
> +            mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
> +            mg_send(c, &id, sizeof(id));  // MQTT5 3.6.1-1, flags = 2
> +            break;
> +          }
> +          case MQTT_CMD_PUBREL: {  // MQTT5: 3.6.2-1 TODO(): variable header rc
> +            uint16_t id = mg_ntohs(mm.id);
> +            uint32_t remaining_len = sizeof(id);  // MQTT5 3.7.2-1
> +            mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
> +            mg_send(c, &id, sizeof(id));
> +            break;
> +          }
> +        }
> +        mg_call(c, MG_EV_MQTT_CMD, &mm);
> +        mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> +      } else {
> +        break;
> +      }
> +    }
> +  }
> +  (void) ev_data;
> +}
> +
> +void mg_mqtt_ping(struct mg_connection *nc) {
> +  mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> +}
> +
> +void mg_mqtt_pong(struct mg_connection *nc) {
> +  mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> +}
> +
> +void mg_mqtt_disconnect(struct mg_connection *c,
> +                        const struct mg_mqtt_opts *opts) {
> +  size_t len = 0;
> +  if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props);
> +  mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
> +
> +  if (c->is_mqtt5) {
> +    uint8_t zero = 0;
> +    mg_send(c, &zero, sizeof(zero));  // reason code
> +    mg_send_mqtt_properties(c, opts->props, opts->num_props);
> +  }
> +}
> +
> +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
> +                                      const struct mg_mqtt_opts *opts,
> +                                      mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> +  if (c != NULL) {
> +    struct mg_mqtt_opts empty;
> +    memset(&empty, 0, sizeof(empty));
> +    mg_mqtt_login(c, opts == NULL ? &empty : opts);
> +    c->pfn = mqtt_cb;
> +  }
> +  return c;
> +}
> +
> +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
> +                                     mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> +  if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> +  return c;
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/net.c"
> +#endif
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) {
> +  size_t old = c->send.len;
> +  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> +  return c->send.len - old;
> +}
> +
> +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> +  size_t len = 0;
> +  va_list ap;
> +  va_start(ap, fmt);
> +  len = mg_vprintf(c, fmt, &ap);
> +  va_end(ap);
> +  return len;
> +}
> +
> +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> +  uint32_t localhost = mg_htonl(0x7f000001);
> +  if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
> +  memcpy(addr->ip, &localhost, sizeof(uint32_t));
> +  addr->is_ip6 = false;
> +  return true;
> +}
> +
> +static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> +  if (str.len > 0) return false;
> +  memset(addr->ip, 0, sizeof(addr->ip));
> +  addr->is_ip6 = false;
> +  return true;
> +}
> +
> +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> +  uint8_t data[4] = {0, 0, 0, 0};
> +  size_t i, num_dots = 0;
> +  for (i = 0; i < str.len; i++) {
> +    if (str.buf[i] >= '0' && str.buf[i] <= '9') {
> +      int octet = data[num_dots] * 10 + (str.buf[i] - '0');
> +      if (octet > 255) return false;
> +      data[num_dots] = (uint8_t) octet;
> +    } else if (str.buf[i] == '.') {
> +      if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false;
> +      num_dots++;
> +    } else {
> +      return false;
> +    }
> +  }
> +  if (num_dots != 3 || str.buf[i - 1] == '.') return false;
> +  memcpy(&addr->ip, data, sizeof(data));
> +  addr->is_ip6 = false;
> +  return true;
> +}
> +
> +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> +  int i;
> +  uint32_t ipv4;
> +  if (str.len < 14) return false;
> +  if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false;
> +  for (i = 2; i < 6; i++) {
> +    if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
> +  }
> +  // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
> +  if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false;
> +  memcpy(&ipv4, addr->ip, sizeof(ipv4));
> +  memset(addr->ip, 0, sizeof(addr->ip));
> +  addr->ip[10] = addr->ip[11] = 255;
> +  memcpy(&addr->ip[12], &ipv4, 4);
> +  addr->is_ip6 = true;
> +  return true;
> +}
> +
> +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> +  size_t i, j = 0, n = 0, dc = 42;
> +  addr->scope_id = 0;
> +  if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
> +  if (mg_v4mapped(str, addr)) return true;
> +  for (i = 0; i < str.len; i++) {
> +    if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
> +        (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
> +        (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
> +      unsigned long val;  // TODO(): This loops on chars, refactor
> +      if (i > j + 3) return false;
> +      // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j]));
> +      mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val));
> +      addr->ip[n] = (uint8_t) ((val >> 8) & 255);
> +      addr->ip[n + 1] = (uint8_t) (val & 255);
> +    } else if (str.buf[i] == ':') {
> +      j = i + 1;
> +      if (i > 0 && str.buf[i - 1] == ':') {
> +        dc = n;  // Double colon
> +        if (i > 1 && str.buf[i - 2] == ':') return false;
> +      } else if (i > 0) {
> +        n += 2;
> +      }
> +      if (n > 14) return false;
> +      addr->ip[n] = addr->ip[n + 1] = 0;  // For trailing ::
> +    } else if (str.buf[i] == '%') {       // Scope ID, last in string
> +      return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10,
> +                           &addr->scope_id, sizeof(uint8_t));
> +    } else {
> +      return false;
> +    }
> +  }
> +  if (n < 14 && dc == 42) return false;
> +  if (n < 14) {
> +    memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
> +    memset(&addr->ip[dc], 0, 14 - n);
> +  }
> +
> +  addr->is_ip6 = true;
> +  return true;
> +}
> +
> +bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> +  // MG_INFO(("[%.*s]", (int) str.len, str.buf));
> +  return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
> +         mg_aton6(str, addr);
> +}
> +
> +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> +  struct mg_connection *c =
> +      (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
> +  if (c != NULL) {
> +    c->mgr = mgr;
> +    c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
> +    c->id = ++mgr->nextid;
> +    MG_PROF_INIT(c);
> +  }
> +  return c;
> +}
> +
> +void mg_close_conn(struct mg_connection *c) {
> +  mg_resolve_cancel(c);  // Close any pending DNS query
> +  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> +  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> +  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> +  // Order of operations is important. `MG_EV_CLOSE` event must be fired
> +  // before we deallocate received data, see #1331
> +  mg_call(c, MG_EV_CLOSE, NULL);
> +  MG_DEBUG(("%lu %ld closed", c->id, c->fd));
> +  MG_PROF_DUMP(c);
> +  MG_PROF_FREE(c);
> +
> +  mg_tls_free(c);
> +  mg_iobuf_free(&c->recv);
> +  mg_iobuf_free(&c->send);
> +  mg_iobuf_free(&c->rtls);
> +  mg_bzero((unsigned char *) c, sizeof(*c));
> +  free(c);
> +}
> +
> +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
> +                                 mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = NULL;
> +  if (url == NULL || url[0] == '\0') {
> +    MG_ERROR(("null url"));
> +  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> +    MG_ERROR(("OOM"));
> +  } else {
> +    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> +    c->is_udp = (strncmp(url, "udp:", 4) == 0);
> +    c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> +    c->fn = fn;
> +    c->is_client = true;
> +    c->fn_data = fn_data;
> +    MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> +    mg_call(c, MG_EV_OPEN, (void *) url);
> +    mg_resolve(c, url);
> +  }
> +  return c;
> +}
> +
> +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
> +                                mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = NULL;
> +  if ((c = mg_alloc_conn(mgr)) == NULL) {
> +    MG_ERROR(("OOM %s", url));
> +  } else if (!mg_open_listener(c, url)) {
> +    MG_ERROR(("Failed: %s, errno %d", url, errno));
> +    MG_PROF_FREE(c);
> +    free(c);
> +    c = NULL;
> +  } else {
> +    c->is_listening = 1;
> +    c->is_udp = strncmp(url, "udp:", 4) == 0;
> +    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> +    c->fn = fn;
> +    c->fn_data = fn_data;
> +    mg_call(c, MG_EV_OPEN, NULL);
> +    if (mg_url_is_ssl(url)) c->is_tls = 1;  // Accepted connection must
> +    MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> +  }
> +  return c;
> +}
> +
> +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> +                                mg_event_handler_t fn, void *fn_data) {
> +  struct mg_connection *c = mg_alloc_conn(mgr);
> +  if (c != NULL) {
> +    c->fd = (void *) (size_t) fd;
> +    c->fn = fn;
> +    c->fn_data = fn_data;
> +    MG_EPOLL_ADD(c);
> +    mg_call(c, MG_EV_OPEN, NULL);
> +    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> +  }
> +  return c;
> +}
> +
> +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
> +                              unsigned flags, void (*fn)(void *), void *arg) {
> +  struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> +  if (t != NULL) {
> +    mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> +    t->id = mgr->timerid++;
> +  }
> +  return t;
> +}
> +
> +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> +  if (c->rtls.len == 0) return MG_IO_WAIT;
> +  if (len > c->rtls.len) len = c->rtls.len;
> +  memcpy(buf, c->rtls.buf, len);
> +  mg_iobuf_del(&c->rtls, 0, len);
> +  return (long) len;
> +}
> +
> +void mg_mgr_free(struct mg_mgr *mgr) {
> +  struct mg_connection *c;
> +  struct mg_timer *tmp, *t = mgr->timers;
> +  while (t != NULL) tmp = t->next, free(t), t = tmp;
> +  mgr->timers = NULL;  // Important. Next call to poll won't touch timers
> +  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> +  mg_mgr_poll(mgr, 0);
> +#if MG_ENABLE_FREERTOS_TCP
> +  FreeRTOS_DeleteSocketSet(mgr->ss);
> +#endif
> +  MG_DEBUG(("All connections closed"));
> +#if MG_ENABLE_EPOLL
> +  if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> +#endif
> +  mg_tls_ctx_free(mgr);
> +}
> +
> +void mg_mgr_init(struct mg_mgr *mgr) {
> +  memset(mgr, 0, sizeof(*mgr));
> +#if MG_ENABLE_EPOLL
> +  if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
> +    MG_ERROR(("epoll_create1 errno %d", errno));
> +#else
> +  mgr->epoll_fd = -1;
> +#endif
> +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> +  // clang-format off
> +  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> +  // clang-format on
> +#elif MG_ENABLE_FREERTOS_TCP
> +  mgr->ss = FreeRTOS_CreateSocketSet();
> +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> +  // Ignore SIGPIPE signal, so if client cancels the request, it
> +  // won't kill the whole process.
> +  signal(SIGPIPE, SIG_IGN);
> +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
> +  MG_TCPIP_DRIVER_INIT(mgr);
> +#endif
> +  mgr->pipe = MG_INVALID_SOCKET;
> +  mgr->dnstimeout = 3000;
> +  mgr->dns4.url = "udp://8.8.8.8:53";
> +  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> +  mg_tls_ctx_init(mgr);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/net_builtin.c"
> +#endif
> +
> +
> +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> +#define MG_EPHEMERAL_PORT_BASE 32768
> +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> +
> +#ifndef MIP_TCP_KEEPALIVE_MS
> +#define MIP_TCP_KEEPALIVE_MS 45000  // TCP keep-alive period, ms
> +#endif
> +
> +#define MIP_TCP_ACK_MS 150    // Timeout for ACKing
> +#define MIP_TCP_ARP_MS 100    // Timeout for ARP response
> +#define MIP_TCP_SYN_MS 15000  // Timeout for connection establishment
> +#define MIP_TCP_FIN_MS 1000   // Timeout for closing connection
> +#define MIP_TCP_WIN 6000      // TCP window size
> +
> +struct connstate {
> +  uint32_t seq, ack;           // TCP seq/ack counters
> +  uint64_t timer;              // TCP keep-alive / ACK timer
> +  uint32_t acked;              // Last ACK-ed number
> +  size_t unacked;              // Not acked bytes
> +  uint8_t mac[6];              // Peer MAC address
> +  uint8_t ttype;               // Timer type. 0: ack, 1: keep-alive
> +#define MIP_TTYPE_KEEPALIVE 0  // Connection is idle for long, send keepalive
> +#define MIP_TTYPE_ACK 1        // Peer sent us data, we have to ack it soon
> +#define MIP_TTYPE_ARP 2        // ARP resolve sent, waiting for response
> +#define MIP_TTYPE_SYN 3        // SYN sent, waiting for response
> +#define MIP_TTYPE_FIN 4  // FIN sent, waiting until terminating the connection
> +  uint8_t tmiss;         // Number of keep-alive misses
> +  struct mg_iobuf raw;   // For TLS only. Incoming raw data
> +};
> +
> +#pragma pack(push, 1)
> +
> +struct lcp {
> +  uint8_t addr, ctrl, proto[2], code, id, len[2];
> +};
> +
> +struct eth {
> +  uint8_t dst[6];  // Destination MAC address
> +  uint8_t src[6];  // Source MAC address
> +  uint16_t type;   // Ethernet type
> +};
> +
> +struct ip {
> +  uint8_t ver;    // Version
> +  uint8_t tos;    // Unused
> +  uint16_t len;   // Length
> +  uint16_t id;    // Unused
> +  uint16_t frag;  // Fragmentation
> +#define IP_FRAG_OFFSET_MSK 0xFF1F
> +#define IP_MORE_FRAGS_MSK 0x20
> +  uint8_t ttl;    // Time to live
> +  uint8_t proto;  // Upper level protocol
> +  uint16_t csum;  // Checksum
> +  uint32_t src;   // Source IP
> +  uint32_t dst;   // Destination IP
> +};
> +
> +struct ip6 {
> +  uint8_t ver;      // Version
> +  uint8_t opts[3];  // Options
> +  uint16_t len;     // Length
> +  uint8_t proto;    // Upper level protocol
> +  uint8_t ttl;      // Time to live
> +  uint8_t src[16];  // Source IP
> +  uint8_t dst[16];  // Destination IP
> +};
> +
> +struct icmp {
> +  uint8_t type;
> +  uint8_t code;
> +  uint16_t csum;
> +};
> +
> +struct arp {
> +  uint16_t fmt;    // Format of hardware address
> +  uint16_t pro;    // Format of protocol address
> +  uint8_t hlen;    // Length of hardware address
> +  uint8_t plen;    // Length of protocol address
> +  uint16_t op;     // Operation
> +  uint8_t sha[6];  // Sender hardware address
> +  uint32_t spa;    // Sender protocol address
> +  uint8_t tha[6];  // Target hardware address
> +  uint32_t tpa;    // Target protocol address
> +};
> +
> +struct tcp {
> +  uint16_t sport;  // Source port
> +  uint16_t dport;  // Destination port
> +  uint32_t seq;    // Sequence number
> +  uint32_t ack;    // Acknowledgement number
> +  uint8_t off;     // Data offset
> +  uint8_t flags;   // TCP flags
> +#define TH_FIN 0x01
> +#define TH_SYN 0x02
> +#define TH_RST 0x04
> +#define TH_PUSH 0x08
> +#define TH_ACK 0x10
> +#define TH_URG 0x20
> +#define TH_ECE 0x40
> +#define TH_CWR 0x80
> +  uint16_t win;   // Window
> +  uint16_t csum;  // Checksum
> +  uint16_t urp;   // Urgent pointer
> +};
> +
> +struct udp {
> +  uint16_t sport;  // Source port
> +  uint16_t dport;  // Destination port
> +  uint16_t len;    // UDP length
> +  uint16_t csum;   // UDP checksum
> +};
> +
> +struct dhcp {
> +  uint8_t op, htype, hlen, hops;
> +  uint32_t xid;
> +  uint16_t secs, flags;
> +  uint32_t ciaddr, yiaddr, siaddr, giaddr;
> +  uint8_t hwaddr[208];
> +  uint32_t magic;
> +  uint8_t options[32];
> +};
> +
> +#pragma pack(pop)
> +
> +struct pkt {
> +  struct mg_str raw;  // Raw packet data
> +  struct mg_str pay;  // Payload data
> +  struct eth *eth;
> +  struct llc *llc;
> +  struct arp *arp;
> +  struct ip *ip;
> +  struct ip6 *ip6;
> +  struct icmp *icmp;
> +  struct tcp *tcp;
> +  struct udp *udp;
> +  struct dhcp *dhcp;
> +};
> +
> +static void send_syn(struct mg_connection *c);
> +
> +static void mkpay(struct pkt *pkt, void *p) {
> +  pkt->pay =
> +      mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p));
> +}
> +
> +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> +  size_t i;
> +  const uint8_t *p = (const uint8_t *) buf;
> +  for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
> +  return sum;
> +}
> +
> +static uint16_t csumfin(uint32_t sum) {
> +  while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> +  return mg_htons(~sum & 0xffff);
> +}
> +
> +static uint16_t ipcsum(const void *buf, size_t len) {
> +  uint32_t sum = csumup(0, buf, len);
> +  return csumfin(sum);
> +}
> +
> +static void settmout(struct mg_connection *c, uint8_t type) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  unsigned n = type == MIP_TTYPE_ACK   ? MIP_TCP_ACK_MS
> +               : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
> +               : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
> +               : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
> +                                       : MIP_TCP_KEEPALIVE_MS;
> +  s->timer = ifp->now + n;
> +  s->ttype = type;
> +  MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> +}
> +
> +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
> +  size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
> +  if (n == len) ifp->nsent++;
> +  return n;
> +}
> +
> +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
> +  struct eth *eth = (struct eth *) ifp->tx.buf;
> +  struct arp *arp = (struct arp *) (eth + 1);
> +  memset(eth->dst, 255, sizeof(eth->dst));
> +  memcpy(eth->src, ifp->mac, sizeof(eth->src));
> +  eth->type = mg_htons(0x806);
> +  memset(arp, 0, sizeof(*arp));
> +  arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> +  arp->plen = 4;
> +  arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> +  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> +  ether_output(ifp, PDIFF(eth, arp + 1));
> +}
> +
> +static void onstatechange(struct mg_tcpip_if *ifp) {
> +  if (ifp->state == MG_TCPIP_STATE_READY) {
> +    MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
> +    MG_INFO(("       GW: %M", mg_print_ip4, &ifp->gw));
> +    MG_INFO(("      MAC: %M", mg_print_mac, &ifp->mac));
> +    arp_ask(ifp, ifp->gw);
> +  } else if (ifp->state == MG_TCPIP_STATE_UP) {
> +    MG_ERROR(("Link up"));
> +    srand((unsigned int) mg_millis());
> +  } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
> +    MG_ERROR(("Link down"));
> +  }
> +}
> +
> +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> +                        uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
> +                        size_t plen) {
> +  struct eth *eth = (struct eth *) ifp->tx.buf;
> +  struct ip *ip = (struct ip *) (eth + 1);
> +  memcpy(eth->dst, mac_dst, sizeof(eth->dst));
> +  memcpy(eth->src, ifp->mac, sizeof(eth->src));  // Use our MAC
> +  eth->type = mg_htons(0x800);
> +  memset(ip, 0, sizeof(*ip));
> +  ip->ver = 0x45;   // Version 4, header length 5 words
> +  ip->frag = 0x40;  // Don't fragment
> +  ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> +  ip->ttl = 64;
> +  ip->proto = proto;
> +  ip->src = ip_src;
> +  ip->dst = ip_dst;
> +  ip->csum = ipcsum(ip, sizeof(*ip));
> +  return ip;
> +}
> +
> +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
> +                   uint16_t sport, uint32_t ip_dst, uint16_t dport,
> +                   const void *buf, size_t len) {
> +  struct ip *ip =
> +      tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
> +  struct udp *udp = (struct udp *) (ip + 1);
> +  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> +  udp->sport = sport;
> +  udp->dport = dport;
> +  udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> +  udp->csum = 0;
> +  uint32_t cs = csumup(0, udp, sizeof(*udp));
> +  cs = csumup(cs, buf, len);
> +  cs = csumup(cs, &ip->src, sizeof(ip->src));
> +  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> +  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> +  udp->csum = csumfin(cs);
> +  memmove(udp + 1, buf, len);
> +  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> +  ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len);
> +}
> +
> +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
> +                    uint32_t ip_dst, uint8_t *opts, size_t optslen,
> +                    bool ciaddr) {
> +  // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
> +  struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> +  dhcp.magic = mg_htonl(0x63825363);
> +  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> +  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> +  memcpy(&dhcp.options, opts, optslen);
> +  if (ciaddr) dhcp.ciaddr = ip_src;
> +  tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
> +         sizeof(dhcp));
> +}
> +
> +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> +
> +// RFC-2131 #4.3.6, #4.4.1
> +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req,
> +                                uint32_t ip_srv) {
> +  uint8_t opts[] = {
> +      53, 1, 3,                 // Type: DHCP request
> +      55, 2, 1,   3,            // GW and mask
> +      12, 3, 'm', 'i', 'p',     // Host name: "mip"
> +      54, 4, 0,   0,   0,   0,  // DHCP server ID
> +      50, 4, 0,   0,   0,   0,  // Requested IP
> +      255                       // End of options
> +  };
> +  memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
> +  memcpy(opts + 20, &ip_req, sizeof(ip_req));
> +  tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
> +  MG_DEBUG(("DHCP req sent"));
> +}
> +
> +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
> +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> +                               uint32_t ip_src, uint32_t ip_dst) {
> +  uint8_t opts[] = {
> +      53, 1, 3,  // Type: DHCP request
> +      255        // End of options
> +  };
> +  tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
> +  MG_DEBUG(("DHCP req sent"));
> +}
> +
> +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
> +  uint8_t opts[] = {
> +      53, 1, 1,     // Type: DHCP discover
> +      55, 2, 1, 3,  // Parameters: ip, mask
> +      255           // End of options
> +  };
> +  tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
> +  MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac));
> +}
> +
> +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
> +                                     bool lsn) {
> +  struct mg_connection *c = NULL;
> +  for (c = mgr->conns; c != NULL; c = c->next) {
> +    if (c->is_arplooking && pkt->arp &&
> +        memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
> +      break;
> +    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
> +    if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> +        lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> +      break;
> +  }
> +  return c;
> +}
> +
> +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> +    // ARP request. Make a response, then send
> +    // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
> +    //          &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
> +    struct eth *eth = (struct eth *) ifp->tx.buf;
> +    struct arp *arp = (struct arp *) (eth + 1);
> +    memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> +    memcpy(eth->src, ifp->mac, sizeof(eth->src));
> +    eth->type = mg_htons(0x806);
> +    *arp = *pkt->arp;
> +    arp->op = mg_htons(2);
> +    memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> +    memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> +    arp->tpa = pkt->arp->spa;
> +    arp->spa = ifp->ip;
> +    MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac,
> +              &ifp->mac));
> +    ether_output(ifp, PDIFF(eth, arp + 1));
> +  } else if (pkt->arp->op == mg_htons(2)) {
> +    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
> +    if (pkt->arp->spa == ifp->gw) {
> +      // Got response for the GW ARP request. Set ifp->gwmac
> +      memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
> +    } else {
> +      struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> +      if (c != NULL && c->is_arplooking) {
> +        struct connstate *s = (struct connstate *) (c + 1);
> +        memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
> +        MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip,
> +                  mg_print_mac, s->mac));
> +        c->is_arplooking = 0;
> +        send_syn(c);
> +        settmout(c, MIP_TTYPE_SYN);
> +      }
> +    }
> +  }
> +}
> +
> +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  // MG_DEBUG(("ICMP %d", (int) len));
> +  if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
> +    size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
> +    size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> +    if (plen > space) plen = space;
> +    struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
> +                          sizeof(struct icmp) + plen);
> +    struct icmp *icmp = (struct icmp *) (ip + 1);
> +    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
> +    memcpy(icmp + 1, pkt->pay.buf, plen);  // Copy RX payload to TX
> +    icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> +    ether_output(ifp, hlen + plen);
> +  }
> +}
> +
> +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
> +  uint8_t msgtype = 0, state = ifp->state;
> +  // perform size check first, then access fields
> +  uint8_t *p = pkt->dhcp->options,
> +          *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> +  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> +  if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
> +  while (p + 1 < end && p[0] != 255) {  // Parse options RFC-1533 #9
> +    if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) {  // Mask
> +      memcpy(&mask, p + 2, sizeof(mask));
> +    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) {  // GW
> +      memcpy(&gw, p + 2, sizeof(gw));
> +      ip = pkt->dhcp->yiaddr;
> +    } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) {  // Lease
> +      memcpy(&lease, p + 2, sizeof(lease));
> +      lease = mg_ntohl(lease);
> +    } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) {  // Msg Type
> +      msgtype = p[2];
> +    }
> +    p += p[1] + 2;
> +  }
> +  // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
> +  if (msgtype == 6 && ifp->ip == ip) {  // DHCPNACK, release IP
> +    ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
> +  } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw &&
> +             lease) {  // DHCPOFFER
> +    // select IP, (4.4.1) (fallback to IP source addr on foul play)
> +    tx_dhcp_request_sel(ifp, ip,
> +                        pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
> +    ifp->state = MG_TCPIP_STATE_REQ;  // REQUESTING state
> +  } else if (msgtype == 5) {          // DHCPACK
> +    if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) {  // got an IP
> +      ifp->lease_expire = ifp->now + lease * 1000;
> +      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
> +      // assume DHCP server = router until ARP resolves
> +      memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> +      ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> +      ifp->state = MG_TCPIP_STATE_READY;  // BOUND state
> +      uint64_t rand;
> +      mg_random(&rand, sizeof(rand));
> +      srand((unsigned int) (rand + mg_millis()));
> +    } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) {  // renew
> +      ifp->lease_expire = ifp->now + lease * 1000;
> +      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
> +    }  // TODO(): accept provided T1/T2 and store server IP for renewal (4.4)
> +  }
> +  if (ifp->state != state) onstatechange(ifp);
> +}
> +
> +// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  uint8_t op = 0, *p = pkt->dhcp->options,
> +          *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> +  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> +  // struct dhcp *req = pkt->dhcp;
> +  struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> +  res.yiaddr = ifp->ip;
> +  ((uint8_t *) (&res.yiaddr))[3]++;                // Offer our IP + 1
> +  while (p + 1 < end && p[0] != 255) {             // Parse options
> +    if (p[0] == 53 && p[1] == 1 && p + 2 < end) {  // Message type
> +      op = p[2];
> +    }
> +    p += p[1] + 2;
> +  }
> +  if (op == 1 || op == 3) {         // DHCP Discover or DHCP Request
> +    uint8_t msg = op == 1 ? 2 : 5;  // Message type: DHCP OFFER or DHCP ACK
> +    uint8_t opts[] = {
> +        53, 1, msg,                 // Message type
> +        1,  4, 0,   0,   0,   0,    // Subnet mask
> +        54, 4, 0,   0,   0,   0,    // Server ID
> +        12, 3, 'm', 'i', 'p',       // Host name: "mip"
> +        51, 4, 255, 255, 255, 255,  // Lease time
> +        255                         // End of options
> +    };
> +    memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> +    memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> +    memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> +    memcpy(&res.options, opts, sizeof(opts));
> +    res.magic = pkt->dhcp->magic;
> +    res.xid = pkt->dhcp->xid;
> +    if (ifp->enable_get_gateway) {
> +      ifp->gw = res.yiaddr;
> +      memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> +    }
> +    tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
> +           op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
> +  }
> +}
> +
> +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> +  if (c == NULL) {
> +    // No UDP listener on this port. Should send ICMP, but keep silent.
> +  } else {
> +    c->rem.port = pkt->udp->sport;
> +    memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> +    if (c->recv.len >= MG_MAX_RECV_SIZE) {
> +      mg_error(c, "max_recv_buf_size reached");
> +    } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> +               !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> +      mg_error(c, "oom");
> +    } else {
> +      memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
> +      c->recv.len += pkt->pay.len;
> +      mg_call(c, MG_EV_READ, &pkt->pay.len);
> +    }
> +  }
> +}
> +
> +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
> +                     uint8_t flags, uint16_t sport, uint16_t dport,
> +                     uint32_t seq, uint32_t ack, const void *buf, size_t len) {
> +#if 0
> +  uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0};  // MSS = 1460, SACK permitted
> +  if (flags & TH_SYN) {
> +    // Handshake? Set MSS
> +    buf = opts;
> +    len = sizeof(opts);
> +  }
> +#endif
> +  struct ip *ip =
> +      tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
> +  struct tcp *tcp = (struct tcp *) (ip + 1);
> +  memset(tcp, 0, sizeof(*tcp));
> +  if (buf != NULL && len) memmove(tcp + 1, buf, len);
> +  tcp->sport = sport;
> +  tcp->dport = dport;
> +  tcp->seq = seq;
> +  tcp->ack = ack;
> +  tcp->flags = flags;
> +  tcp->win = mg_htons(MIP_TCP_WIN);
> +  tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> +  // if (flags & TH_SYN) tcp->off = 0x70;  // Handshake? header size 28 bytes
> +
> +  uint32_t cs = 0;
> +  uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> +  uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
> +  cs = csumup(cs, tcp, n);
> +  cs = csumup(cs, &ip->src, sizeof(ip->src));
> +  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> +  cs = csumup(cs, pseudo, sizeof(pseudo));
> +  tcp->csum = csumfin(cs);
> +  MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
> +              mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
> +              mg_ntohs(tcp->dport), tcp->flags, len));
> +  // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
> +  return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
> +}
> +
> +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
> +                         uint8_t flags, uint32_t seq, const void *buf,
> +                         size_t len) {
> +  uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> +  return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport,
> +                pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
> +                buf, len);
> +}
> +
> +static struct mg_connection *accept_conn(struct mg_connection *lsn,
> +                                         struct pkt *pkt) {
> +  struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> +  if (c == NULL) {
> +    MG_ERROR(("OOM"));
> +    return NULL;
> +  }
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
> +  memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> +  settmout(c, MIP_TTYPE_KEEPALIVE);
> +  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> +  c->rem.port = pkt->tcp->sport;
> +  MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
> +  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> +  c->is_accepted = 1;
> +  c->is_hexdumping = lsn->is_hexdumping;
> +  c->pfn = lsn->pfn;
> +  c->loc = lsn->loc;
> +  c->pfn_data = lsn->pfn_data;
> +  c->fn = lsn->fn;
> +  c->fn_data = lsn->fn_data;
> +  mg_call(c, MG_EV_OPEN, NULL);
> +  mg_call(c, MG_EV_ACCEPT, NULL);
> +  return c;
> +}
> +
> +static size_t trim_len(struct mg_connection *c, size_t len) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8;
> +  size_t max_headers_len =
> +      eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
> +  size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len;
> +
> +  // If the frame exceeds the available buffer, trim the length
> +  if (len + max_headers_len > ifp->tx.len) {
> +    len = ifp->tx.len - max_headers_len;
> +  }
> +  // Ensure the MTU isn't lower than the minimum allowed value
> +  if (ifp->mtu < min_mtu) {
> +    MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
> +    ifp->mtu = (uint16_t) min_mtu;
> +  }
> +  // If the total packet size exceeds the MTU, trim the length
> +  if (len + max_headers_len - eth_h_len > ifp->mtu) {
> +    len = ifp->mtu - max_headers_len + eth_h_len;
> +    if (c->is_udp) {
> +      MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
> +    }
> +  }
> +
> +  return len;
> +}
> +
> +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  uint32_t dst_ip = *(uint32_t *) c->rem.ip;
> +  len = trim_len(c, len);
> +  if (c->is_udp) {
> +    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len);
> +  } else {
> +    size_t sent =
> +        tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
> +               mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
> +    if (sent == 0) {
> +      return MG_IO_WAIT;
> +    } else if (sent == (size_t) -1) {
> +      return MG_IO_ERR;
> +    } else {
> +      s->seq += (uint32_t) len;
> +      if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> +    }
> +  }
> +  return (long) len;
> +}
> +
> +static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) {
> +  long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> +  if (n == MG_IO_ERR) {
> +    mg_error(c, "TLS recv error");
> +  } else if (n > 0) {
> +    // Decrypted successfully - trigger MG_EV_READ
> +    io->len += (size_t) n;
> +    mg_call(c, MG_EV_READ, &n);
> +  }
> +}
> +
> +static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
> +  uint32_t seq = mg_ntohl(pkt->tcp->seq);
> +  uint32_t rem_ip;
> +  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +  if (pkt->tcp->flags & TH_FIN) {
> +    // If we initiated the closure, we reply with ACK upon receiving FIN
> +    // If we didn't initiate it, we reply with FIN as part of the normal TCP
> +    // closure process
> +    uint8_t flags = TH_ACK;
> +    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
> +    if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
> +      if (s->seq == mg_htonl(pkt->tcp->ack)) {  // Simultaneous closure ?
> +        s->seq++;                               // Yes. Increment our SEQ
> +      } else {                                  // Otherwise,
> +        s->seq = mg_htonl(pkt->tcp->ack);       // Set to peer's ACK
> +      }
> +    } else {
> +      flags |= TH_FIN;
> +      c->is_draining = 1;
> +      settmout(c, MIP_TTYPE_FIN);
> +    }
> +    tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
> +           c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> +  } else if (pkt->pay.len == 0) {
> +    // TODO(cpq): handle this peer's ACK
> +  } else if (seq != s->ack) {
> +    uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> +    if (s->ack == ack) {
> +      MG_VERBOSE(("ignoring duplicate pkt"));
> +    } else {
> +      MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
> +      tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
> +             c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
> +             0);
> +    }
> +  } else if (io->size - io->len < pkt->pay.len &&
> +             !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> +    mg_error(c, "oom");
> +  } else {
> +    // Copy TCP payload into the IO buffer. If the connection is plain text,
> +    // we copy to c->recv. If the connection is TLS, this data is encrypted,
> +    // therefore we copy that encrypted data to the c->rtls iobuffer instead,
> +    // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
> +    // call back mg_io_recv() which grabs raw data from c->rtls
> +    memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
> +    io->len += pkt->pay.len;
> +
> +    MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> +    // Advance ACK counter
> +    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> +    s->unacked += pkt->pay.len;
> +    // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
> +    if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
> +      // Send ACK immediately
> +      MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
> +      tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
> +             c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL,
> +             0);
> +      s->unacked = 0;
> +      s->acked = s->ack;
> +      if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
> +    } else {
> +      // if not already running, setup a timer to send an ACK later
> +      if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> +    }
> +
> +    if (c->is_tls && c->is_tls_hs) {
> +      mg_tls_handshake(c);
> +    } else if (c->is_tls) {
> +      // TLS connection. Make room for decrypted data in c->recv
> +      io = &c->recv;
> +      if (io->size - io->len < pkt->pay.len &&
> +          !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> +        mg_error(c, "oom");
> +      } else {
> +        // Decrypt data directly into c->recv
> +        handle_tls_recv(c, io);
> +      }
> +    } else {
> +      // Plain text connection, data is already in c->recv, trigger
> +      // MG_EV_READ
> +      mg_call(c, MG_EV_READ, &pkt->pay.len);
> +    }
> +  }
> +}
> +
> +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> +  struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
> +#if 0
> +  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
> +#endif
> +  if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) {
> +    s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
> +    tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> +    c->is_connecting = 0;  // Client connected
> +    settmout(c, MIP_TTYPE_KEEPALIVE);
> +    mg_call(c, MG_EV_CONNECT, NULL);  // Let user know
> +  } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) {
> +    // mg_hexdump(pkt->raw.buf, pkt->raw.len);
> +    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> +  } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> +    mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
> +  } else if (c != NULL) {
> +#if 0
> +    MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
> +              mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> +              mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> +    mg_hexdump(pkt->pay.buf, pkt->pay.len);
> +#endif
> +    s->tmiss = 0;                         // Reset missed keep-alive counter
> +    if (s->ttype == MIP_TTYPE_KEEPALIVE)  // Advance keep-alive timer
> +      settmout(c,
> +               MIP_TTYPE_KEEPALIVE);  // unless a former ACK timeout is pending
> +    read_conn(c, pkt);  // Override timer with ACK timeout if needed
> +  } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> +    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> +  } else if (pkt->tcp->flags & TH_RST) {
> +    if (c->is_accepted) mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
> +    // ignore RST if not connected
> +  } else if (pkt->tcp->flags & TH_SYN) {
> +    // Use peer's source port as ISN, in order to recognise the handshake
> +    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> +    tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> +  } else if (pkt->tcp->flags & TH_FIN) {
> +    tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> +  } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
> +    accept_conn(c, pkt);
> +  } else if (!c->is_accepted) {  // no peer
> +    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> +  } else {
> +    // MG_VERBOSE(("dropped silently.."));
> +  }
> +}
> +
> +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & IP_FRAG_OFFSET_MSK) {
> +    if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
> +    if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
> +    struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> +    if (c) mg_error(c, "Received fragmented packet");
> +  } else if (pkt->ip->proto == 1) {
> +    pkt->icmp = (struct icmp *) (pkt->ip + 1);
> +    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> +    mkpay(pkt, pkt->icmp + 1);
> +    rx_icmp(ifp, pkt);
> +  } else if (pkt->ip->proto == 17) {
> +    pkt->udp = (struct udp *) (pkt->ip + 1);
> +    if (pkt->pay.len < sizeof(*pkt->udp)) return;
> +    mkpay(pkt, pkt->udp + 1);
> +    MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
> +                mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
> +                mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
> +    if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
> +      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> +      mkpay(pkt, pkt->dhcp + 1);
> +      rx_dhcp_client(ifp, pkt);
> +    } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) {
> +      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> +      mkpay(pkt, pkt->dhcp + 1);
> +      rx_dhcp_server(ifp, pkt);
> +    } else {
> +      rx_udp(ifp, pkt);
> +    }
> +  } else if (pkt->ip->proto == 6) {
> +    pkt->tcp = (struct tcp *) (pkt->ip + 1);
> +    if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> +    mkpay(pkt, pkt->tcp + 1);
> +    uint16_t iplen = mg_ntohs(pkt->ip->len);
> +    uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
> +    if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> +    MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
> +                mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
> +                mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
> +    rx_tcp(ifp, pkt);
> +  }
> +}
> +
> +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> +  // MG_DEBUG(("IP %d", (int) len));
> +  if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> +    pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> +    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> +    mkpay(pkt, pkt->icmp + 1);
> +    rx_icmp(ifp, pkt);
> +  } else if (pkt->ip6->proto == 17) {
> +    pkt->udp = (struct udp *) (pkt->ip6 + 1);
> +    if (pkt->pay.len < sizeof(*pkt->udp)) return;
> +    // MG_DEBUG(("  UDP %u %u -> %u", len, mg_htons(udp->sport),
> +    // mg_htons(udp->dport)));
> +    mkpay(pkt, pkt->udp + 1);
> +  }
> +}
> +
> +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
> +  struct pkt pkt;
> +  memset(&pkt, 0, sizeof(pkt));
> +  pkt.raw.buf = (char *) buf;
> +  pkt.raw.len = len;
> +  pkt.eth = (struct eth *) buf;
> +  // mg_hexdump(buf, len > 16 ? 16: len);
> +  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
> +  if (ifp->enable_mac_check &&
> +      memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> +      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
> +    return;
> +  if (ifp->enable_crc32_check && len > 4) {
> +    len -= 4;  // TODO(scaprile): check on bigendian
> +    uint32_t crc = mg_crc32(0, (const char *) buf, len);
> +    if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return;
> +  }
> +  if (pkt.eth->type == mg_htons(0x806)) {
> +    pkt.arp = (struct arp *) (pkt.eth + 1);
> +    if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;  // Truncated
> +    rx_arp(ifp, &pkt);
> +  } else if (pkt.eth->type == mg_htons(0x86dd)) {
> +    pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> +    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;  // Truncated
> +    if ((pkt.ip6->ver >> 4) != 0x6) return;                         // Not IP
> +    mkpay(&pkt, pkt.ip6 + 1);
> +    rx_ip6(ifp, &pkt);
> +  } else if (pkt.eth->type == mg_htons(0x800)) {
> +    pkt.ip = (struct ip *) (pkt.eth + 1);
> +    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
> +    // Truncate frame to what IP header tells us
> +    if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
> +      pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> +    }
> +    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
> +    if ((pkt.ip->ver >> 4) != 4) return;                           // Not IP
> +    mkpay(&pkt, pkt.ip + 1);
> +    rx_ip(ifp, &pkt);
> +  } else {
> +    MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
> +    if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len);
> +  }
> +}
> +
> +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
> +  struct mg_connection *c;
> +  bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now);
> +  ifp->now = now;
> +
> +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> +  if (expired_1000ms) {
> +    const char *names[] = {"down", "up", "req", "ready"};
> +    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
> +             names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
> +             ifp->ndrop, ifp->nerr));
> +  }
> +#endif
> +  // Handle physical interface up/down status
> +  if (expired_1000ms && ifp->driver->up) {
> +    bool up = ifp->driver->up(ifp);
> +    bool current = ifp->state != MG_TCPIP_STATE_DOWN;
> +    if (up != current) {
> +      ifp->state = up == false               ? MG_TCPIP_STATE_DOWN
> +                   : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
> +                                             : MG_TCPIP_STATE_READY;
> +      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> +      onstatechange(ifp);
> +    }
> +    if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
> +  }
> +  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
> +
> +  // DHCP RFC-2131 (4.4)
> +  if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
> +    tx_dhcp_discover(ifp);  // INIT (4.4.1)
> +  } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
> +             ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
> +    if (ifp->now >= ifp->lease_expire) {
> +      ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
> +      onstatechange(ifp);
> +    } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
> +               ((ifp->now / 1000) % 60) == 0) {
> +      // hack: 30 min before deadline, try to rebind (4.3.6) every min
> +      tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
> +    }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
> +  }
> +
> +  // Read data from the network
> +  if (ifp->driver->rx != NULL) {  // Polling driver. We must call it
> +    size_t len =
> +        ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
> +    if (len > 0) {
> +      ifp->nrecv++;
> +      mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
> +    }
> +  } else {  // Interrupt-based driver. Fills recv queue itself
> +    char *buf;
> +    size_t len = mg_queue_next(&ifp->recv_queue, &buf);
> +    if (len > 0) {
> +      mg_tcpip_rx(ifp, buf, len);
> +      mg_queue_del(&ifp->recv_queue, len);
> +    }
> +  }
> +
> +  // Process timeouts
> +  for (c = ifp->mgr->conns; c != NULL; c = c->next) {
> +    if (c->is_udp || c->is_listening || c->is_resolving) continue;
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    uint32_t rem_ip;
> +    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +    if (now > s->timer) {
> +      if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
> +        MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
> +        tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> +               mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> +        s->acked = s->ack;
> +      } else if (s->ttype == MIP_TTYPE_ARP) {
> +        mg_error(c, "ARP timeout");
> +      } else if (s->ttype == MIP_TTYPE_SYN) {
> +        mg_error(c, "Connection timeout");
> +      } else if (s->ttype == MIP_TTYPE_FIN) {
> +        c->is_closing = 1;
> +        continue;
> +      } else {
> +        if (s->tmiss++ > 2) {
> +          mg_error(c, "keepalive");
> +        } else {
> +          MG_VERBOSE(("%lu keepalive", c->id));
> +          tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> +                 mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
> +        }
> +      }
> +
> +      settmout(c, MIP_TTYPE_KEEPALIVE);
> +    }
> +  }
> +}
> +
> +// This function executes in interrupt context, thus it should copy data
> +// somewhere fast. Note that newlib's malloc is not thread safe, thus use
> +// our lock-free queue with preallocated buffer to copy data and return asap
> +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) {
> +  char *p;
> +  if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
> +    memcpy(p, buf, len);
> +    mg_queue_add(&ifp->recv_queue, len);
> +    ifp->nrecv++;
> +  } else {
> +    ifp->ndrop++;
> +  }
> +}
> +
> +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
> +  // If MAC address is not set, make a random one
> +  if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
> +      ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
> +    ifp->mac[0] = 0x02;  // Locally administered, unicast
> +    mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
> +    MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac));
> +  }
> +
> +  if (ifp->driver->init && !ifp->driver->init(ifp)) {
> +    MG_ERROR(("driver init failed"));
> +  } else {
> +    size_t framesize = 1540;
> +    ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize;
> +    if (ifp->recv_queue.size == 0)
> +      ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
> +    ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
> +    ifp->timer_1000ms = mg_millis();
> +    mgr->priv = ifp;
> +    ifp->mgr = mgr;
> +    ifp->mtu = MG_TCPIP_MTU_DEFAULT;
> +    mgr->extraconnsize = sizeof(struct connstate);
> +    if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> +    memset(ifp->gwmac, 255, sizeof(ifp->gwmac));  // Set to broadcast
> +    mg_random(&ifp->eport, sizeof(ifp->eport));   // Random from 0 to 65535
> +    ifp->eport |= MG_EPHEMERAL_PORT_BASE;         // Random from
> +                                           // MG_EPHEMERAL_PORT_BASE to 65535
> +    if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
> +  }
> +}
> +
> +void mg_tcpip_free(struct mg_tcpip_if *ifp) {
> +  free(ifp->recv_queue.buf);
> +  free(ifp->tx.buf);
> +}
> +
> +static void send_syn(struct mg_connection *c) {
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  uint32_t rem_ip;
> +  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +  tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL,
> +         0);
> +}
> +
> +void mg_connect_resolved(struct mg_connection *c) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  uint32_t rem_ip;
> +  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +  c->is_resolving = 0;
> +  if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
> +  memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
> +  c->loc.port = mg_htons(ifp->eport++);
> +  MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
> +            &c->rem));
> +  mg_call(c, MG_EV_RESOLVE, NULL);
> +  if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
> +  } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) {
> +    // If we're in the same LAN, fire an ARP lookup.
> +    MG_DEBUG(("%lu ARP lookup...", c->id));
> +    arp_ask(ifp, rem_ip);
> +    settmout(c, MIP_TTYPE_ARP);
> +    c->is_arplooking = 1;
> +    c->is_connecting = 1;
> +  } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
> +    struct connstate *s = (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
> +    uint8_t mcastp[3] = {0x01, 0x00, 0x5E};              // multicast group
> +    memcpy(s->mac, mcastp, 3);
> +    memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3);  // 23 LSb
> +    s->mac[3] &= 0x7F;
> +  } else {
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
> +    if (c->is_udp) {
> +      mg_call(c, MG_EV_CONNECT, NULL);
> +    } else {
> +      send_syn(c);
> +      settmout(c, MIP_TTYPE_SYN);
> +      c->is_connecting = 1;
> +    }
> +  }
> +}
> +
> +bool mg_open_listener(struct mg_connection *c, const char *url) {
> +  c->loc.port = mg_htons(mg_url_port(url));
> +  return true;
> +}
> +
> +static void write_conn(struct mg_connection *c) {
> +  long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> +                       : mg_io_send(c, c->send.buf, c->send.len);
> +  if (len == MG_IO_ERR) {
> +    mg_error(c, "tx err");
> +  } else if (len > 0) {
> +    mg_iobuf_del(&c->send, 0, (size_t) len);
> +    mg_call(c, MG_EV_WRITE, &len);
> +  }
> +}
> +
> +static void init_closure(struct mg_connection *c) {
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  if (c->is_udp == false && c->is_listening == false &&
> +      c->is_connecting == false) {  // For TCP conns,
> +    struct mg_tcpip_if *ifp =
> +        (struct mg_tcpip_if *) c->mgr->priv;  // send TCP FIN
> +    uint32_t rem_ip;
> +    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +    tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
> +           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> +    settmout(c, MIP_TTYPE_FIN);
> +  }
> +}
> +
> +static void close_conn(struct mg_connection *c) {
> +  struct connstate *s = (struct connstate *) (c + 1);
> +  mg_iobuf_free(&s->raw);  // For TLS connections, release raw data
> +  mg_close_conn(c);
> +}
> +
> +static bool can_write(struct mg_connection *c) {
> +  return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
> +         c->is_tls_hs == 0 && c->is_arplooking == 0;
> +}
> +
> +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
> +  struct mg_connection *c, *tmp;
> +  uint64_t now = mg_millis();
> +  mg_timer_poll(&mgr->timers, now);
> +  if (ifp == NULL || ifp->driver == NULL) return;
> +  mg_tcpip_poll(ifp, now);
> +  for (c = mgr->conns; c != NULL; c = tmp) {
> +    tmp = c->next;
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    mg_call(c, MG_EV_POLL, &now);
> +    MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> +                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> +                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> +    if (c->is_tls && mg_tls_pending(c) > 0)
> +      handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
> +    if (can_write(c)) write_conn(c);
> +    if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN)
> +      init_closure(c);
> +    if (c->is_closing) close_conn(c);
> +  }
> +  (void) ms;
> +}
> +
> +bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> +  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> +  bool res = false;
> +  uint32_t rem_ip;
> +  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> +  if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
> +    mg_error(c, "net down");
> +  } else if (c->is_udp) {
> +    struct connstate *s = (struct connstate *) (c + 1);
> +    len = trim_len(c, len);  // Trimming length if necessary
> +    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
> +    res = true;
> +  } else {
> +    res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> +  }
> +  return res;
> +}
> +#endif  // MG_ENABLE_TCPIP
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/ota_dummy.c"
> +#endif
> +
> +
> +
> +#if MG_OTA == MG_OTA_NONE
> +bool mg_ota_begin(size_t new_firmware_size) {
> +  (void) new_firmware_size;
> +  return true;
> +}
> +bool mg_ota_write(const void *buf, size_t len) {
> +  (void) buf, (void) len;
> +  return true;
> +}
> +bool mg_ota_end(void) {
> +  return true;
> +}
> +bool mg_ota_commit(void) {
> +  return true;
> +}
> +bool mg_ota_rollback(void) {
> +  return true;
> +}
> +int mg_ota_status(int fw) {
> +  (void) fw;
> +  return 0;
> +}
> +uint32_t mg_ota_crc32(int fw) {
> +  (void) fw;
> +  return 0;
> +}
> +uint32_t mg_ota_timestamp(int fw) {
> +  (void) fw;
> +  return 0;
> +}
> +size_t mg_ota_size(int fw) {
> +  (void) fw;
> +  return 0;
> +}
> +MG_IRAM void mg_ota_boot(void) {
> +}
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/ota_esp32.c"
> +#endif
> +
> +
> +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
> +
> +static const esp_partition_t *s_ota_update_partition;
> +static esp_ota_handle_t s_ota_update_handle;
> +static bool s_ota_success;
> +
> +// Those empty macros do nothing, but mark places in the code which could
> +// potentially trigger a watchdog reboot due to the log flash erase operation
> +#define disable_wdt()
> +#define enable_wdt()
> +
> +bool mg_ota_begin(size_t new_firmware_size) {
> +  if (s_ota_update_partition != NULL) {
> +    MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
> +    return false;
> +  } else {
> +    s_ota_success = false;
> +    disable_wdt();
> +    s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
> +    esp_err_t err = esp_ota_begin(s_ota_update_partition, new_firmware_size,
> +                                  &s_ota_update_handle);
> +    enable_wdt();
> +    MG_DEBUG(("esp_ota_begin(): %d", err));
> +    s_ota_success = (err == ESP_OK);
> +  }
> +  return s_ota_success;
> +}
> +
> +bool mg_ota_write(const void *buf, size_t len) {
> +  disable_wdt();
> +  esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
> +  enable_wdt();
> +  MG_INFO(("esp_ota_write(): %d", err));
> +  s_ota_success = err == ESP_OK;
> +  return s_ota_success;
> +}
> +
> +bool mg_ota_end(void) {
> +  esp_err_t err = esp_ota_end(s_ota_update_handle);
> +  MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
> +  if (s_ota_success && err == ESP_OK) {
> +    err = esp_ota_set_boot_partition(s_ota_update_partition);
> +    s_ota_success = (err == ESP_OK);
> +  }
> +  MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
> +  s_ota_update_partition = NULL;
> +  return s_ota_success;
> +}
> +
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/ota_flash.c"
> +#endif
> +
> +
> +
> +
> +
> +// This OTA implementation uses the internal flash API outlined in device.h
> +// It splits flash into 2 equal partitions, and stores OTA status in the
> +// last sector of the partition.
> +
> +#if MG_OTA == MG_OTA_FLASH
> +
> +#define MG_OTADATA_KEY 0xb07afed0
> +
> +static char *s_addr;      // Current address to write to
> +static size_t s_size;     // Firmware size to flash. In-progress indicator
> +static uint32_t s_crc32;  // Firmware checksum
> +
> +struct mg_otadata {
> +  uint32_t crc32, size, timestamp, status;
> +};
> +
> +bool mg_ota_begin(size_t new_firmware_size) {
> +  bool ok = false;
> +  if (s_size) {
> +    MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
> +  } else {
> +    size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
> +    s_crc32 = 0;
> +    s_addr = (char *) mg_flash_start() + half;
> +    MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
> +    if (new_firmware_size < max) {
> +      ok = true;
> +      s_size = new_firmware_size;
> +      MG_INFO(("Starting OTA, firmware size %lu", s_size));
> +    } else {
> +      MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max));
> +    }
> +  }
> +  return ok;
> +}
> +
> +bool mg_ota_write(const void *buf, size_t len) {
> +  bool ok = false;
> +  if (s_size == 0) {
> +    MG_ERROR(("OTA is not started, call mg_ota_begin()"));
> +  } else {
> +    size_t align = mg_flash_write_align();
> +    size_t len_aligned_down = MG_ROUND_DOWN(len, align);
> +    if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down);
> +    if (len_aligned_down < len) {
> +      size_t left = len - len_aligned_down;
> +      char tmp[align];
> +      memset(tmp, 0xff, sizeof(tmp));
> +      memcpy(tmp, (char *) buf + len_aligned_down, left);
> +      ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
> +    }
> +    s_crc32 = mg_crc32(s_crc32, (char *) buf, len);  // Update CRC
> +    MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
> +    s_addr += len;
> +  }
> +  return ok;
> +}
> +
> +MG_IRAM static uint32_t mg_fwkey(int fw) {
> +  uint32_t key = MG_OTADATA_KEY + fw;
> +  int bank = mg_flash_bank();
> +  if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
> +  if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
> +  return key;
> +}
> +
> +bool mg_ota_end(void) {
> +  char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
> +  bool ok = false;
> +  if (s_size) {
> +    size_t size = s_addr - base;
> +    uint32_t crc32 = mg_crc32(0, base, s_size);
> +    if (size == s_size && crc32 == s_crc32) {
> +      uint32_t now = (uint32_t) (mg_now() / 1000);
> +      struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
> +      uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
> +      ok = mg_flash_save(NULL, key, &od, sizeof(od));
> +    }
> +    MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
> +              size, ok ? "ok" : "fail"));
> +    s_size = 0;
> +    if (ok) ok = mg_flash_swap_bank();
> +  }
> +  MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
> +  return ok;
> +}
> +
> +MG_IRAM static struct mg_otadata mg_otadata(int fw) {
> +  uint32_t key = mg_fwkey(fw);
> +  struct mg_otadata od = {};
> +  MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev"));
> +  mg_flash_load(NULL, key, &od, sizeof(od));
> +  // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key));
> +  // mg_hexdump(&od, sizeof(od));
> +  return od;
> +}
> +
> +int mg_ota_status(int fw) {
> +  struct mg_otadata od = mg_otadata(fw);
> +  return od.status;
> +}
> +uint32_t mg_ota_crc32(int fw) {
> +  struct mg_otadata od = mg_otadata(fw);
> +  return od.crc32;
> +}
> +uint32_t mg_ota_timestamp(int fw) {
> +  struct mg_otadata od = mg_otadata(fw);
> +  return od.timestamp;
> +}
> +size_t mg_ota_size(int fw) {
> +  struct mg_otadata od = mg_otadata(fw);
> +  return od.size;
> +}
> +
> +MG_IRAM bool mg_ota_commit(void) {
> +  bool ok = true;
> +  struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
> +  if (od.status != MG_OTA_COMMITTED) {
> +    od.status = MG_OTA_COMMITTED;
> +    MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
> +    ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od));
> +  }
> +  return ok;
> +}
> +
> +bool mg_ota_rollback(void) {
> +  MG_DEBUG(("Rolling firmware back"));
> +  if (mg_flash_bank() == 0) {
> +    // No dual bank support. Mark previous firmware as FIRST_BOOT
> +    struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> +    prev.status = MG_OTA_FIRST_BOOT;
> +    return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev,
> +                         sizeof(prev));
> +  } else {
> +    return mg_flash_swap_bank();
> +  }
> +}
> +
> +MG_IRAM void mg_ota_boot(void) {
> +  MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
> +  struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
> +  struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> +
> +  if (curr.status == MG_OTA_FIRST_BOOT) {
> +    if (prev.status == MG_OTA_UNAVAILABLE) {
> +      MG_INFO(("Setting previous firmware state to committed"));
> +      prev.status = MG_OTA_COMMITTED;
> +      mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev));
> +    }
> +    curr.status = MG_OTA_UNCOMMITTED;
> +    MG_INFO(("First boot, setting status to UNCOMMITTED"));
> +    mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr));
> +  } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
> +    // Swap paritions. Pray power does not disappear
> +    size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
> +    char *partition1 = mg_flash_start();
> +    char *partition2 = mg_flash_start() + fs / 2;
> +    size_t ofs, max = fs / 2 - ss;  // Set swap size to the whole partition
> +
> +    if (curr.status != MG_OTA_UNAVAILABLE &&
> +        prev.status != MG_OTA_UNAVAILABLE) {
> +      // We know exact sizes of both firmwares.
> +      // Shrink swap size to the MAX(firmware1, firmware2)
> +      size_t sz = curr.size > prev.size ? curr.size : prev.size;
> +      if (sz > 0 && sz < max) max = sz;
> +    }
> +
> +    // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
> +    prev.status = MG_OTA_UNCOMMITTED;
> +    mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
> +                  sizeof(prev));
> +    mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
> +                  sizeof(curr));
> +
> +    MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
> +    MG_INFO(("Do NOT power off..."));
> +    mg_log_level = MG_LL_NONE;
> +
> +    // We use the last sector of partition2 for OTA data/config storage
> +    // Therefore we can use last sector of partition1 for swapping
> +    char *tmpsector = partition1 + fs / 2 - ss;  // Last sector of partition1
> +    (void) tmpsector;
> +    for (ofs = 0; ofs < max; ofs += ss) {
> +      // mg_flash_erase(tmpsector);
> +      mg_flash_write(tmpsector, partition1 + ofs, ss);
> +      // mg_flash_erase(partition1 + ofs);
> +      mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
> +      // mg_flash_erase(partition2 + ofs);
> +      mg_flash_write(partition2 + ofs, tmpsector, ss);
> +    }
> +    mg_device_reset();
> +  }
> +}
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/printf.c"
> +#endif
> +
> +
> +
> +
> +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) {
> +  size_t len = mg_snprintf(NULL, 0, fmt, ap);
> +  char *buf;
> +  if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
> +    len = 0;  // Nah. Not enough space
> +  } else {
> +    len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
> +    mg_queue_add(q, len);
> +  }
> +  return len;
> +}
> +
> +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
> +  va_list ap;
> +  size_t len;
> +  va_start(ap, fmt);
> +  len = mg_queue_vprintf(q, fmt, &ap);
> +  va_end(ap);
> +  return len;
> +}
> +
> +static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
> +  struct mg_iobuf *io = (struct mg_iobuf *) param;
> +  if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
> +  if (io->len + 2 <= io->size) {
> +    io->buf[io->len++] = (uint8_t) ch;
> +    io->buf[io->len] = 0;
> +  } else if (io->len < io->size) {
> +    io->buf[io->len++] = 0;  // Guarantee to 0-terminate
> +  }
> +}
> +
> +static void mg_putchar_iobuf_static(char ch, void *param) {
> +  mg_pfn_iobuf_private(ch, param, false);
> +}
> +
> +void mg_pfn_iobuf(char ch, void *param) {
> +  mg_pfn_iobuf_private(ch, param, true);
> +}
> +
> +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
> +  struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> +  size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> +  if (n < len) buf[n] = '\0';
> +  return n;
> +}
> +
> +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> +  va_list ap;
> +  size_t n;
> +  va_start(ap, fmt);
> +  n = mg_vsnprintf(buf, len, fmt, &ap);
> +  va_end(ap);
> +  return n;
> +}
> +
> +char *mg_vmprintf(const char *fmt, va_list *ap) {
> +  struct mg_iobuf io = {0, 0, 0, 256};
> +  mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> +  return (char *) io.buf;
> +}
> +
> +char *mg_mprintf(const char *fmt, ...) {
> +  char *s;
> +  va_list ap;
> +  va_start(ap, fmt);
> +  s = mg_vmprintf(fmt, &ap);
> +  va_end(ap);
> +  return s;
> +}
> +
> +void mg_pfn_stdout(char c, void *param) {
> +  putchar(c);
> +  (void) param;
> +}
> +
> +static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
> +  return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
> +}
> +
> +static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t *p) {
> +  return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", mg_ntohs(p[0]),
> +                    mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
> +                    mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
> +                    mg_ntohs(p[7]));
> +}
> +
> +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) {
> +  uint8_t *p = va_arg(*ap, uint8_t *);
> +  return print_ip4(out, arg, p);
> +}
> +
> +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) {
> +  uint16_t *p = va_arg(*ap, uint16_t *);
> +  return print_ip6(out, arg, p);
> +}
> +
> +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) {
> +  struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
> +  if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip);
> +  return print_ip4(out, arg, (uint8_t *) &addr->ip);
> +}
> +
> +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap) {
> +  struct mg_addr *a = va_arg(*ap, struct mg_addr *);
> +  return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, mg_ntohs(a->port));
> +}
> +
> +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) {
> +  uint8_t *p = va_arg(*ap, uint8_t *);
> +  return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2],
> +                    p[3], p[4], p[5]);
> +}
> +
> +static char mg_esc(int c, bool esc) {
> +  const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> +  for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> +    if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> +  }
> +  return 0;
> +}
> +
> +static char mg_escape(int c) {
> +  return mg_esc(c, true);
> +}
> +
> +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> +                   size_t len) {
> +  size_t i = 0, extra = 0;
> +  for (i = 0; i < len && buf[i] != '\0'; i++) {
> +    char c = mg_escape(buf[i]);
> +    if (c) {
> +      out('\\', ptr), out(c, ptr), extra++;
> +    } else {
> +      out(buf[i], ptr);
> +    }
> +  }
> +  return i + extra;
> +}
> +
> +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf,
> +                   size_t len) {
> +  size_t i, j, n = 0;
> +  const char *t =
> +      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> +  for (i = 0; i < len; i += 3) {
> +    uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> +            c3 = i + 2 < len ? buf[i + 2] : 0;
> +    char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
> +    if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> +    if (i + 2 < len) tmp[3] = t[c3 & 63];
> +    for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg);
> +    n += j;
> +  }
> +  return n;
> +}
> +
> +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) {
> +  size_t bl = (size_t) va_arg(*ap, int);
> +  uint8_t *p = va_arg(*ap, uint8_t *);
> +  const char *hex = "0123456789abcdef";
> +  size_t j;
> +  for (j = 0; j < bl; j++) {
> +    out(hex[(p[j] >> 4) & 0x0F], arg);
> +    out(hex[p[j] & 0x0F], arg);
> +  }
> +  return 2 * bl;
> +}
> +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap) {
> +  size_t len = (size_t) va_arg(*ap, int);
> +  uint8_t *buf = va_arg(*ap, uint8_t *);
> +  return bcpy(out, arg, buf, len);
> +}
> +
> +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) {
> +  size_t len = (size_t) va_arg(*ap, int);
> +  char *p = va_arg(*ap, char *);
> +  if (len == 0) len = p == NULL ? 0 : strlen(p);
> +  return qcpy(out, arg, p, len);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/queue.c"
> +#endif
> +
> +
> +
> +#if (defined(__GNUC__) && (__GNUC__ > 4) ||                                \
> +     (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \
> +    defined(__clang__)
> +#define MG_MEMORY_BARRIER() __sync_synchronize()
> +#elif defined(_MSC_VER) && _MSC_VER >= 1700
> +#define MG_MEMORY_BARRIER() MemoryBarrier()
> +#elif !defined(MG_MEMORY_BARRIER)
> +#define MG_MEMORY_BARRIER()
> +#endif
> +
> +// Every message in a queue is prepended by a 32-bit message length (ML).
> +// If ML is 0, then it is the end, and reader must wrap to the beginning.
> +//
> +//  Queue when q->tail <= q->head:
> +//  |----- free -----| ML | message1 | ML | message2 |  ----- free ------|
> +//  ^                ^                               ^                   ^
> +// buf              tail                            head                len
> +//
> +//  Queue when q->tail > q->head:
> +//  | ML | message2 |----- free ------| ML | message1 | 0 |---- free ----|
> +//  ^               ^                 ^                                  ^
> +// buf             head              tail                               len
> +
> +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
> +  q->size = size;
> +  q->buf = buf;
> +  q->head = q->tail = 0;
> +}
> +
> +static size_t mg_queue_read_len(struct mg_queue *q) {
> +  uint32_t n = 0;
> +  MG_MEMORY_BARRIER();
> +  memcpy(&n, q->buf + q->tail, sizeof(n));
> +  assert(q->tail + n + sizeof(n) <= q->size);
> +  return n;
> +}
> +
> +static void mg_queue_write_len(struct mg_queue *q, size_t len) {
> +  uint32_t n = (uint32_t) len;
> +  memcpy(q->buf + q->head, &n, sizeof(n));
> +  MG_MEMORY_BARRIER();
> +}
> +
> +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
> +  size_t space = 0, hs = sizeof(uint32_t) * 2;  // *2 is for the 0 marker
> +  if (q->head >= q->tail && q->head + len + hs <= q->size) {
> +    space = q->size - q->head - hs;  // There is enough space
> +  } else if (q->head >= q->tail && q->tail > hs) {
> +    mg_queue_write_len(q, 0);  // Not enough space ahead
> +    q->head = 0;               // Wrap head to the beginning
> +  }
> +  if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
> +  if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
> +  return space;
> +}
> +
> +size_t mg_queue_next(struct mg_queue *q, char **buf) {
> +  size_t len = 0;
> +  if (q->tail != q->head) {
> +    len = mg_queue_read_len(q);
> +    if (len == 0) {  // Zero (head wrapped) ?
> +      q->tail = 0;   // Reset tail to the start
> +      if (q->head > q->tail) len = mg_queue_read_len(q);  // Read again
> +    }
> +  }
> +  if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
> +  assert(q->tail + len <= q->size);
> +  return len;
> +}
> +
> +void mg_queue_add(struct mg_queue *q, size_t len) {
> +  assert(len > 0);
> +  mg_queue_write_len(q, len);
> +  assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
> +  q->head += len + sizeof(uint32_t);
> +}
> +
> +void mg_queue_del(struct mg_queue *q, size_t len) {
> +  q->tail += len + sizeof(uint32_t);
> +  assert(q->tail + sizeof(uint32_t) <= q->size);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/rpc.c"
> +#endif
> +
> +
> +
> +void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> +                void (*fn)(struct mg_rpc_req *), void *fn_data) {
> +  struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> +  if (rpc != NULL) {
> +    rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
> +    rpc->method.len = method.len;
> +    rpc->fn = fn;
> +    rpc->fn_data = fn_data;
> +    rpc->next = *head, *head = rpc;
> +  }
> +}
> +
> +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
> +  struct mg_rpc *r;
> +  while ((r = *head) != NULL) {
> +    if (r->fn == fn || fn == NULL) {
> +      *head = r->next;
> +      free((void *) r->method.buf);
> +      free(r);
> +    } else {
> +      head = &(*head)->next;
> +    }
> +  }
> +}
> +
> +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
> +  struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> +  while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
> +  if (h != NULL) {
> +    r->rpc = h;
> +    h->fn(r);
> +  } else {
> +    mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.buf);
> +  }
> +}
> +
> +void mg_rpc_process(struct mg_rpc_req *r) {
> +  int len, off = mg_json_get(r->frame, "$.method", &len);
> +  if (off > 0 && r->frame.buf[off] == '"') {
> +    struct mg_str method = mg_str_n(&r->frame.buf[off + 1], (size_t) len - 2);
> +    mg_rpc_call(r, method);
> +  } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> +             (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> +    mg_rpc_call(r, mg_str(""));  // JSON response! call "" method handler
> +  } else {
> +    mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
> +               r->frame.buf);  // Invalid
> +  }
> +}
> +
> +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
> +  int len, off = mg_json_get(r->frame, "$.id", &len);
> +  if (off > 0) {
> +    mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len,
> +               &r->frame.buf[off], mg_print_esc, 0, "result");
> +    mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> +    mg_xprintf(r->pfn, r->pfn_data, "}");
> +  }
> +}
> +
> +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> +  va_list ap;
> +  va_start(ap, fmt);
> +  mg_rpc_vok(r, fmt, &ap);
> +  va_end(ap);
> +}
> +
> +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) {
> +  int len, off = mg_json_get(r->frame, "$.id", &len);
> +  mg_xprintf(r->pfn, r->pfn_data, "{");
> +  if (off > 0) {
> +    mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len,
> +               &r->frame.buf[off]);
> +  }
> +  mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error",
> +             mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
> +  mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> +  mg_xprintf(r->pfn, r->pfn_data, "}}");
> +}
> +
> +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
> +  va_list ap;
> +  va_start(ap, fmt);
> +  mg_rpc_verr(r, code, fmt, &ap);
> +  va_end(ap);
> +}
> +
> +static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
> +  struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
> +  size_t len = 0;
> +  for (h = *head; h != NULL; h = h->next) {
> +    if (h->method.len == 0) continue;  // Ignore response handler
> +    len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
> +                      mg_print_esc, (int) h->method.len, h->method.buf);
> +  }
> +  return len;
> +}
> +
> +void mg_rpc_list(struct mg_rpc_req *r) {
> +  mg_rpc_ok(r, "[%M]", print_methods, r->head);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/sha1.c"
> +#endif
> +/* Copyright(c) By Steve Reid <steve@edmweb.com> */
> +/* 100% Public Domain */
> +
> +
> +
> +union char64long16 {
> +  unsigned char c[64];
> +  uint32_t l[16];
> +};
> +
> +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
> +
> +static uint32_t blk0(union char64long16 *block, int i) {
> +  if (MG_BIG_ENDIAN) {
> +  } else {
> +    block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> +                  (rol(block->l[i], 8) & 0x00FF00FF);
> +  }
> +  return block->l[i];
> +}
> +
> +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
> +#undef blk
> +#undef R0
> +#undef R1
> +#undef R2
> +#undef R3
> +#undef R4
> +
> +#define blk(i)                                                               \
> +  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
> +                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
> +                          1))
> +#define R0(v, w, x, y, z, i)                                          \
> +  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
> +  w = rol(w, 30);
> +#define R1(v, w, x, y, z, i)                                  \
> +  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> +  w = rol(w, 30);
> +#define R2(v, w, x, y, z, i)                          \
> +  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> +  w = rol(w, 30);
> +#define R3(v, w, x, y, z, i)                                        \
> +  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
> +  w = rol(w, 30);
> +#define R4(v, w, x, y, z, i)                          \
> +  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> +  w = rol(w, 30);
> +
> +static void mg_sha1_transform(uint32_t state[5],
> +                              const unsigned char *buffer) {
> +  uint32_t a, b, c, d, e;
> +  union char64long16 block[1];
> +
> +  memcpy(block, buffer, 64);
> +  a = state[0];
> +  b = state[1];
> +  c = state[2];
> +  d = state[3];
> +  e = state[4];
> +  R0(a, b, c, d, e, 0);
> +  R0(e, a, b, c, d, 1);
> +  R0(d, e, a, b, c, 2);
> +  R0(c, d, e, a, b, 3);
> +  R0(b, c, d, e, a, 4);
> +  R0(a, b, c, d, e, 5);
> +  R0(e, a, b, c, d, 6);
> +  R0(d, e, a, b, c, 7);
> +  R0(c, d, e, a, b, 8);
> +  R0(b, c, d, e, a, 9);
> +  R0(a, b, c, d, e, 10);
> +  R0(e, a, b, c, d, 11);
> +  R0(d, e, a, b, c, 12);
> +  R0(c, d, e, a, b, 13);
> +  R0(b, c, d, e, a, 14);
> +  R0(a, b, c, d, e, 15);
> +  R1(e, a, b, c, d, 16);
> +  R1(d, e, a, b, c, 17);
> +  R1(c, d, e, a, b, 18);
> +  R1(b, c, d, e, a, 19);
> +  R2(a, b, c, d, e, 20);
> +  R2(e, a, b, c, d, 21);
> +  R2(d, e, a, b, c, 22);
> +  R2(c, d, e, a, b, 23);
> +  R2(b, c, d, e, a, 24);
> +  R2(a, b, c, d, e, 25);
> +  R2(e, a, b, c, d, 26);
> +  R2(d, e, a, b, c, 27);
> +  R2(c, d, e, a, b, 28);
> +  R2(b, c, d, e, a, 29);
> +  R2(a, b, c, d, e, 30);
> +  R2(e, a, b, c, d, 31);
> +  R2(d, e, a, b, c, 32);
> +  R2(c, d, e, a, b, 33);
> +  R2(b, c, d, e, a, 34);
> +  R2(a, b, c, d, e, 35);
> +  R2(e, a, b, c, d, 36);
> +  R2(d, e, a, b, c, 37);
> +  R2(c, d, e, a, b, 38);
> +  R2(b, c, d, e, a, 39);
> +  R3(a, b, c, d, e, 40);
> +  R3(e, a, b, c, d, 41);
> +  R3(d, e, a, b, c, 42);
> +  R3(c, d, e, a, b, 43);
> +  R3(b, c, d, e, a, 44);
> +  R3(a, b, c, d, e, 45);
> +  R3(e, a, b, c, d, 46);
> +  R3(d, e, a, b, c, 47);
> +  R3(c, d, e, a, b, 48);
> +  R3(b, c, d, e, a, 49);
> +  R3(a, b, c, d, e, 50);
> +  R3(e, a, b, c, d, 51);
> +  R3(d, e, a, b, c, 52);
> +  R3(c, d, e, a, b, 53);
> +  R3(b, c, d, e, a, 54);
> +  R3(a, b, c, d, e, 55);
> +  R3(e, a, b, c, d, 56);
> +  R3(d, e, a, b, c, 57);
> +  R3(c, d, e, a, b, 58);
> +  R3(b, c, d, e, a, 59);
> +  R4(a, b, c, d, e, 60);
> +  R4(e, a, b, c, d, 61);
> +  R4(d, e, a, b, c, 62);
> +  R4(c, d, e, a, b, 63);
> +  R4(b, c, d, e, a, 64);
> +  R4(a, b, c, d, e, 65);
> +  R4(e, a, b, c, d, 66);
> +  R4(d, e, a, b, c, 67);
> +  R4(c, d, e, a, b, 68);
> +  R4(b, c, d, e, a, 69);
> +  R4(a, b, c, d, e, 70);
> +  R4(e, a, b, c, d, 71);
> +  R4(d, e, a, b, c, 72);
> +  R4(c, d, e, a, b, 73);
> +  R4(b, c, d, e, a, 74);
> +  R4(a, b, c, d, e, 75);
> +  R4(e, a, b, c, d, 76);
> +  R4(d, e, a, b, c, 77);
> +  R4(c, d, e, a, b, 78);
> +  R4(b, c, d, e, a, 79);
> +  state[0] += a;
> +  state[1] += b;
> +  state[2] += c;
> +  state[3] += d;
> +  state[4] += e;
> +  /* Erase working structures. The order of operations is important,
> +   * used to ensure that compiler doesn't optimize those out. */
> +  memset(block, 0, sizeof(block));
> +  a = b = c = d = e = 0;
> +  (void) a;
> +  (void) b;
> +  (void) c;
> +  (void) d;
> +  (void) e;
> +}
> +
> +void mg_sha1_init(mg_sha1_ctx *context) {
> +  context->state[0] = 0x67452301;
> +  context->state[1] = 0xEFCDAB89;
> +  context->state[2] = 0x98BADCFE;
> +  context->state[3] = 0x10325476;
> +  context->state[4] = 0xC3D2E1F0;
> +  context->count[0] = context->count[1] = 0;
> +}
> +
> +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
> +                    size_t len) {
> +  size_t i, j;
> +
> +  j = context->count[0];
> +  if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
> +  context->count[1] += (uint32_t) (len >> 29);
> +  j = (j >> 3) & 63;
> +  if ((j + len) > 63) {
> +    memcpy(&context->buffer[j], data, (i = 64 - j));
> +    mg_sha1_transform(context->state, context->buffer);
> +    for (; i + 63 < len; i += 64) {
> +      mg_sha1_transform(context->state, &data[i]);
> +    }
> +    j = 0;
> +  } else
> +    i = 0;
> +  memcpy(&context->buffer[j], &data[i], len - i);
> +}
> +
> +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
> +  unsigned i;
> +  unsigned char finalcount[8], c;
> +
> +  for (i = 0; i < 8; i++) {
> +    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
> +                                      ((3 - (i & 3)) * 8)) &
> +                                     255);
> +  }
> +  c = 0200;
> +  mg_sha1_update(context, &c, 1);
> +  while ((context->count[0] & 504) != 448) {
> +    c = 0000;
> +    mg_sha1_update(context, &c, 1);
> +  }
> +  mg_sha1_update(context, finalcount, 8);
> +  for (i = 0; i < 20; i++) {
> +    digest[i] =
> +        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
> +  }
> +  memset(context, '\0', sizeof(*context));
> +  memset(&finalcount, '\0', sizeof(finalcount));
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/sha256.c"
> +#endif
> +// https://github.com/B-Con/crypto-algorithms
> +// Author:     Brad Conte (brad AT bradconte.com)
> +// Disclaimer: This code is presented "as is" without any guarantees.
> +// Details:    Defines the API for the corresponding SHA1 implementation.
> +// Copyright:  public domain
> +
> +
> +
> +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
> +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
> +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
> +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
> +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
> +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
> +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
> +
> +static const uint32_t mg_sha256_k[64] = {
> +    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
> +    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
> +    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
> +    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
> +    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
> +    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
> +    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
> +    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
> +    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
> +    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
> +    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
> +
> +void mg_sha256_init(mg_sha256_ctx *ctx) {
> +  ctx->len = 0;
> +  ctx->bits = 0;
> +  ctx->state[0] = 0x6a09e667;
> +  ctx->state[1] = 0xbb67ae85;
> +  ctx->state[2] = 0x3c6ef372;
> +  ctx->state[3] = 0xa54ff53a;
> +  ctx->state[4] = 0x510e527f;
> +  ctx->state[5] = 0x9b05688c;
> +  ctx->state[6] = 0x1f83d9ab;
> +  ctx->state[7] = 0x5be0cd19;
> +}
> +
> +static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
> +  int i, j;
> +  uint32_t a, b, c, d, e, f, g, h;
> +  uint32_t m[64];
> +  for (i = 0, j = 0; i < 16; ++i, j += 4)
> +    m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
> +                       ((uint32_t) ctx->buffer[j + 1] << 16) |
> +                       ((uint32_t) ctx->buffer[j + 2] << 8) |
> +                       ((uint32_t) ctx->buffer[j + 3]));
> +  for (; i < 64; ++i)
> +    m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
> +
> +  a = ctx->state[0];
> +  b = ctx->state[1];
> +  c = ctx->state[2];
> +  d = ctx->state[3];
> +  e = ctx->state[4];
> +  f = ctx->state[5];
> +  g = ctx->state[6];
> +  h = ctx->state[7];
> +
> +  for (i = 0; i < 64; ++i) {
> +    uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
> +    uint32_t t2 = ep0(a) + maj(a, b, c);
> +    h = g;
> +    g = f;
> +    f = e;
> +    e = d + t1;
> +    d = c;
> +    c = b;
> +    b = a;
> +    a = t1 + t2;
> +  }
> +
> +  ctx->state[0] += a;
> +  ctx->state[1] += b;
> +  ctx->state[2] += c;
> +  ctx->state[3] += d;
> +  ctx->state[4] += e;
> +  ctx->state[5] += f;
> +  ctx->state[6] += g;
> +  ctx->state[7] += h;
> +}
> +
> +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char *data,
> +                      size_t len) {
> +  size_t i;
> +  for (i = 0; i < len; i++) {
> +    ctx->buffer[ctx->len] = data[i];
> +    if ((++ctx->len) == 64) {
> +      mg_sha256_chunk(ctx);
> +      ctx->bits += 512;
> +      ctx->len = 0;
> +    }
> +  }
> +}
> +
> +// TODO: make final reusable (remove side effects)
> +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *ctx) {
> +  uint32_t i = ctx->len;
> +  if (i < 56) {
> +    ctx->buffer[i++] = 0x80;
> +    while (i < 56) {
> +      ctx->buffer[i++] = 0x00;
> +    }
> +  } else {
> +    ctx->buffer[i++] = 0x80;
> +    while (i < 64) {
> +      ctx->buffer[i++] = 0x00;
> +    }
> +    mg_sha256_chunk(ctx);
> +    memset(ctx->buffer, 0, 56);
> +  }
> +
> +  ctx->bits += ctx->len * 8;
> +  ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
> +  ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
> +  ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
> +  ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
> +  ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
> +  ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
> +  ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
> +  ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
> +  mg_sha256_chunk(ctx);
> +
> +  for (i = 0; i < 4; ++i) {
> +    digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
> +    digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) & 0xff);
> +    digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) & 0xff);
> +    digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) & 0xff);
> +    digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) & 0xff);
> +    digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) & 0xff);
> +    digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) & 0xff);
> +    digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) & 0xff);
> +  }
> +}
> +
> +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data,
> +                    size_t datasz) {
> +  mg_sha256_ctx ctx;
> +  uint8_t k[64] = {0};
> +  uint8_t o_pad[64], i_pad[64];
> +  unsigned int i;
> +  memset(i_pad, 0x36, sizeof(i_pad));
> +  memset(o_pad, 0x5c, sizeof(o_pad));
> +  if (keysz < 64) {
> +    if (keysz > 0) memmove(k, key, keysz);
> +  } else {
> +    mg_sha256_init(&ctx);
> +    mg_sha256_update(&ctx, key, keysz);
> +    mg_sha256_final(k, &ctx);
> +  }
> +  for (i = 0; i < sizeof(k); i++) {
> +    i_pad[i] ^= k[i];
> +    o_pad[i] ^= k[i];
> +  }
> +  mg_sha256_init(&ctx);
> +  mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
> +  mg_sha256_update(&ctx, data, datasz);
> +  mg_sha256_final(dst, &ctx);
> +  mg_sha256_init(&ctx);
> +  mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
> +  mg_sha256_update(&ctx, dst, 32);
> +  mg_sha256_final(dst, &ctx);
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/sntp.c"
> +#endif
> +
> +
> +
> +
> +
> +
> +#define SNTP_TIME_OFFSET 2208988800U  // (1970 - 1900) in seconds
> +#define SNTP_MAX_FRAC 4294967295.0    // 2 ** 32 - 1
> +
> +static int64_t gettimestamp(const uint32_t *data) {
> +  uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> +  if (sec) sec -= SNTP_TIME_OFFSET;
> +  return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0);
> +}
> +
> +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> +  int64_t res = -1;
> +  int mode = len > 0 ? buf[0] & 7 : 0;
> +  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> +  if (len < 48) {
> +    MG_ERROR(("%s", "corrupt packet"));
> +  } else if (mode != 4 && mode != 5) {
> +    MG_ERROR(("%s", "not a server reply"));
> +  } else if (buf[1] == 0) {
> +    MG_ERROR(("%s", "server sent a kiss of death"));
> +  } else if (version == 4 || version == 3) {
> +    // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> +    int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> +    int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> +    int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> +    int64_t t3 = (int64_t) mg_millis();
> +    int64_t delta = (t3 - t0) - (t2 - t1);
> +    MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
> +    res = t2 + delta / 2;
> +  } else {
> +    MG_ERROR(("unexpected version: %d", version));
> +  }
> +  return res;
> +}
> +
> +static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_READ) {
> +    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> +    if (milliseconds > 0) {
> +      MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds));
> +      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> +      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> +                  (unsigned) (milliseconds % 1000)));
> +    }
> +    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
> +  } else if (ev == MG_EV_CONNECT) {
> +    mg_sntp_request(c);
> +  } else if (ev == MG_EV_CLOSE) {
> +  }
> +  (void) ev_data;
> +}
> +
> +void mg_sntp_request(struct mg_connection *c) {
> +  if (c->is_resolving) {
> +    MG_ERROR(("%lu wait until resolved", c->id));
> +  } else {
> +    int64_t now = (int64_t) mg_millis();  // Use int64_t, for vc98
> +    uint8_t buf[48] = {0};
> +    uint32_t *t = (uint32_t *) &buf[40];
> +    double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> +    buf[0] = (0 << 6) | (4 << 3) | 3;
> +    t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> +    t[1] = mg_htonl((uint32_t) frac);
> +    mg_send(c, buf, sizeof(buf));
> +  }
> +}
> +
> +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
> +                                      mg_event_handler_t fn, void *fnd) {
> +  struct mg_connection *c = NULL;
> +  if (url == NULL) url = "udp://time.google.com:123";
> +  if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> +  return c;
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/sock.c"
> +#endif
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +#if MG_ENABLE_SOCKET
> +
> +#ifndef closesocket
> +#define closesocket(x) close(x)
> +#endif
> +
> +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> +#define S2PTR(s_) ((void *) (size_t) (s_))
> +
> +#ifndef MSG_NONBLOCKING
> +#define MSG_NONBLOCKING 0
> +#endif
> +
> +#ifndef AF_INET6
> +#define AF_INET6 10
> +#endif
> +
> +#ifndef MG_SOCK_ERR
> +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
> +#endif
> +
> +#ifndef MG_SOCK_INTR
> +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == EINTR)
> +#endif
> +
> +#ifndef MG_SOCK_PENDING
> +#define MG_SOCK_PENDING(errcode) \
> +  (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK))
> +#endif
> +
> +#ifndef MG_SOCK_RESET
> +#define MG_SOCK_RESET(errcode) \
> +  (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
> +#endif
> +
> +union usa {
> +  struct sockaddr sa;
> +  struct sockaddr_in sin;
> +#if MG_ENABLE_IPV6
> +  struct sockaddr_in6 sin6;
> +#endif
> +};
> +
> +static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> +  socklen_t len = sizeof(usa->sin);
> +  memset(usa, 0, sizeof(*usa));
> +  usa->sin.sin_family = AF_INET;
> +  usa->sin.sin_port = a->port;
> +  memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
> +#if MG_ENABLE_IPV6
> +  if (a->is_ip6) {
> +    usa->sin.sin_family = AF_INET6;
> +    usa->sin6.sin6_port = a->port;
> +    usa->sin6.sin6_scope_id = a->scope_id;
> +    memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
> +    len = sizeof(usa->sin6);
> +  }
> +#endif
> +  return len;
> +}
> +
> +static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
> +  a->is_ip6 = is_ip6;
> +  a->port = usa->sin.sin_port;
> +  memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
> +#if MG_ENABLE_IPV6
> +  if (is_ip6) {
> +    memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
> +    a->port = usa->sin6.sin6_port;
> +    a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
> +  }
> +#endif
> +}
> +
> +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> +  union usa usa;
> +  socklen_t n = sizeof(usa);
> +  if (getsockname(fd, &usa.sa, &n) == 0) {
> +    tomgaddr(&usa, addr, n != sizeof(usa.sin));
> +  }
> +}
> +
> +static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
> +  if (n == MG_IO_WAIT) {
> +    // Do nothing
> +  } else if (n <= 0) {
> +    c->is_closing = 1;  // Termination. Don't call mg_error(): #1529
> +  } else if (n > 0) {
> +    if (c->is_hexdumping) {
> +      MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc,
> +               r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
> +      mg_hexdump(buf, (size_t) n);
> +    }
> +    if (r) {
> +      c->recv.len += (size_t) n;
> +      mg_call(c, MG_EV_READ, &n);
> +    } else {
> +      mg_iobuf_del(&c->send, 0, (size_t) n);
> +      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> +      if (c->send.len == 0) {
> +        MG_EPOLL_MOD(c, 0);
> +      }
> +      mg_call(c, MG_EV_WRITE, &n);
>       }
>     }
> -  return flags;
>   }
>
> -static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
> -                       const struct mg_http_serve_opts *opts, char *path,
> -                       size_t path_size) {
> -  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> -  struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0};
> -  while (mg_commalist(&s, &k, &v)) {
> -    if (v.len == 0) v = k, k = mg_str("/");
> -    if (hm->uri.len < k.len) continue;
> -    if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
> -    u = k, p = v;
> +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> +  long n;
> +  if (c->is_udp) {
> +    union usa usa;
> +    socklen_t slen = tousa(&c->rem, &usa);
> +    n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
> +    if (n > 0) setlocaddr(FD(c), &c->loc);
> +  } else {
> +    n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>     }
> -  return uri_to_path2(c, hm, fs, u, p, path, path_size);
> +  MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> +  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> +  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
>   }
>
> -void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
> -                       const struct mg_http_serve_opts *opts) {
> -  char path[MG_PATH_MAX];
> -  const char *sp = opts->ssi_pattern;
> -  int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> -  if (flags < 0) {
> -    // Do nothing: the response has already been sent by uri_to_path()
> -  } else if (flags & MG_FS_DIR) {
> -    listdir(c, hm, opts, path);
> -  } else if (flags && sp != NULL &&
> -             mg_globmatch(sp, strlen(sp), path, strlen(path))) {
> -    mg_http_serve_ssi(c, opts->root_dir, path);
> +bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> +  if (c->is_udp) {
> +    long n = mg_io_send(c, buf, len);
> +    MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
> +              c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> +    iolog(c, (char *) buf, n, false);
> +    return n > 0;
>     } else {
> -    mg_http_serve_file(c, hm, path, opts);
> +    return mg_iobuf_add(&c->send, c->send.len, buf, len);
>     }
>   }
>
> -static bool mg_is_url_safe(int c) {
> -  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> -         (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
> +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> +#if defined(MG_CUSTOM_NONBLOCK)
> +  MG_CUSTOM_NONBLOCK(fd);
> +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> +  unsigned long on = 1;
> +  ioctlsocket(fd, FIONBIO, &on);
> +#elif MG_ENABLE_RL
> +  unsigned long on = 1;
> +  ioctlsocket(fd, FIONBIO, &on);
> +#elif MG_ENABLE_FREERTOS_TCP
> +  const BaseType_t off = 0;
> +  if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0;
> +  if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0;
> +#elif MG_ENABLE_LWIP
> +  lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> +#elif MG_ARCH == MG_ARCH_AZURERTOS
> +  fcntl(fd, F_SETFL, O_NONBLOCK);
> +#elif MG_ARCH == MG_ARCH_TIRTOS
> +  int val = 0;
> +  setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> +  // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> +  int sz = sizeof(val);
> +  getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> +  val /= 2;  // set send low-water mark at half send buffer size
> +  setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> +#else
> +  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);  // Non-blocking mode
> +  fcntl(fd, F_SETFD, FD_CLOEXEC);                          // Set close-on-exec
> +#endif
>   }
>
> -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
> -  size_t i, n = 0;
> -  for (i = 0; i < sl; i++) {
> -    int c = *(unsigned char *) &s[i];
> -    if (n + 4 >= len) return 0;
> -    if (mg_is_url_safe(c)) {
> -      buf[n++] = s[i];
> +bool mg_open_listener(struct mg_connection *c, const char *url) {
> +  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> +  bool success = false;
> +  c->loc.port = mg_htons(mg_url_port(url));
> +  if (!mg_aton(mg_url_host(url), &c->loc)) {
> +    MG_ERROR(("invalid listening URL: %s", url));
> +  } else {
> +    union usa usa;
> +    socklen_t slen = tousa(&c->loc, &usa);
> +    int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> +    int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
> +    int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> +    (void) on;
> +
> +    if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> +      MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
> +#if defined(SO_EXCLUSIVEADDRUSE)
> +    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
> +                                (char *) &on, sizeof(on))) != 0) {
> +      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> +      MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc)));
> +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
> +    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
> +                                sizeof(on))) != 0) {
> +      // 1. SO_REUSEADDR semantics on UNIX and Windows is different.  On
> +      // Windows, SO_REUSEADDR allows to bind a socket to a port without error
> +      // even if the port is already open by another program. This is not the
> +      // behavior SO_REUSEADDR was designed for, and leads to hard-to-track
> +      // failure scenarios.
> +      //
> +      // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining
> +      // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but
> +      // won't work! (setsockopt will return EINVAL)
> +      MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
> +#endif
> +#if MG_IPV6_V6ONLY
> +      // Bind only to the V6 address, not V4 address on this port
> +    } else if (c->loc.is_ip6 &&
> +               (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
> +                                sizeof(on))) != 0) {
> +      // See #2089. Allow to bind v4 and v6 sockets on the same port
> +      MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
> +#endif
> +    } else if ((rc = bind(fd, &usa.sa, slen)) != 0) {
> +      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> +    } else if ((type == SOCK_STREAM &&
> +                (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
> +      // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> +      // In case port was set to 0, get the real port number
> +      MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
>       } else {
> -      buf[n++] = '%';
> -      mg_hex(&s[i], 1, &buf[n]);
> -      n += 2;
> +      setlocaddr(fd, &c->loc);
> +      mg_set_non_blocking_mode(fd);
> +      c->fd = S2PTR(fd);
> +      MG_EPOLL_ADD(c);
> +      success = true;
>       }
>     }
> -  if (len > 0 && n < len - 1) buf[n] = '\0';  // Null-terminate the destination
> -  if (len > 0) buf[len - 1] = '\0';           // Always.
> -  return n;
> +  if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> +  return success;
>   }
>
> -void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
> -                   char *pass, size_t passlen) {
> -  struct mg_str *v = mg_http_get_header(hm, "Authorization");
> -  user[0] = pass[0] = '\0';
> -  if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
> -    char buf[256];
> -    int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
> -    const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 0);
> -    if (p != NULL) {
> -      mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
> -      mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
> -    }
> -  } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) {
> -    mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
> -  } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> -    struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12));
> -    if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
> +static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
> +  long n = 0;
> +  if (c->is_udp) {
> +    union usa usa;
> +    socklen_t slen = tousa(&c->rem, &usa);
> +    n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
> +    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
>     } else {
> -    mg_http_get_var(&hm->query, "access_token", pass, passlen);
> +    n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>     }
> +  MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> +  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> +  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
>   }
>
> -static struct mg_str stripquotes(struct mg_str s) {
> -  return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
> -             ? mg_str_n(s.ptr + 1, s.len - 2)
> -             : s;
> +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
> +  bool res = false;
> +  if (io->len >= MG_MAX_RECV_SIZE) {
> +    mg_error(c, "MG_MAX_RECV_SIZE");
> +  } else if (io->size <= io->len &&
> +             !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
> +    mg_error(c, "OOM");
> +  } else {
> +    res = true;
> +  }
> +  return res;
>   }
>
> -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
> -  size_t i;
> -  for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> -    if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) {
> -      const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
> -      int q = p < x && *p == '"' ? 1 : 0;
> -      while (p < x &&
> -             (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> -        p++;
> -      // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len,
> -      // v.ptr, (int) (p - b), b));
> -      return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> +// NOTE(lsm): do only one iteration of reads, cause some systems
> +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
> +static void read_conn(struct mg_connection *c) {
> +  if (ioalloc(c, &c->recv)) {
> +    char *buf = (char *) &c->recv.buf[c->recv.len];
> +    size_t len = c->recv.size - c->recv.len;
> +    long n = -1;
> +    if (c->is_tls) {
> +      if (!ioalloc(c, &c->rtls)) return;
> +      n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
> +                   c->rtls.size - c->rtls.len);
> +      if (n == MG_IO_ERR && c->rtls.len == 0) {
> +        // Close only if we have fully drained both raw (rtls) and TLS buffers
> +        c->is_closing = 1;
> +      } else {
> +        if (n > 0) c->rtls.len += (size_t) n;
> +        if (c->is_tls_hs) mg_tls_handshake(c);
> +        n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
> +      }
> +    } else {
> +      n = recv_raw(c, buf, len);
>       }
> +    MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
> +              c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> +    iolog(c, buf, n, true);
>     }
> -  return mg_str_n(NULL, 0);
>   }
>
> -bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) {
> -  return mg_match(hm->uri, mg_str(glob), NULL);
> +static void write_conn(struct mg_connection *c) {
> +  char *buf = (char *) c->send.buf;
> +  size_t len = c->send.len;
> +  long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len);
> +  MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> +            (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> +            (long) c->recv.size, n, MG_SOCK_ERR(n)));
> +  iolog(c, buf, n, false);
>   }
>
> -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> -                    struct mg_fs *fs, const char *path, size_t max_size) {
> -  char buf[20] = "0";
> -  long res = 0, offset;
> -  mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> -  offset = strtol(buf, NULL, 0);
> -  if (hm->body.len == 0) {
> -    mg_http_reply(c, 200, "", "%ld", res);  // Nothing to write
> +static void close_conn(struct mg_connection *c) {
> +  if (FD(c) != MG_INVALID_SOCKET) {
> +#if MG_ENABLE_EPOLL
> +    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> +#endif
> +    closesocket(FD(c));
> +#if MG_ENABLE_FREERTOS_TCP
> +    FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> +#endif
> +  }
> +  mg_close_conn(c);
> +}
> +
> +static void connect_conn(struct mg_connection *c) {
> +  union usa usa;
> +  socklen_t n = sizeof(usa);
> +  // Use getpeername() to test whether we have connected
> +  if (getpeername(FD(c), &usa.sa, &n) == 0) {
> +    c->is_connecting = 0;
> +    setlocaddr(FD(c), &c->loc);
> +    mg_call(c, MG_EV_CONNECT, NULL);
> +    MG_EPOLL_MOD(c, 0);
> +    if (c->is_tls_hs) mg_tls_handshake(c);
>     } else {
> -    struct mg_fd *fd;
> -    size_t current_size = 0;
> -    MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset));
> -    if (offset == 0) fs->rm(path);  // If offset if 0, truncate file
> -    fs->st(path, &current_size, NULL);
> -    if (offset < 0) {
> -      mg_http_reply(c, 400, "", "offset required");
> -      res = -1;
> -    } else if (offset > 0 && current_size != (size_t) offset) {
> -      mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> -      res = -2;
> -    } else if ((size_t) offset + hm->body.len > max_size) {
> -      mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> -                    (unsigned long) max_size);
> -      res = -3;
> -    } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> -      mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> -      res = -4;
> +    mg_error(c, "socket error");
> +  }
> +}
> +
> +static void setsockopts(struct mg_connection *c) {
> +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> +    MG_ARCH == MG_ARCH_TIRTOS
> +  (void) c;
> +#else
> +  int on = 1;
> +#if !defined(SOL_TCP)
> +#define SOL_TCP IPPROTO_TCP
> +#endif
> +  if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0)
> +    (void) 0;
> +  if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) !=
> +      0)
> +    (void) 0;
> +#endif
> +}
> +
> +void mg_connect_resolved(struct mg_connection *c) {
> +  int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> +  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;  // c->rem has resolved IP
> +  c->fd = S2PTR(socket(af, type, 0));               // Create outbound socket
> +  c->is_resolving = 0;                              // Clear resolving flag
> +  if (FD(c) == MG_INVALID_SOCKET) {
> +    mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
> +  } else if (c->is_udp) {
> +    MG_EPOLL_ADD(c);
> +#if MG_ARCH == MG_ARCH_TIRTOS
> +    union usa usa;  // TI-RTOS NDK requires binding to receive on UDP sockets
> +    socklen_t slen = tousa(&c->loc, &usa);
> +    if ((rc = bind(c->fd, &usa.sa, slen)) != 0)
> +      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> +#endif
> +    setlocaddr(FD(c), &c->loc);
> +    mg_call(c, MG_EV_RESOLVE, NULL);
> +    mg_call(c, MG_EV_CONNECT, NULL);
> +  } else {
> +    union usa usa;
> +    socklen_t slen = tousa(&c->rem, &usa);
> +    mg_set_non_blocking_mode(FD(c));
> +    setsockopts(c);
> +    MG_EPOLL_ADD(c);
> +    mg_call(c, MG_EV_RESOLVE, NULL);
> +    rc = connect(FD(c), &usa.sa, slen);  // Attempt to connect
> +    if (rc == 0) {                       // Success
> +      setlocaddr(FD(c), &c->loc);
> +      mg_call(c, MG_EV_CONNECT, NULL);  // Send MG_EV_CONNECT to the user
> +    } else if (MG_SOCK_PENDING(rc)) {   // Need to wait for TCP handshake
> +      MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem));
> +      c->is_connecting = 1;
>       } else {
> -      res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
> -      mg_fs_close(fd);
> -      mg_http_reply(c, 200, "", "%ld", res);
> +      mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
>       }
>     }
> -  return res;
> -}
> -
> -int mg_http_status(const struct mg_http_message *hm) {
> -  return atoi(hm->uri.ptr);
> -}
> -
> -// If a server sends data to the client using chunked encoding, Mongoose strips
> -// off the chunking prefix (hex length and \r\n) and suffix (\r\n), appends the
> -// stripped data to the body, and fires the MG_EV_HTTP_CHUNK event.  When zero
> -// chunk is received, we fire MG_EV_HTTP_MSG, and the body already has all
> -// chunking prefixes/suffixes stripped.
> -//
> -// If a server sends data without chunked encoding, we also fire a series of
> -// MG_EV_HTTP_CHUNK events for every received piece of data, and then we fire
> -// MG_EV_HTTP_MSG event in the end.
> -//
> -// We track total processed length in the c->pfn_data, which is a void *
> -// pointer: we store a size_t value there.
> -static bool getchunk(struct mg_str s, size_t *prefixlen, size_t *datalen) {
> -  size_t i = 0, n;
> -  while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
> -  n = mg_unhexn(s.ptr, i);
> -  // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
> -  if (s.len < i + n + 4) return false;  // Chunk not yet fully buffered
> -  if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
> -  if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n') return false;
> -  *prefixlen = i + 2;
> -  *datalen = n;
> -  return true;
>   }
>
> -static bool mg_is_chunked(struct mg_http_message *hm) {
> -  const char *needle = "chunked";
> -  struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
> -  return te != NULL && mg_vcasecmp(te, needle) == 0;
> -}
> -
> -void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
> -  size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
> -  mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
> -  c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
> -}
> -
> -static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
> -                                   struct mg_http_message *hm, bool *next) {
> -  //  |  ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
> -  //  +------------------+--------------------------+----
> -  //  |      hlen        |           chunk1         | ......
> -  char *buf = (char *) &c->recv.buf[hlen], *p = buf;
> -  size_t len = c->recv.len - hlen;
> -  size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> -  size_t mark, pl, dl, del = 0, ofs = 0;
> -  bool last = false;
> -  if (processed <= len) len -= processed, buf += processed;
> -  while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
> -    size_t saved = c->recv.len;
> -    memmove(p + processed, buf + ofs + pl, dl);
> -    // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
> -    hm->chunk = mg_str_n(p + processed, dl);
> -    mg_call(c, MG_EV_HTTP_CHUNK, hm);
> -    ofs += pl + dl + 2, del += pl + 2;  // 2 is for \r\n suffix
> -    processed += dl;
> -    if (c->recv.len != saved) processed -= dl, buf -= dl;
> -    // mg_hexdump(c->recv.buf, hlen + processed);
> -    last = (dl == 0);
> -  }
> -  mg_iobuf_del(&c->recv, hlen + processed, del);
> -  mark = ((size_t) c->pfn_data) & MG_DMARK;
> -  c->pfn_data = (void *) (processed | mark);
> -  if (last) {
> -    hm->body.len = processed;
> -    hm->message.len = hlen + processed;
> -    c->pfn_data = NULL;
> -    if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
> -    // MG_INFO(("LAST, mark: %lx", mark));
> -    // mg_hexdump(c->recv.buf, c->recv.len);
> -  }
> +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> +                              socklen_t *len) {
> +  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> +  do {
> +    memset(usa, 0, sizeof(*usa));
> +    fd = accept(sock, &usa->sa, len);
> +  } while (MG_SOCK_INTR(fd));
> +  return fd;
>   }
>
> -static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
> -                                  struct mg_http_message *hm, bool *next) {
> -  size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> -  size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
> -  hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
> -  if (processed <= hm->chunk.len && !deleted) {
> -    hm->chunk.len -= processed;
> -    hm->chunk.ptr += processed;
> -  }
> -  left = hm->body.len < processed ? 0 : hm->body.len - processed;
> -  if (hm->chunk.len > left) hm->chunk.len = left;
> -  if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
> -  processed += hm->chunk.len;
> -  deleted = ((size_t) c->pfn_data) & MG_DMARK;  // Re-evaluate after user call
> -  if (processed >= hm->body.len) {              // Last, 0-len chunk
> -    hm->chunk.len = 0;                          // Reset length
> -    mg_call(c, MG_EV_HTTP_CHUNK, hm);           // Call user handler
> -    c->pfn_data = NULL;                         // Reset processed counter
> -    if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
> +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
> +  struct mg_connection *c = NULL;
> +  union usa usa;
> +  socklen_t sa_len = sizeof(usa);
> +  MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> +  if (fd == MG_INVALID_SOCKET) {
> +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
> +    // AzureRTOS, in non-block socket mode can mark listening socket readable
> +    // even it is not. See comment for 'select' func implementation in
> +    // nx_bsd.c That's not an error, just should try later
> +    if (errno != EAGAIN)
> +#endif
> +      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1)));
> +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> +    (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
> +  } else if ((long) fd >= FD_SETSIZE) {
> +    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> +    closesocket(fd);
> +#endif
> +  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> +    MG_ERROR(("%lu OOM", lsn->id));
> +    closesocket(fd);
>     } else {
> -    c->pfn_data = (void *) (processed | deleted);  // if it is set
> +    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> +    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> +    c->fd = S2PTR(fd);
> +    MG_EPOLL_ADD(c);
> +    mg_set_non_blocking_mode(FD(c));
> +    setsockopts(c);
> +    c->is_accepted = 1;
> +    c->is_hexdumping = lsn->is_hexdumping;
> +    c->loc = lsn->loc;
> +    c->pfn = lsn->pfn;
> +    c->pfn_data = lsn->pfn_data;
> +    c->fn = lsn->fn;
> +    c->fn_data = lsn->fn_data;
> +    MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port,
> +              &c->rem, mg_print_ip_port, &c->loc));
> +    mg_call(c, MG_EV_OPEN, NULL);
> +    mg_call(c, MG_EV_ACCEPT, NULL);
>     }
>   }
>
> -static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
> -  if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> -    struct mg_http_message hm;
> -    // mg_hexdump(c->recv.buf, c->recv.len);
> -    while (c->recv.buf != NULL && c->recv.len > 0) {
> -      bool next = false;
> -      int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> -      if (hlen < 0) {
> -        mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
> -        break;
> -      }
> -      if (c->is_resp) break;           // Response is still generated
> -      if (hlen == 0) break;            // Request is not buffered yet
> -      if (ev == MG_EV_CLOSE) {         // If client did not set Content-Length
> -        hm.message.len = c->recv.len;  // and closes now, deliver a MSG
> -        hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
> -      }
> -      if (mg_is_chunked(&hm)) {
> -        deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
> -      } else {
> -        deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
> -      }
> -      if (next) continue;  // Chunks & request were deleted
> -      //  Chunk events are delivered. If we have full body, deliver MSG
> -      if (c->recv.len < hm.message.len) break;
> -      if (c->is_accepted) c->is_resp = 1;  // Start generating response
> -      mg_call(c, MG_EV_HTTP_MSG, &hm);     // User handler can clear is_resp
> -      mg_iobuf_del(&c->recv, 0, hm.message.len);
> -    }
> -  }
> -  (void) evd, (void) fnd;
> +static bool can_read(const struct mg_connection *c) {
> +  return c->is_full == false;
>   }
>
> -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url,
> -                                      mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> -  if (c != NULL) c->pfn = http_cb;
> -  return c;
> +static bool can_write(const struct mg_connection *c) {
> +  return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
>   }
>
> -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
> -                                     mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> -  if (c != NULL) c->pfn = http_cb;
> -  return c;
> +static bool skip_iotest(const struct mg_connection *c) {
> +  return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) ||
> +         (can_read(c) == false && can_write(c) == false);
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/iobuf.c"
> -#endif
> -
> -
> -
> -
> -// Not using memset for zeroing memory, cause it can be dropped by compiler
> -// See https://github.com/cesanta/mongoose/pull/1265
> -static void zeromem(volatile unsigned char *buf, size_t len) {
> -  if (buf != NULL) {
> -    while (len--) *buf++ = 0;
> +static void mg_iotest(struct mg_mgr *mgr, int ms) {
> +#if MG_ENABLE_FREERTOS_TCP
> +  struct mg_connection *c;
> +  for (c = mgr->conns; c != NULL; c = c->next) {
> +    c->is_readable = c->is_writable = 0;
> +    if (skip_iotest(c)) continue;
> +    if (can_read(c))
> +      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> +    if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> +    if (c->is_closing) ms = 1;
>     }
> -}
> -
> -static size_t roundup(size_t size, size_t align) {
> -  return align == 0 ? size : (size + align - 1) / align * align;
> -}
> -
> -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> -  int ok = 1;
> -  new_size = roundup(new_size, io->align);
> -  if (new_size == 0) {
> -    zeromem(io->buf, io->size);
> -    free(io->buf);
> -    io->buf = NULL;
> -    io->len = io->size = 0;
> -  } else if (new_size != io->size) {
> -    // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
> -    // porting to some obscure platforms like FreeRTOS
> -    void *p = calloc(1, new_size);
> -    if (p != NULL) {
> -      size_t len = new_size < io->len ? new_size : io->len;
> -      if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> -      zeromem(io->buf, io->size);
> -      free(io->buf);
> -      io->buf = (unsigned char *) p;
> -      io->size = new_size;
> +  FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> +  for (c = mgr->conns; c != NULL; c = c->next) {
> +    EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> +    c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> +    c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> +    if (c->fd != MG_INVALID_SOCKET)
> +      FreeRTOS_FD_CLR(c->fd, mgr->ss,
> +                      eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> +  }
> +#elif MG_ENABLE_EPOLL
> +  size_t max = 1;
> +  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> +    c->is_readable = c->is_writable = 0;
> +    if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
> +    if (can_write(c)) MG_EPOLL_MOD(c, 1);
> +    if (c->is_closing) ms = 1;
> +    max++;
> +  }
> +  struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));
> +  int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> +  for (int i = 0; i < n; i++) {
> +    struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
> +    if (evs[i].events & EPOLLERR) {
> +      mg_error(c, "socket error");
> +    } else if (c->is_readable == 0) {
> +      bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> +      bool wr = evs[i].events & EPOLLOUT;
> +      c->is_readable = can_read(c) && rd ? 1U : 0;
> +      c->is_writable = can_write(c) && wr ? 1U : 0;
> +      if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> +    }
> +  }
> +  (void) skip_iotest;
> +#elif MG_ENABLE_POLL
> +  nfds_t n = 0;
> +  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
> +  struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> +  memset(fds, 0, n * sizeof(fds[0]));
> +  n = 0;
> +  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> +    c->is_readable = c->is_writable = 0;
> +    if (skip_iotest(c)) {
> +      // Socket not valid, ignore
> +    } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> +      ms = 1;  // Don't wait if TLS is ready
>       } else {
> -      ok = 0;
> -      MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
> +      fds[n].fd = FD(c);
> +      if (can_read(c)) fds[n].events |= POLLIN;
> +      if (can_write(c)) fds[n].events |= POLLOUT;
> +      if (c->is_closing) ms = 1;
> +      n++;
>       }
>     }
> -  return ok;
> -}
> -
> -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> -  io->buf = NULL;
> -  io->align = align;
> -  io->size = io->len = 0;
> -  return mg_iobuf_resize(io, size);
> -}
>
> -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
> -                    size_t len) {
> -  size_t new_size = roundup(io->len + len, io->align);
> -  mg_iobuf_resize(io, new_size);      // Attempt to resize
> -  if (new_size != io->size) len = 0;  // Resize failure, append nothing
> -  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
> -  if (buf != NULL) memmove(io->buf + ofs, buf, len);
> -  if (ofs > io->len) io->len += ofs - io->len;
> -  io->len += len;
> -  return len;
> -}
> +  // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> +  if (poll(fds, n, ms) < 0) {
> +#if MG_ARCH == MG_ARCH_WIN32
> +    if (n == 0) Sleep(ms);  // On Windows, poll fails if no sockets
> +#endif
> +    memset(fds, 0, n * sizeof(fds[0]));
> +  }
> +  n = 0;
> +  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> +    if (skip_iotest(c)) {
> +      // Socket not valid, ignore
> +    } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> +      c->is_readable = 1;
> +    } else {
> +      if (fds[n].revents & POLLERR) {
> +        mg_error(c, "socket error");
> +      } else {
> +        c->is_readable =
> +            (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> +        c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> +        if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> +      }
> +      n++;
> +    }
> +  }
> +#else
> +  struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, *tvp;
> +  struct mg_connection *c;
> +  fd_set rset, wset, eset;
> +  MG_SOCKET_TYPE maxfd = 0;
> +  int rc;
>
> -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> -  if (ofs > io->len) ofs = io->len;
> -  if (ofs + len > io->len) len = io->len - ofs;
> -  if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
> -  if (io->buf) zeromem(io->buf + io->len - len, len);
> -  io->len -= len;
> -  return len;
> -}
> +  FD_ZERO(&rset);
> +  FD_ZERO(&wset);
> +  FD_ZERO(&eset);
> +  tvp = ms < 0 ? NULL : &tv;
> +  for (c = mgr->conns; c != NULL; c = c->next) {
> +    c->is_readable = c->is_writable = 0;
> +    if (skip_iotest(c)) continue;
> +    FD_SET(FD(c), &eset);
> +    if (can_read(c)) FD_SET(FD(c), &rset);
> +    if (can_write(c)) FD_SET(FD(c), &wset);
> +    if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
> +    if (FD(c) > maxfd) maxfd = FD(c);
> +    if (c->is_closing) ms = 1;
> +  }
>
> -void mg_iobuf_free(struct mg_iobuf *io) {
> -  mg_iobuf_resize(io, 0);
> -}
> +  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) {
> +#if MG_ARCH == MG_ARCH_WIN32
> +    if (maxfd == 0) Sleep(ms);  // On Windows, select fails if no sockets
> +#else
> +    MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
> +#endif
> +    FD_ZERO(&rset);
> +    FD_ZERO(&wset);
> +    FD_ZERO(&eset);
> +  }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/json.c"
> +  for (c = mgr->conns; c != NULL; c = c->next) {
> +    if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> +      mg_error(c, "socket error");
> +    } else {
> +      c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
> +      c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
> +      if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> +    }
> +  }
>   #endif
> +}
>
> +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
> +  socklen_t n = sizeof(usa[0].sin);
> +  bool success = false;
>
> +  sp[0] = sp[1] = MG_INVALID_SOCKET;
> +  (void) memset(&usa[0], 0, sizeof(usa[0]));
> +  usa[0].sin.sin_family = AF_INET;
> +  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U);  // 127.0.0.1
> +  usa[1] = usa[0];
>
> -
> -static const char *escapeseq(int esc) {
> -  return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> +  if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> +      (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> +      bind(sp[0], &usa[0].sa, n) == 0 &&          //
> +      bind(sp[1], &usa[1].sa, n) == 0 &&          //
> +      getsockname(sp[0], &usa[0].sa, &n) == 0 &&  //
> +      getsockname(sp[1], &usa[1].sa, &n) == 0 &&  //
> +      connect(sp[0], &usa[1].sa, n) == 0 &&       //
> +      connect(sp[1], &usa[0].sa, n) == 0) {       //
> +    success = true;
> +  }
> +  if (!success) {
> +    if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> +    if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> +    sp[0] = sp[1] = MG_INVALID_SOCKET;
> +  }
> +  return success;
>   }
>
> -static char json_esc(int c, int esc) {
> -  const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> -  for (p = esc1; *p != '\0'; p++) {
> -    if (*p == c) return esc2[p - esc1];
> +// mg_wakeup() event handler
> +static void wufn(struct mg_connection *c, int ev, void *ev_data) {
> +  if (ev == MG_EV_READ) {
> +    unsigned long *id = (unsigned long *) c->recv.buf;
> +    // MG_INFO(("Got data"));
> +    // mg_hexdump(c->recv.buf, c->recv.len);
> +    if (c->recv.len >= sizeof(*id)) {
> +      struct mg_connection *t;
> +      for (t = c->mgr->conns; t != NULL; t = t->next) {
> +        if (t->id == *id) {
> +          struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
> +                                        c->recv.len - sizeof(*id));
> +          mg_call(t, MG_EV_WAKEUP, &data);
> +        }
> +      }
> +    }
> +    c->recv.len = 0;  // Consume received data
> +  } else if (ev == MG_EV_CLOSE) {
> +    closesocket(c->mgr->pipe);         // When we're closing, close the other
> +    c->mgr->pipe = MG_INVALID_SOCKET;  // side of the socketpair, too
>     }
> -  return 0;
> +  (void) ev_data;
>   }
>
> -static int mg_pass_string(const char *s, int len) {
> -  int i;
> -  for (i = 0; i < len; i++) {
> -    if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> -      i++;
> -    } else if (s[i] == '\0') {
> -      return MG_JSON_INVALID;
> -    } else if (s[i] == '"') {
> -      return i;
> +bool mg_wakeup_init(struct mg_mgr *mgr) {
> +  bool ok = false;
> +  if (mgr->pipe == MG_INVALID_SOCKET) {
> +    union usa usa[2];
> +    MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> +    struct mg_connection *c = NULL;
> +    if (!mg_socketpair(sp, usa)) {
> +      MG_ERROR(("Cannot create socket pair"));
> +    } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) == NULL) {
> +      closesocket(sp[0]);
> +      closesocket(sp[1]);
> +      sp[0] = sp[1] = MG_INVALID_SOCKET;
> +    } else {
> +      tomgaddr(&usa[0], &c->rem, false);
> +      MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
> +      mgr->pipe = sp[0];
> +      ok = true;
>       }
>     }
> -  return MG_JSON_INVALID;
> +  return ok;
>   }
>
> -static double mg_atod(const char *p, int len, int *numlen) {
> -  double d = 0.0;
> -  int i = 0, sign = 1;
> -
> -  // Sign
> -  if (i < len && *p == '-') {
> -    sign = -1, i++;
> -  } else if (i < len && *p == '+') {
> -    i++;
> +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
> +               size_t len) {
> +  if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
> +    char *extended_buf = (char *) alloca(len + sizeof(conn_id));
> +    memcpy(extended_buf, &conn_id, sizeof(conn_id));
> +    memcpy(extended_buf + sizeof(conn_id), buf, len);
> +    send(mgr->pipe, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING);
> +    return true;
>     }
> +  return false;
> +}
>
> -  // Decimal
> -  for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> -    d *= 10.0;
> -    d += p[i] - '0';
> -  }
> -  d *= sign;
> +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> +  struct mg_connection *c, *tmp;
> +  uint64_t now;
>
> -  // Fractional
> -  if (i < len && p[i] == '.') {
> -    double frac = 0.0, base = 0.1;
> -    i++;
> -    for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> -      frac += base * (p[i] - '0');
> -      base /= 10.0;
> +  mg_iotest(mgr, ms);
> +  now = mg_millis();
> +  mg_timer_poll(&mgr->timers, now);
> +
> +  for (c = mgr->conns; c != NULL; c = tmp) {
> +    bool is_resp = c->is_resp;
> +    tmp = c->next;
> +    mg_call(c, MG_EV_POLL, &now);
> +    if (is_resp && !c->is_resp) {
> +      long n = 0;
> +      mg_call(c, MG_EV_READ, &n);
> +    }
> +    MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
> +                c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
> +                c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
> +                c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
> +                c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
> +    if (c->is_resolving || c->is_closing) {
> +      // Do nothing
> +    } else if (c->is_listening && c->is_udp == 0) {
> +      if (c->is_readable) accept_conn(mgr, c);
> +    } else if (c->is_connecting) {
> +      if (c->is_readable || c->is_writable) connect_conn(c);
> +      //} else if (c->is_tls_hs) {
> +      //  if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> +    } else {
> +      if (c->is_readable) read_conn(c);
> +      if (c->is_writable) write_conn(c);
>       }
> -    d += frac * sign;
> -  }
>
> -  // Exponential
> -  if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> -    int j, exp = 0, minus = 0;
> -    i++;
> -    if (i < len && p[i] == '-') minus = 1, i++;
> -    if (i < len && p[i] == '+') i++;
> -    while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> -      exp = exp * 10 + (p[i++] - '0');
> -    if (minus) exp = -exp;
> -    for (j = 0; j < exp; j++) d *= 10.0;
> -    for (j = 0; j < -exp; j++) d /= 10.0;
> +    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> +    if (c->is_closing) close_conn(c);
>     }
> -
> -  if (numlen != NULL) *numlen = i;
> -  return d;
>   }
> +#endif
>
> -int mg_json_get(struct mg_str json, const char *path, int *toklen) {
> -  const char *s = json.ptr;
> -  int len = (int) json.len;
> -  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
> -  unsigned char nesting[MG_JSON_MAX_DEPTH];
> -  int i = 0;             // Current offset in `s`
> -  int j = 0;             // Offset in `s` we're looking for (return value)
> -  int depth = 0;         // Current depth (nesting level)
> -  int ed = 0;            // Expected depth
> -  int pos = 1;           // Current position in `path`
> -  int ci = -1, ei = -1;  // Current and expected index in array
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/ssi.c"
> +#endif
>
> -  if (toklen) *toklen = 0;
> -  if (path[0] != '$') return MG_JSON_INVALID;
>
> -#define MG_CHECKRET(x)                                  \
> -  do {                                                  \
> -    if (depth == ed && path[pos] == '\0' && ci == ei) { \
> -      if (toklen) *toklen = i - j + 1;                  \
> -      return j;                                         \
> -    }                                                   \
> -  } while (0)
>
> -// In the ascii table, the distance between `[` and `]` is 2.
> -// Ditto for `{` and `}`. Hence +2 in the code below.
> -#define MG_EOO(x)                                            \
> -  do {                                                       \
> -    if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND;   \
> -    if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> -    depth--;                                                 \
> -    MG_CHECKRET(x);                                          \
> -  } while (0)
>
> -  for (i = 0; i < len; i++) {
> -    unsigned char c = ((unsigned char *) s)[i];
> -    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> -    switch (expecting) {
> -      case S_VALUE:
> -        // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
> -        if (depth == ed) j = i;
> -        if (c == '{') {
> -          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> -          if (depth == ed && path[pos] == '.' && ci == ei) {
> -            // If we start the object, reset array indices
> -            ed++, pos++, ci = ei = -1;
> -          }
> -          nesting[depth++] = c;
> -          expecting = S_KEY;
> -          break;
> -        } else if (c == '[') {
> -          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> -          if (depth == ed && path[pos] == '[' && ei == ci) {
> -            ed++, pos++, ci = 0;
> -            for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> -              ei *= 10;
> -              ei += path[pos] - '0';
> -            }
> -            if (path[pos] != 0) pos++;
> -          }
> -          nesting[depth++] = c;
> -          break;
> -        } else if (c == ']' && depth > 0) {  // Empty array
> -          MG_EOO(']');
> -        } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
> -          i += 3;
> -        } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
> -          i += 3;
> -        } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
> -          i += 4;
> -        } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> -          int numlen = 0;
> -          mg_atod(&s[i], len - i, &numlen);
> -          i += numlen - 1;
> -        } else if (c == '"') {
> -          int n = mg_pass_string(&s[i + 1], len - i - 1);
> -          if (n < 0) return n;
> -          i += n + 1;
> -        } else {
> -          return MG_JSON_INVALID;
> -        }
> -        MG_CHECKRET('V');
> -        if (depth == ed && ei >= 0) ci++;
> -        expecting = S_COMMA_OR_EOO;
> -        break;
> +#ifndef MG_MAX_SSI_DEPTH
> +#define MG_MAX_SSI_DEPTH 5
> +#endif
>
> -      case S_KEY:
> -        if (c == '"') {
> -          int n = mg_pass_string(&s[i + 1], len - i - 1);
> -          if (n < 0) return n;
> -          if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> -          if (depth < ed) return MG_JSON_NOT_FOUND;
> -          if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> -          // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
> -          //  &s[i + 1], n, depth, ed);
> -          // NOTE(cpq): in the check sequence below is important.
> -          // strncmp() must go first: it fails fast if the remaining length of
> -          // the path is smaller than `n`.
> -          if (depth == ed && path[pos - 1] == '.' &&
> -              strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> -              (path[pos + n] == '\0' || path[pos + n] == '.' ||
> -               path[pos + n] == '[')) {
> -            pos += n;
> -          }
> -          i += n + 1;
> -          expecting = S_COLON;
> -        } else if (c == '}') {  // Empty object
> -          MG_EOO('}');
> -          expecting = S_COMMA_OR_EOO;
> -        } else {
> -          return MG_JSON_INVALID;
> -        }
> -        break;
> +#ifndef MG_SSI_BUFSIZ
> +#define MG_SSI_BUFSIZ 1024
> +#endif
>
> -      case S_COLON:
> -        if (c == ':') {
> -          expecting = S_VALUE;
> +#if MG_ENABLE_SSI
> +static char *mg_ssi(const char *path, const char *root, int depth) {
> +  struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> +  FILE *fp = fopen(path, "rb");
> +  if (fp != NULL) {
> +    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> +    int ch, intag = 0;
> +    size_t len = 0;
> +    buf[0] = arg[0] = '\0';
> +    while ((ch = fgetc(fp)) != EOF) {
> +      if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
> +        buf[len++] = (char) (ch & 0xff);
> +        buf[len] = '\0';
> +        if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> +          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> +              *p = (char *) path + strlen(path), *data;
> +          while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> +          mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
> +          if (depth < MG_MAX_SSI_DEPTH &&
> +              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> +            mg_iobuf_add(&b, b.len, data, strlen(data));
> +            free(data);
> +          } else {
> +            MG_ERROR(("%s: file=%s error or too deep", path, arg));
> +          }
> +        } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> +          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> +          mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> +          if (depth < MG_MAX_SSI_DEPTH &&
> +              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> +            mg_iobuf_add(&b, b.len, data, strlen(data));
> +            free(data);
> +          } else {
> +            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> +          }
>           } else {
> -          return MG_JSON_INVALID;
> +          // Unknown SSI tag
> +          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> +          mg_iobuf_add(&b, b.len, buf, len);
>           }
> -        break;
> -
> -      case S_COMMA_OR_EOO:
> -        if (depth <= 0) {
> -          return MG_JSON_INVALID;
> -        } else if (c == ',') {
> -          expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> -        } else if (c == ']' || c == '}') {
> -          MG_EOO('O');
> -          if (depth == ed && ei >= 0) ci++;
> -        } else {
> -          return MG_JSON_INVALID;
> +        intag = 0;
> +        len = 0;
> +      } else if (ch == '<') {
> +        intag = 1;
> +        if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> +        len = 0;
> +        buf[len++] = (char) (ch & 0xff);
> +      } else if (intag) {
> +        if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> +          intag = 0;
> +        } else if (len >= sizeof(buf) - 2) {
> +          MG_ERROR(("%s: SSI tag is too large", path));
> +          len = 0;
>           }
> -        break;
> +        buf[len++] = (char) (ch & 0xff);
> +      } else {
> +        buf[len++] = (char) (ch & 0xff);
> +        if (len >= sizeof(buf)) {
> +          mg_iobuf_add(&b, b.len, buf, len);
> +          len = 0;
> +        }
> +      }
>       }
> +    if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> +    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1);  // nul-terminate
> +    fclose(fp);
>     }
> -  return MG_JSON_NOT_FOUND;
> +  (void) depth;
> +  (void) root;
> +  return (char *) b.buf;
>   }
>
> -bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
> -  int n, toklen, found = 0;
> -  if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> -      (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) {
> -    if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
> -    found = 1;
> -  }
> -  return found;
> +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> +                       const char *fullpath) {
> +  const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
> +  char *data = mg_ssi(fullpath, root, 0);
> +  mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> +  free(data);
> +}
> +#else
> +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> +                       const char *fullpath) {
> +  mg_http_reply(c, 501, NULL, "SSI not enabled");
> +  (void) root, (void) fullpath;
>   }
> +#endif
>
> -bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
> -  int found = 0, off = mg_json_get(json, path, NULL);
> -  if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
> -    if (v != NULL) *v = json.ptr[off] == 't';
> -    found = 1;
> -  }
> -  return found;
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/str.c"
> +#endif
> +
> +
> +struct mg_str mg_str_s(const char *s) {
> +  struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
> +  return str;
>   }
>
> -static bool json_unescape(const char *s, size_t len, char *to, size_t n) {
> -  size_t i, j;
> -  for (i = 0, j = 0; i < len && j < n; i++, j++) {
> -    if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
> -      //  \uXXXX escape. We could process a simple one-byte chars
> -      // \u00xx from the ASCII range. More complex chars would require
> -      // dragging in a UTF8 library, which is too much for us
> -      if (s[i + 2] != '0' || s[i + 3] != '0') return false;  // Give up
> -      ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2);
> +struct mg_str mg_str_n(const char *s, size_t n) {
> +  struct mg_str str = {(char *) s, n};
> +  return str;
> +}
>
> -      i += 5;
> -    } else if (s[i] == '\\' && i + 1 < len) {
> -      char c = json_esc(s[i + 1], 0);
> -      if (c == 0) return false;
> -      to[j] = c;
> -      i++;
> -    } else {
> -      to[j] = s[i];
> -    }
> +static int mg_tolc(char c) {
> +  return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
> +}
> +
> +int mg_casecmp(const char *s1, const char *s2) {
> +  int diff = 0;
> +  do {
> +    int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
> +    diff = c - d;
> +  } while (diff == 0 && s1[-1] != '\0');
> +  return diff;
> +}
> +
> +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> +  size_t i = 0;
> +  while (i < str1.len && i < str2.len) {
> +    int c1 = str1.buf[i];
> +    int c2 = str2.buf[i];
> +    if (c1 < c2) return -1;
> +    if (c1 > c2) return 1;
> +    i++;
>     }
> -  if (j >= n) return false;
> -  if (n > 0) to[j] = '\0';
> -  return true;
> +  if (i < str1.len) return 1;
> +  if (i < str2.len) return -1;
> +  return 0;
>   }
>
> -char *mg_json_get_str(struct mg_str json, const char *path) {
> -  char *result = NULL;
> -  int len = 0, off = mg_json_get(json, path, &len);
> -  if (off >= 0 && len > 1 && json.ptr[off] == '"') {
> -    if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> -        !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
> -                       (size_t) len)) {
> -      free(result);
> -      result = NULL;
> -    }
> +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) {
> +  size_t i = 0;
> +  while (i < str1.len && i < str2.len) {
> +    int c1 = mg_tolc(str1.buf[i]);
> +    int c2 = mg_tolc(str2.buf[i]);
> +    if (c1 < c2) return -1;
> +    if (c1 > c2) return 1;
> +    i++;
>     }
> -  return result;
> +  if (i < str1.len) return 1;
> +  if (i < str2.len) return -1;
> +  return 0;
>   }
>
> -char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
> -  char *result = NULL;
> -  int len = 0, off = mg_json_get(json, path, &len);
> -  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> -      (result = (char *) calloc(1, (size_t) len)) != NULL) {
> -    int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
> -    if (slen != NULL) *slen = k;
> +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
> +  size_t i = 0, j = 0, ni = 0, nj = 0;
> +  if (caps) caps->buf = NULL, caps->len = 0;
> +  while (i < p.len || j < s.len) {
> +    if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] == p.buf[i])) {
> +      if (caps == NULL) {
> +      } else if (p.buf[i] == '?') {
> +        caps->buf = &s.buf[j], caps->len = 1;     // Finalize `?` cap
> +        caps++, caps->buf = NULL, caps->len = 0;  // Init next cap
> +      } else if (caps->buf != NULL && caps->len == 0) {
> +        caps->len = (size_t) (&s.buf[j] - caps->buf);  // Finalize current cap
> +        caps++, caps->len = 0, caps->buf = NULL;       // Init next cap
> +      }
> +      i++, j++;
> +    } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
> +      if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j];  // Init cap
> +      ni = i++, nj = j + 1;
> +    } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' || s.buf[j] != '/')) {
> +      i = ni, j = nj;
> +      if (caps && caps->buf == NULL && caps->len == 0) {
> +        caps--, caps->len = 0;  // Restart previous cap
> +      }
> +    } else {
> +      return false;
> +    }
>     }
> -  return result;
> +  if (caps && caps->buf && caps->len == 0) {
> +    caps->len = (size_t) (&s.buf[j] - caps->buf);
> +  }
> +  return true;
>   }
>
> -char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
> -  char *result = NULL;
> -  int len = 0, off = mg_json_get(json, path, &len);
> -  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> -      (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> -    mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result);
> -    result[len / 2 - 1] = '\0';
> -    if (slen != NULL) *slen = len / 2 - 1;
> +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char sep) {
> +  if (s.len == 0 || s.buf == NULL) {
> +    return false;  // Empty string, nothing to span - fail
> +  } else {
> +    size_t len = 0;
> +    while (len < s.len && s.buf[len] != sep) len++;  // Find separator
> +    if (a) *a = mg_str_n(s.buf, len);                // Init a
> +    if (b) *b = mg_str_n(s.buf + len, s.len - len);  // Init b
> +    if (b && len < s.len) b->buf++, b->len--;        // Skip separator
> +    return true;
>     }
> -  return result;
>   }
>
> -long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
> -  double dv;
> -  long result = dflt;
> -  if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> -  return result;
> +bool mg_str_to_num(struct mg_str str, int base, void *val, size_t val_len) {
> +  size_t i = 0, ndigits = 0;
> +  uint64_t max = val_len == sizeof(uint8_t)   ? 0xFF
> +                 : val_len == sizeof(uint16_t) ? 0xFFFF
> +                 : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
> +                                : (uint64_t) ~0;
> +  uint64_t result = 0;
> +  if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return false;
> +  if (base == 0 && str.len >= 2) {
> +    if (str.buf[i] == '0') {
> +      i++;
> +      base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
> +      if (base != 10) ++i;
> +    } else {
> +      base = 10;
> +    }
> +  }
> +  switch (base) {
> +    case 2:
> +      while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
> +        uint64_t digit = (uint64_t) (str.buf[i] - '0');
> +        if (result > max/2) return false;  // Overflow
> +        result *= 2;
> +        if (result > max - digit) return false;  // Overflow
> +        result += digit;
> +        i++, ndigits++;
> +      }
> +      break;
> +    case 10:
> +      while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> +        uint64_t digit = (uint64_t) (str.buf[i] - '0');
> +        if (result > max/10) return false;  // Overflow
> +        result *= 10;
> +        if (result > max - digit) return false;  // Overflow
> +        result += digit;
> +        i++, ndigits++;
> +    }
> +      break;
> +    case 16:
> +      while (i < str.len) {
> +        char c = str.buf[i];
> +        uint64_t digit = (c >= '0' && c <= '9')   ? (uint64_t) (c - '0')
> +                         : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
> +                         : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
> +                                                  : (uint64_t) ~0;
> +        if (digit == (uint64_t) ~0) break;
> +        if (result > max/16) return false;  // Overflow
> +        result *= 16;
> +        if (result > max - digit) return false;  // Overflow
> +        result += digit;
> +        i++, ndigits++;
> +      }
> +      break;
> +    default:
> +      return false;
> +  }
> +  if (ndigits == 0) return false;
> +  if (i != str.len) return false;
> +  if (val_len == 1) {
> +    *((uint8_t *) val) = (uint8_t) result;
> +  } else if (val_len == 2) {
> +    *((uint16_t *) val) = (uint16_t) result;
> +  } else if (val_len == 4) {
> +    *((uint32_t *) val) = (uint32_t) result;
> +  } else {
> +    *((uint64_t *) val) = (uint64_t) result;
> +  }
> +  return true;
>   }
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/log.c"
> +#line 1 "src/timer.c"
>   #endif
>
>
>
> +#define MG_TIMER_CALLED 4
>
> -
> -static void default_logger(char c, void *param) {
> -  putchar(c);
> -  (void) c, (void) param;
> -}
> -
> -static int s_level = MG_LL_INFO;
> -static mg_pfn_t s_log_func = default_logger;
> -static void *s_log_func_param = NULL;
> -
> -void mg_log_set_fn(mg_pfn_t fn, void *param) {
> -  s_log_func = fn;
> -  s_log_func_param = param;
> -}
> -
> -static void logc(unsigned char c) {
> -  s_log_func((char) c, s_log_func_param);
> -}
> -
> -static void logs(const char *buf, size_t len) {
> -  size_t i;
> -  for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> -}
> -
> -void mg_log_set(int log_level) {
> -  MG_DEBUG(("Setting log level to %d", log_level));
> -  s_level = log_level;
> -}
> -
> -bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
> -  if (level <= s_level) {
> -    const char *p = strrchr(file, '/');
> -    char buf[41];
> -    size_t n;
> -    if (p == NULL) p = strrchr(file, '\\');
> -    n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(), level,
> -                    p == NULL ? file : p + 1, line, fname);
> -    if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> -    while (n < sizeof(buf)) buf[n++] = ' ';
> -    logs(buf, n - 1);
> -    return true;
> -  } else {
> -    return false;
> -  }
> +void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
> +                   unsigned flags, void (*fn)(void *), void *arg) {
> +  t->id = 0, t->period_ms = ms, t->expire = 0;
> +  t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> +  *head = t;
>   }
>
> -void mg_log(const char *fmt, ...) {
> -  va_list ap;
> -  va_start(ap, fmt);
> -  mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> -  va_end(ap);
> -  logc((unsigned char) '\n');
> +void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> +  while (*head && *head != t) head = &(*head)->next;
> +  if (*head) *head = t->next;
>   }
>
> -static unsigned char nibble(unsigned c) {
> -  return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> +// t: expiration time, prd: period, now: current time. Return true if expired
> +bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> +  if (now + prd < *t) *t = 0;                    // Time wrapped? Reset timer
> +  if (*t == 0) *t = now + prd;                   // Firt poll? Set expiration
> +  if (*t > now) return false;                    // Not expired yet, return
> +  *t = (now - *t) > prd ? now + prd : *t + prd;  // Next expiration time
> +  return true;                                   // Expired, return true
>   }
>
> -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> -void mg_hexdump(const void *buf, size_t len) {
> -  const unsigned char *p = (const unsigned char *) buf;
> -  unsigned char ascii[16], alen = 0;
> -  size_t i;
> -  for (i = 0; i < len; i++) {
> -    if ((i % 16) == 0) {
> -      // Print buffered ascii chars
> -      if (i > 0) logs("  ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0;
> -      // Print hex address, then \t
> -      logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> -          logc(nibble((i >> 4) & 15)), logc('0'), logs("   ", 3);
> -    }
> -    logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15));  // Two nibbles, e.g. c5
> -    logc(' ');                                         // Space after hex number
> -    ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.';        // Add to the ascii buf
> +void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> +  struct mg_timer *t, *tmp;
> +  for (t = *head; t != NULL; t = tmp) {
> +    bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> +                !(t->flags & MG_TIMER_CALLED);  // Handle MG_TIMER_NOW only once
> +    bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> +    tmp = t->next;
> +    if (!once && !expired) continue;
> +    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
> +      t->fn(t->arg);
> +    }
> +    t->flags |= MG_TIMER_CALLED;
>     }
> -  while (alen < 16) logs("   ", 3), ascii[alen++] = ' ';
> -  logs("  ", 2), logs((char *) ascii, 16), logc('\n');
>   }
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/md5.c"
> +#line 1 "src/tls_aes128.c"
>   #endif
> +/******************************************************************************
> + *
> + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
> + *
> + * This is a simple and straightforward implementation of the AES Rijndael
> + * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
> + * of this work was correctness & accuracy.  It is written in 'C' without any
> + * particular focus upon optimization or speed. It should be endian (memory
> + * byte order) neutral since the few places that care are handled explicitly.
> + *
> + * This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
> + *
> + * It is intended for general purpose use, but was written in support of GRC's
> + * reference implementation of the SQRL (Secure Quick Reliable Login) client.
> + *
> + * See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> + *
> + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
> + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
> + *
> + *******************************************************************************/
> +
> +/******************************************************************************/
> +#define AES_DECRYPTION 1  // whether AES decryption is supported
> +/******************************************************************************/
> +
> +#define MG_ENCRYPT 1  // specify whether we're encrypting
> +#define MG_DECRYPT 0  // or decrypting
> +
> +
> +
> +
> +
> +#if MG_TLS == MG_TLS_BUILTIN
> +/******************************************************************************
> + *  AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
> + ******************************************************************************/
> +static void aes_init_keygen_tables(void);
> +
> +/******************************************************************************
> + *  AES_SETKEY : called to expand the key for encryption or decryption
> + ******************************************************************************/
> +static int aes_setkey(aes_context *ctx,  // pointer to context
> +                      int mode,          // 1 or 0 for Encrypt/Decrypt
> +                      const uchar *key,  // AES input key
> +                      uint keysize);  // size in bytes (must be 16, 24, 32 for
> +                                      // 128, 192 or 256-bit keys respectively)
> +                                      // returns 0 for success
> +
> +/******************************************************************************
> + *  AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data
> + ******************************************************************************/
> +static int aes_cipher(aes_context *ctx,       // pointer to context
> +                      const uchar input[16],  // 128-bit block to en/decipher
> +                      uchar output[16]);      // 128-bit output result block
> +                                              // returns 0 for success
> +
> +/******************************************************************************
> + *  GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx
> + ******************************************************************************/
> +typedef struct {
> +  int mode;             // cipher direction: encrypt/decrypt
> +  uint64_t len;         // cipher data length processed so far
> +  uint64_t add_len;     // total add data length
> +  uint64_t HL[16];      // precalculated lo-half HTable
> +  uint64_t HH[16];      // precalculated hi-half HTable
> +  uchar base_ectr[16];  // first counter-mode cipher output for tag
> +  uchar y[16];          // the current cipher-input IV|Counter value
> +  uchar buf[16];        // buf working value
> +  aes_context aes_ctx;  // cipher context used
> +} gcm_context;
> +
> +/******************************************************************************
> + *  GCM_SETKEY : sets the GCM (and AES) keying material for use
> + ******************************************************************************/
> +static int gcm_setkey(
> +    gcm_context *ctx,   // caller-provided context ptr
> +    const uchar *key,   // pointer to cipher key
> +    const uint keysize  // size in bytes (must be 16, 24, 32 for
> +                        // 128, 192 or 256-bit keys respectively)
> +);                      // returns 0 for success
> +
> +/******************************************************************************
> + *
> + *  GCM_CRYPT_AND_TAG
> + *
> + *  This either encrypts or decrypts the user-provided data and, either
> + *  way, generates an authentication tag of the requested length. It must be
> + *  called with a GCM context whose key has already been set with GCM_SETKEY.
> + *
> + *  The user would typically call this explicitly to ENCRYPT a buffer of data
> + *  and optional associated data, and produce its an authentication tag.
> + *
> + *  To reverse the process the user would typically call the companion
> + *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
> + *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function
> + *  to perform its decryption and tag generation, which it then compares.
> + *
> + ******************************************************************************/
> +static int gcm_crypt_and_tag(
> +    gcm_context *ctx,    // gcm context with key already setup
> +    int mode,            // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
> +    const uchar *iv,     // pointer to the 12-byte initialization vector
> +    size_t iv_len,       // byte length if the IV. should always be 12
> +    const uchar *add,    // pointer to the non-ciphered additional data
> +    size_t add_len,      // byte length of the additional AEAD data
> +    const uchar *input,  // pointer to the cipher data source
> +    uchar *output,       // pointer to the cipher data destination
> +    size_t length,       // byte length of the cipher data
> +    uchar *tag,          // pointer to the tag to be generated
> +    size_t tag_len);     // byte length of the tag to be generated
> +
> +/******************************************************************************
> + *
> + *  GCM_START
> + *
> + *  Given a user-provided GCM context, this initializes it, sets the encryption
> + *  mode, and preprocesses the initialization vector and additional AEAD data.
> + *
> + ******************************************************************************/
> +static int gcm_start(
> +    gcm_context *ctx,  // pointer to user-provided GCM context
> +    int mode,          // MG_ENCRYPT (1) or MG_DECRYPT (0)
> +    const uchar *iv,   // pointer to initialization vector
> +    size_t iv_len,     // IV length in bytes (should == 12)
> +    const uchar *add,  // pointer to additional AEAD data (NULL if none)
> +    size_t add_len);   // length of additional AEAD data (bytes)
> +
> +/******************************************************************************
> + *
> + *  GCM_UPDATE
> + *
> + *  This is called once or more to process bulk plaintext or ciphertext data.
> + *  We give this some number of bytes of input and it returns the same number
> + *  of output bytes. If called multiple times (which is fine) all but the final
> + *  invocation MUST be called with length mod 16 == 0. (Only the final call can
> + *  have a partial block length of < 128 bits.)
> + *
> + ******************************************************************************/
> +static int gcm_update(gcm_context *ctx,  // pointer to user-provided GCM context
> +                      size_t length,     // length, in bytes, of data to process
> +                      const uchar *input,  // pointer to source data
> +                      uchar *output);      // pointer to destination data
> +
> +/******************************************************************************
> + *
> + *  GCM_FINISH
> + *
> + *  This is called once after all calls to GCM_UPDATE to finalize the GCM.
> + *  It performs the final GHASH to produce the resulting authentication TAG.
> + *
> + ******************************************************************************/
> +static int gcm_finish(
> +    gcm_context *ctx,  // pointer to user-provided GCM context
> +    uchar *tag,        // ptr to tag buffer - NULL if tag_len = 0
> +    size_t tag_len);   // length, in bytes, of the tag-receiving buf
> +
> +/******************************************************************************
> + *
> + *  GCM_ZERO_CTX
> + *
> + *  The GCM context contains both the GCM context and the AES context.
> + *  This includes keying and key-related material which is security-
> + *  sensitive, so it MUST be zeroed after use. This function does that.
> + *
> + ******************************************************************************/
> +static void gcm_zero_ctx(gcm_context *ctx);
> +
> +/******************************************************************************
> + *
> + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
> + *
> + * This is a simple and straightforward implementation of the AES Rijndael
> + * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
> + * of this work was correctness & accuracy.  It is written in 'C' without any
> + * particular focus upon optimization or speed. It should be endian (memory
> + * byte order) neutral since the few places that care are handled explicitly.
> + *
> + * This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
> + *
> + * It is intended for general purpose use, but was written in support of GRC's
> + * reference implementation of the SQRL (Secure Quick Reliable Login) client.
> + *
> + * See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> + *
> + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
> + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
> + *
> + *******************************************************************************/
> +
> +
> +
> +
> +static int aes_tables_inited = 0;  // run-once flag for performing key
> +                                   // expasion table generation (see below)
> +/*
> + *  The following static local tables must be filled-in before the first use of
> + *  the GCM or AES ciphers. They are used for the AES key expansion/scheduling
> + *  and once built are read-only and thread safe. The "gcm_initialize" function
> + *  must be called once during system initialization to populate these arrays
> + *  for subsequent use by the AES key scheduler. If they have not been built
> + *  before attempted use, an error will be returned to the caller.
> + *
> + *  NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since
> + *  GCM uses AES in counter-mode, where the AES cipher output is XORed with
> + *  the GCM input, we ONLY NEED AES encryption.  Thus, to save space AES
> + *  decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h.
> + */
> +// We always need our forward tables
> +static uchar FSb[256];     // Forward substitution box (FSb)
> +static uint32_t FT0[256];  // Forward key schedule assembly tables
> +static uint32_t FT1[256];
> +static uint32_t FT2[256];
> +static uint32_t FT3[256];
> +
> +#if AES_DECRYPTION         // We ONLY need reverse for decryption
> +static uchar RSb[256];     // Reverse substitution box (RSb)
> +static uint32_t RT0[256];  // Reverse key schedule assembly tables
> +static uint32_t RT1[256];
> +static uint32_t RT2[256];
> +static uint32_t RT3[256];
> +#endif /* AES_DECRYPTION */
> +
> +static uint32_t RCON[10];  // AES round constants
>
> +/*
> + * Platform Endianness Neutralizing Load and Store Macro definitions
> + * AES wants platform-neutral Little Endian (LE) byte ordering
> + */
> +#define GET_UINT32_LE(n, b, i)                                               \
> +  {                                                                          \
> +    (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) |           \
> +          ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] << 24); \
> +  }
>
> -
> -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> -
> -static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> -  if (MG_BIG_ENDIAN) {
> -    do {
> -      uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> -                   ((unsigned) buf[1] << 8 | buf[0]);
> -      *(uint32_t *) buf = t;
> -      buf += 4;
> -    } while (--longs);
> -  } else {
> -    (void) buf, (void) longs;  // Little endian. Do nothing
> +#define PUT_UINT32_LE(n, b, i)          \
> +  {                                     \
> +    (b)[(i)] = (uchar) ((n));           \
> +    (b)[(i) + 1] = (uchar) ((n) >> 8);  \
> +    (b)[(i) + 2] = (uchar) ((n) >> 16); \
> +    (b)[(i) + 3] = (uchar) ((n) >> 24); \
>     }
> -}
>
> -#define F1(x, y, z) (z ^ (x & (y ^ z)))
> -#define F2(x, y, z) F1(z, x, y)
> -#define F3(x, y, z) (x ^ y ^ z)
> -#define F4(x, y, z) (y ^ (x | ~z))
> +/*
> + *  AES forward and reverse encryption round processing macros
> + */
> +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3)          \
> +  {                                                         \
> +    X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
> +         FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF];   \
> +                                                            \
> +    X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
> +         FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF];   \
> +                                                            \
> +    X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
> +         FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF];   \
> +                                                            \
> +    X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
> +         FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF];   \
> +  }
>
> -#define MD5STEP(f, w, x, y, z, data, s) \
> -  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3)          \
> +  {                                                         \
> +    X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
> +         RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF];   \
> +                                                            \
> +    X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
> +         RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF];   \
> +                                                            \
> +    X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
> +         RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF];   \
> +                                                            \
> +    X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
> +         RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF];   \
> +  }
>
>   /*
> - * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
> - * initialization constants.
> + *  These macros improve the readability of the key
> + *  generation initialization code by collapsing
> + *  repetitive common operations into logical pieces.
>    */
> -void mg_md5_init(mg_md5_ctx *ctx) {
> -  ctx->buf[0] = 0x67452301;
> -  ctx->buf[1] = 0xefcdab89;
> -  ctx->buf[2] = 0x98badcfe;
> -  ctx->buf[3] = 0x10325476;
> +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
> +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
> +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
> +#define MIX(x, y)                     \
> +  {                                   \
> +    y = ((y << 1) | (y >> 7)) & 0xFF; \
> +    x ^= y;                           \
> +  }
> +#define CPY128     \
> +  {                \
> +    *RK++ = *SK++; \
> +    *RK++ = *SK++; \
> +    *RK++ = *SK++; \
> +    *RK++ = *SK++; \
> +  }
>
> -  ctx->bits[0] = 0;
> -  ctx->bits[1] = 0;
> -}
> +/******************************************************************************
> + *
> + *  AES_INIT_KEYGEN_TABLES
> + *
> + *  Fills the AES key expansion tables allocated above with their static
> + *  data. This is not "per key" data, but static system-wide read-only
> + *  table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once
> + *  at system initialization to setup the tables for all subsequent use.
> + *
> + ******************************************************************************/
> +void aes_init_keygen_tables(void) {
> +  int i, x, y, z;  // general purpose iteration and computation locals
> +  int pow[256];
> +  int log[256];
> +
> +  if (aes_tables_inited) return;
> +
> +  // fill the 'pow' and 'log' tables over GF(2^8)
> +  for (i = 0, x = 1; i < 256; i++) {
> +    pow[i] = x;
> +    log[x] = i;
> +    x = (x ^ XTIME(x)) & 0xFF;
> +  }
> +  // compute the round constants
> +  for (i = 0, x = 1; i < 10; i++) {
> +    RCON[i] = (uint32_t) x;
> +    x = XTIME(x) & 0xFF;
> +  }
> +  // fill the forward and reverse substitution boxes
> +  FSb[0x00] = 0x63;
> +#if AES_DECRYPTION  // whether AES decryption is supported
> +  RSb[0x63] = 0x00;
> +#endif /* AES_DECRYPTION */
> +
> +  for (i = 1; i < 256; i++) {
> +    x = y = pow[255 - log[i]];
> +    MIX(x, y);
> +    MIX(x, y);
> +    MIX(x, y);
> +    MIX(x, y);
> +    FSb[i] = (uchar) (x ^= 0x63);
> +#if AES_DECRYPTION  // whether AES decryption is supported
> +    RSb[x] = (uchar) i;
> +#endif /* AES_DECRYPTION */
> +  }
> +  // generate the forward and reverse key expansion tables
> +  for (i = 0; i < 256; i++) {
> +    x = FSb[i];
> +    y = XTIME(x) & 0xFF;
> +    z = (y ^ x) & 0xFF;
>
> -static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
> -  uint32_t a, b, c, d;
> +    FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x << 16) ^
> +             ((uint32_t) z << 24);
>
> -  a = buf[0];
> -  b = buf[1];
> -  c = buf[2];
> -  d = buf[3];
> +    FT1[i] = ROTL8(FT0[i]);
> +    FT2[i] = ROTL8(FT1[i]);
> +    FT3[i] = ROTL8(FT2[i]);
>
> -  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> -  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> -  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> -  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> -  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> -  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> -  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> -  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> -  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> -  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> -  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> -  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> -  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> -  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> -  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> -  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> +#if AES_DECRYPTION  // whether AES decryption is supported
> +    x = RSb[i];
>
> -  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> -  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> -  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> -  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> -  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> -  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> -  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> -  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> -  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> -  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> -  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> -  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> -  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> -  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> -  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> -  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> +    RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x) << 8) ^
> +             ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
>
> -  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> -  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> -  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> -  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> -  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> -  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> -  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> -  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> -  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> -  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> -  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> -  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> -  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> -  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> -  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> -  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> +    RT1[i] = ROTL8(RT0[i]);
> +    RT2[i] = ROTL8(RT1[i]);
> +    RT3[i] = ROTL8(RT2[i]);
> +#endif /* AES_DECRYPTION */
> +  }
> +  aes_tables_inited = 1;  // flag that the tables have been generated
> +}  // to permit subsequent use of the AES cipher
> +
> +/******************************************************************************
> + *
> + *  AES_SET_ENCRYPTION_KEY
> + *
> + *  This is called by 'aes_setkey' when we're establishing a key for
> + *  subsequent encryption.  We give it a pointer to the encryption
> + *  context, a pointer to the key, and the key's length in bytes.
> + *  Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
> + *
> + ******************************************************************************/
> +static int aes_set_encryption_key(aes_context *ctx, const uchar *key,
> +                                  uint keysize) {
> +  uint i;                  // general purpose iteration local
> +  uint32_t *RK = ctx->rk;  // initialize our RoundKey buffer pointer
> +
> +  for (i = 0; i < (keysize >> 2); i++) {
> +    GET_UINT32_LE(RK[i], key, i << 2);
> +  }
>
> -  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> -  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> -  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> -  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> -  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> -  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> -  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> -  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> -  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> -  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> -  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> -  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> -  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> -  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> -  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> -  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> +  switch (ctx->rounds) {
> +    case 10:
> +      for (i = 0; i < 10; i++, RK += 4) {
> +        RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
> +                ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
> +                ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
> +                ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
> +
> +        RK[5] = RK[1] ^ RK[4];
> +        RK[6] = RK[2] ^ RK[5];
> +        RK[7] = RK[3] ^ RK[6];
> +      }
> +      break;
>
> -  buf[0] += a;
> -  buf[1] += b;
> -  buf[2] += c;
> -  buf[3] += d;
> -}
> +    case 12:
> +      for (i = 0; i < 8; i++, RK += 6) {
> +        RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
> +                ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
> +                ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
> +                ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
> +
> +        RK[7] = RK[1] ^ RK[6];
> +        RK[8] = RK[2] ^ RK[7];
> +        RK[9] = RK[3] ^ RK[8];
> +        RK[10] = RK[4] ^ RK[9];
> +        RK[11] = RK[5] ^ RK[10];
> +      }
> +      break;
>
> -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) {
> -  uint32_t t;
> +    case 14:
> +      for (i = 0; i < 7; i++, RK += 8) {
> +        RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
> +                ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
> +                ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
> +                ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
> +
> +        RK[9] = RK[1] ^ RK[8];
> +        RK[10] = RK[2] ^ RK[9];
> +        RK[11] = RK[3] ^ RK[10];
> +
> +        RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
> +                 ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
> +                 ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
> +                 ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
> +
> +        RK[13] = RK[5] ^ RK[12];
> +        RK[14] = RK[6] ^ RK[13];
> +        RK[15] = RK[7] ^ RK[14];
> +      }
> +      break;
>
> -  t = ctx->bits[0];
> -  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
> -  ctx->bits[1] += (uint32_t) len >> 29;
> +    default:
> +      return -1;
> +  }
> +  return (0);
> +}
> +
> +#if AES_DECRYPTION  // whether AES decryption is supported
> +
> +/******************************************************************************
> + *
> + *  AES_SET_DECRYPTION_KEY
> + *
> + *  This is called by 'aes_setkey' when we're establishing a
> + *  key for subsequent decryption.  We give it a pointer to
> + *  the encryption context, a pointer to the key, and the key's
> + *  length in bits. Valid lengths are: 128, 192, or 256 bits.
> + *
> + ******************************************************************************/
> +static int aes_set_decryption_key(aes_context *ctx, const uchar *key,
> +                                  uint keysize) {
> +  int i, j;
> +  aes_context cty;         // a calling aes context for set_encryption_key
> +  uint32_t *RK = ctx->rk;  // initialize our RoundKey buffer pointer
> +  uint32_t *SK;
> +  int ret;
>
> -  t = (t >> 3) & 0x3f;
> +  cty.rounds = ctx->rounds;  // initialize our local aes context
> +  cty.rk = cty.buf;          // round count and key buf pointer
>
> -  if (t) {
> -    unsigned char *p = (unsigned char *) ctx->in + t;
> +  if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0) return (ret);
>
> -    t = 64 - t;
> -    if (len < t) {
> -      memcpy(p, buf, len);
> -      return;
> +  SK = cty.rk + cty.rounds * 4;
> +
> +  CPY128  // copy a 128-bit block from *SK to *RK
> +
> +      for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
> +    for (j = 0; j < 4; j++, SK++) {
> +      *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
> +              RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
>       }
> -    memcpy(p, buf, t);
> -    mg_byte_reverse(ctx->in, 16);
> -    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> -    buf += t;
> -    len -= t;
> +  }
> +  CPY128  // copy a 128-bit block from *SK to *RK
> +      memset(&cty, 0, sizeof(aes_context));  // clear local aes context
> +  return (0);
> +}
> +
> +#endif /* AES_DECRYPTION */
> +
> +/******************************************************************************
> + *
> + *  AES_SETKEY
> + *
> + *  Invoked to establish the key schedule for subsequent encryption/decryption
> + *
> + ******************************************************************************/
> +static int aes_setkey(aes_context *ctx,  // AES context provided by our caller
> +                      int mode,          // ENCRYPT or DECRYPT flag
> +                      const uchar *key,  // pointer to the key
> +                      uint keysize)      // key length in bytes
> +{
> +  // since table initialization is not thread safe, we could either add
> +  // system-specific mutexes and init the AES key generation tables on
> +  // demand, or ask the developer to simply call "gcm_initialize" once during
> +  // application startup before threading begins. That's what we choose.
> +  if (!aes_tables_inited) return (-1);  // fail the call when not inited.
> +
> +  ctx->mode = mode;    // capture the key type we're creating
> +  ctx->rk = ctx->buf;  // initialize our round key pointer
> +
> +  switch (keysize)  // set the rounds count based upon the keysize
> +  {
> +    case 16:
> +      ctx->rounds = 10;
> +      break;  // 16-byte, 128-bit key
> +    case 24:
> +      ctx->rounds = 12;
> +      break;  // 24-byte, 192-bit key
> +    case 32:
> +      ctx->rounds = 14;
> +      break;  // 32-byte, 256-bit key
> +    default:
> +      return (-1);
>     }
>
> -  while (len >= 64) {
> -    memcpy(ctx->in, buf, 64);
> -    mg_byte_reverse(ctx->in, 16);
> -    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> -    buf += 64;
> -    len -= 64;
> +#if AES_DECRYPTION
> +  if (mode == MG_DECRYPT)  // expand our key for encryption or decryption
> +    return (aes_set_decryption_key(ctx, key, keysize));
> +  else /* MG_ENCRYPT */
> +#endif /* AES_DECRYPTION */
> +    return (aes_set_encryption_key(ctx, key, keysize));
> +}
> +
> +/******************************************************************************
> + *
> + *  AES_CIPHER
> + *
> + *  Perform AES encryption and decryption.
> + *  The AES context will have been setup with the encryption mode
> + *  and all keying information appropriate for the task.
> + *
> + ******************************************************************************/
> +static int aes_cipher(aes_context *ctx, const uchar input[16],
> +                      uchar output[16]) {
> +  int i;
> +  uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;  // general purpose locals
> +
> +  RK = ctx->rk;
> +
> +  GET_UINT32_LE(X0, input, 0);
> +  X0 ^= *RK++;  // load our 128-bit
> +  GET_UINT32_LE(X1, input, 4);
> +  X1 ^= *RK++;  // input buffer in a storage
> +  GET_UINT32_LE(X2, input, 8);
> +  X2 ^= *RK++;  // memory endian-neutral way
> +  GET_UINT32_LE(X3, input, 12);
> +  X3 ^= *RK++;
> +
> +#if AES_DECRYPTION  // whether AES decryption is supported
> +
> +  if (ctx->mode == MG_DECRYPT) {
> +    for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> +      AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> +      AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> +    }
> +
> +    AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> +
> +    X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
> +         ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
> +
> +    X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
> +         ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
> +
> +    X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
> +         ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
> +
> +    X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
> +         ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
> +  } else /* MG_ENCRYPT */
> +  {
> +#endif /* AES_DECRYPTION */
> +
> +    for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> +      AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> +      AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> +    }
> +
> +    AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> +
> +    X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
> +         ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
> +
> +    X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
> +         ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
> +
> +    X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
> +         ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
> +
> +    X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
> +         ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
> +         ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
> +         ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
> +
> +#if AES_DECRYPTION  // whether AES decryption is supported
>     }
> +#endif /* AES_DECRYPTION */
> +
> +  PUT_UINT32_LE(X0, output, 0);
> +  PUT_UINT32_LE(X1, output, 4);
> +  PUT_UINT32_LE(X2, output, 8);
> +  PUT_UINT32_LE(X3, output, 12);
> +
> +  return (0);
> +}
> +/* end of aes.c */
> +/******************************************************************************
> + *
> + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
> + *
> + * This is a simple and straightforward implementation of AES-GCM authenticated
> + * encryption. The focus of this work was correctness & accuracy. It is written
> + * in straight 'C' without any particular focus upon optimization or speed. It
> + * should be endian (memory byte order) neutral since the few places that care
> + * are handled explicitly.
> + *
> + * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
> + *
> + * It is intended for general purpose use, but was written in support of GRC's
> + * reference implementation of the SQRL (Secure Quick Reliable Login) client.
> + *
> + * See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> + *         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
> + *         gcm/gcm-revised-spec.pdf
> + *
> + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
> + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
> + *
> + *******************************************************************************/
> +
> +/******************************************************************************
> + *                      ==== IMPLEMENTATION WARNING ====
> + *
> + *  This code was developed for use within SQRL's fixed environmnent. Thus, it
> + *  is somewhat less "general purpose" than it would be if it were designed as
> + *  a general purpose AES-GCM library. Specifically, it bothers with almost NO
> + *  error checking on parameter limits, buffer bounds, etc. It assumes that it
> + *  is being invoked by its author or by someone who understands the values it
> + *  expects to receive. Its behavior will be undefined otherwise.
> + *
> + *  All functions that might fail are defined to return 'ints' to indicate a
> + *  problem. Most do not do so now. But this allows for error propagation out
> + *  of internal functions if robust error checking should ever be desired.
> + *
> + ******************************************************************************/
> +
> +/* Calculating the "GHASH"
> + *
> + * There are many ways of calculating the so-called GHASH in software, each with
> + * a traditional size vs performance tradeoff.  The GHASH (Galois field hash) is
> + * an intriguing construction which takes two 128-bit strings (also the cipher's
> + * block size and the fundamental operation size for the system) and hashes them
> + * into a third 128-bit result.
> + *
> + * Many implementation solutions have been worked out that use large precomputed
> + * table lookups in place of more time consuming bit fiddling, and this approach
> + * can be scaled easily upward or downward as needed to change the time/space
> + * tradeoff. It's been studied extensively and there's a solid body of theory
> + * and practice.  For example, without using any lookup tables an implementation
> + * might obtain 119 cycles per byte throughput, whereas using a simple, though
> + * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13
> + * cycles per byte.
> + *
> + * And Intel's processors have, since 2010, included an instruction which does
> + * the entire 128x128->128 bit job in just several 64x64->128 bit pieces.
> + *
> + * Since SQRL is interactive, and only processing a few 128-bit blocks, I've
> + * settled upon a relatively slower but appealing small-table compromise which
> + * folds a bunch of not only time consuming but also bit twiddling into a simple
> + * 16-entry table which is attributed to Victor Shoup's 1996 work while at
> + * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on
> + * Universal Hashing."  See: http://www.shoup.net/papers/macs.pdf
> + * See, also section 4.1 of the "gcm-revised-spec" cited above.
> + */
>
> -  memcpy(ctx->in, buf, len);
> -}
> +/*
> + *  This 16-entry table of pre-computed constants is used by the
> + *  GHASH multiplier to improve over a strictly table-free but
> + *  significantly slower 128x128 bit multiple within GF(2^128).
> + */
> +static const uint64_t last4[16] = {
> +    0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
> +    0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
>
> -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> -  unsigned count;
> -  unsigned char *p;
> -  uint32_t *a;
> +/*
> + * Platform Endianness Neutralizing Load and Store Macro definitions
> + * GCM wants platform-neutral Big Endian (BE) byte ordering
> + */
> +#define GET_UINT32_BE(n, b, i)                                            \
> +  {                                                                       \
> +    (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] << 16) | \
> +          ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]);     \
> +  }
>
> -  count = (ctx->bits[0] >> 3) & 0x3F;
> +#define PUT_UINT32_BE(n, b, i)          \
> +  {                                     \
> +    (b)[(i)] = (uchar) ((n) >> 24);     \
> +    (b)[(i) + 1] = (uchar) ((n) >> 16); \
> +    (b)[(i) + 2] = (uchar) ((n) >> 8);  \
> +    (b)[(i) + 3] = (uchar) ((n));       \
> +  }
>
> -  p = ctx->in + count;
> -  *p++ = 0x80;
> -  count = 64 - 1 - count;
> -  if (count < 8) {
> -    memset(p, 0, count);
> -    mg_byte_reverse(ctx->in, 16);
> -    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> -    memset(ctx->in, 0, 56);
> -  } else {
> -    memset(p, 0, count - 8);
> +/******************************************************************************
> + *
> + *  GCM_INITIALIZE
> + *
> + *  Must be called once to initialize the GCM library.
> + *
> + *  At present, this only calls the AES keygen table generator, which expands
> + *  the AES keying tables for use. This is NOT A THREAD-SAFE function, so it
> + *  MUST be called during system initialization before a multi-threading
> + *  environment is running.
> + *
> + ******************************************************************************/
> +int mg_gcm_initialize(void) {
> +  aes_init_keygen_tables();
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_MULT
> + *
> + *  Performs a GHASH operation on the 128-bit input vector 'x', setting
> + *  the 128-bit output vector to 'x' times H using our precomputed tables.
> + *  'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field.
> + *
> + ******************************************************************************/
> +static void gcm_mult(gcm_context *ctx,   // pointer to established context
> +                     const uchar x[16],  // pointer to 128-bit input vector
> +                     uchar output[16])   // pointer to 128-bit output vector
> +{
> +  int i;
> +  uchar lo, hi, rem;
> +  uint64_t zh, zl;
> +
> +  lo = (uchar) (x[15] & 0x0f);
> +  hi = (uchar) (x[15] >> 4);
> +  zh = ctx->HH[lo];
> +  zl = ctx->HL[lo];
> +
> +  for (i = 15; i >= 0; i--) {
> +    lo = (uchar) (x[i] & 0x0f);
> +    hi = (uchar) (x[i] >> 4);
> +
> +    if (i != 15) {
> +      rem = (uchar) (zl & 0x0f);
> +      zl = (zh << 60) | (zl >> 4);
> +      zh = (zh >> 4);
> +      zh ^= (uint64_t) last4[rem] << 48;
> +      zh ^= ctx->HH[lo];
> +      zl ^= ctx->HL[lo];
> +    }
> +    rem = (uchar) (zl & 0x0f);
> +    zl = (zh << 60) | (zl >> 4);
> +    zh = (zh >> 4);
> +    zh ^= (uint64_t) last4[rem] << 48;
> +    zh ^= ctx->HH[hi];
> +    zl ^= ctx->HL[hi];
>     }
> -  mg_byte_reverse(ctx->in, 14);
> +  PUT_UINT32_BE(zh >> 32, output, 0);
> +  PUT_UINT32_BE(zh, output, 4);
> +  PUT_UINT32_BE(zl >> 32, output, 8);
> +  PUT_UINT32_BE(zl, output, 12);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_SETKEY
> + *
> + *  This is called to set the AES-GCM key. It initializes the AES key
> + *  and populates the gcm context's pre-calculated HTables.
> + *
> + ******************************************************************************/
> +static int gcm_setkey(
> +    gcm_context *ctx,    // pointer to caller-provided gcm context
> +    const uchar *key,    // pointer to the AES encryption key
> +    const uint keysize)  // size in bytes (must be 16, 24, 32 for
> +                         // 128, 192 or 256-bit keys respectively)
> +{
> +  int ret, i, j;
> +  uint64_t hi, lo;
> +  uint64_t vl, vh;
> +  unsigned char h[16];
> +
> +  memset(ctx, 0, sizeof(gcm_context));  // zero caller-provided GCM context
> +  memset(h, 0, 16);                     // initialize the block to encrypt
> +
> +  // encrypt the null 128-bit block to generate a key-based value
> +  // which is then used to initialize our GHASH lookup tables
> +  if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize)) != 0)
> +    return (ret);
> +  if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
> +
> +  GET_UINT32_BE(hi, h, 0);  // pack h as two 64-bit ints, big-endian
> +  GET_UINT32_BE(lo, h, 4);
> +  vh = (uint64_t) hi << 32 | lo;
> +
> +  GET_UINT32_BE(hi, h, 8);
> +  GET_UINT32_BE(lo, h, 12);
> +  vl = (uint64_t) hi << 32 | lo;
> +
> +  ctx->HL[8] = vl;  // 8 = 1000 corresponds to 1 in GF(2^128)
> +  ctx->HH[8] = vh;
> +  ctx->HH[0] = 0;  // 0 corresponds to 0 in GF(2^128)
> +  ctx->HL[0] = 0;
> +
> +  for (i = 4; i > 0; i >>= 1) {
> +    uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
> +    vl = (vh << 63) | (vl >> 1);
> +    vh = (vh >> 1) ^ ((uint64_t) T << 32);
> +    ctx->HL[i] = vl;
> +    ctx->HH[i] = vh;
> +  }
> +  for (i = 2; i < 16; i <<= 1) {
> +    uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
> +    vh = *HiH;
> +    vl = *HiL;
> +    for (j = 1; j < i; j++) {
> +      HiH[j] = vh ^ ctx->HH[j];
> +      HiL[j] = vl ^ ctx->HL[j];
> +    }
> +  }
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *    GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH.
> + *
> + *  SETKEY:
> + *
> + *   START: Sets the Encryption/Decryption mode.
> + *          Accepts the initialization vector and additional data.
> + *
> + *  UPDATE: Encrypts or decrypts the plaintext or ciphertext.
> + *
> + *  FINISH: Performs a final GHASH to generate the authentication tag.
> + *
> + ******************************************************************************
> + *
> + *  GCM_START
> + *
> + *  Given a user-provided GCM context, this initializes it, sets the encryption
> + *  mode, and preprocesses the initialization vector and additional AEAD data.
> + *
> + ******************************************************************************/
> +int gcm_start(gcm_context *ctx,  // pointer to user-provided GCM context
> +              int mode,          // GCM_ENCRYPT or GCM_DECRYPT
> +              const uchar *iv,   // pointer to initialization vector
> +              size_t iv_len,     // IV length in bytes (should == 12)
> +              const uchar *add,  // ptr to additional AEAD data (NULL if none)
> +              size_t add_len)    // length of additional AEAD data (bytes)
> +{
> +  int ret;             // our error return if the AES encrypt fails
> +  uchar work_buf[16];  // XOR source built from provided IV if len != 16
> +  const uchar *p;      // general purpose array pointer
> +  size_t use_len;      // byte count to process, up to 16 bytes
> +  size_t i;            // local loop iterator
> +
> +  // since the context might be reused under the same key
> +  // we zero the working buffers for this next new process
> +  memset(ctx->y, 0x00, sizeof(ctx->y));
> +  memset(ctx->buf, 0x00, sizeof(ctx->buf));
> +  ctx->len = 0;
> +  ctx->add_len = 0;
> +
> +  ctx->mode = mode;                // set the GCM encryption/decryption mode
> +  ctx->aes_ctx.mode = MG_ENCRYPT;  // GCM *always* runs AES in ENCRYPTION mode
> +
> +  if (iv_len == 12) {            // GCM natively uses a 12-byte, 96-bit IV
> +    memcpy(ctx->y, iv, iv_len);  // copy the IV to the top of the 'y' buff
> +    ctx->y[15] = 1;              // start "counting" from 1 (not 0)
> +  } else  // if we don't have a 12-byte IV, we GHASH whatever we've been given
> +  {
> +    memset(work_buf, 0x00, 16);               // clear the working buffer
> +    PUT_UINT32_BE(iv_len * 8, work_buf, 12);  // place the IV into buffer
> +
> +    p = iv;
> +    while (iv_len > 0) {
> +      use_len = (iv_len < 16) ? iv_len : 16;
> +      for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
> +      gcm_mult(ctx, ctx->y, ctx->y);
> +      iv_len -= use_len;
> +      p += use_len;
> +    }
> +    for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
> +    gcm_mult(ctx, ctx->y, ctx->y);
> +  }
> +  if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0)
> +    return (ret);
> +
> +  ctx->add_len = add_len;
> +  p = add;
> +  while (add_len > 0) {
> +    use_len = (add_len < 16) ? add_len : 16;
> +    for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
> +    gcm_mult(ctx, ctx->buf, ctx->buf);
> +    add_len -= use_len;
> +    p += use_len;
> +  }
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_UPDATE
> + *
> + *  This is called once or more to process bulk plaintext or ciphertext data.
> + *  We give this some number of bytes of input and it returns the same number
> + *  of output bytes. If called multiple times (which is fine) all but the final
> + *  invocation MUST be called with length mod 16 == 0. (Only the final call can
> + *  have a partial block length of < 128 bits.)
> + *
> + ******************************************************************************/
> +int gcm_update(gcm_context *ctx,    // pointer to user-provided GCM context
> +               size_t length,       // length, in bytes, of data to process
> +               const uchar *input,  // pointer to source data
> +               uchar *output)       // pointer to destination data
> +{
> +  int ret;         // our error return if the AES encrypt fails
> +  uchar ectr[16];  // counter-mode cipher output for XORing
> +  size_t use_len;  // byte count to process, up to 16 bytes
> +  size_t i;        // local loop iterator
> +
> +  ctx->len += length;  // bump the GCM context's running length count
> +
> +  while (length > 0) {
> +    // clamp the length to process at 16 bytes
> +    use_len = (length < 16) ? length : 16;
> +
> +    // increment the context's 128-bit IV||Counter 'y' vector
> +    for (i = 16; i > 12; i--)
> +      if (++ctx->y[i - 1] != 0) break;
> +
> +    // encrypt the context's 'y' vector under the established key
> +    if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0) return (ret);
> +
> +    // encrypt or decrypt the input to the output
> +    if (ctx->mode == MG_ENCRYPT) {
> +      for (i = 0; i < use_len; i++) {
> +        // XOR the cipher's ouptut vector (ectr) with our input
> +        output[i] = (uchar) (ectr[i] ^ input[i]);
> +        // now we mix in our data into the authentication hash.
> +        // if we're ENcrypting we XOR in the post-XOR (output)
> +        // results, but if we're DEcrypting we XOR in the input
> +        // data
> +        ctx->buf[i] ^= output[i];
> +      }
> +    } else {
> +      for (i = 0; i < use_len; i++) {
> +        // but if we're DEcrypting we XOR in the input data first,
> +        // i.e. before saving to ouput data, otherwise if the input
> +        // and output buffer are the same (inplace decryption) we
> +        // would not get the correct auth tag
>
> -  a = (uint32_t *) ctx->in;
> -  a[14] = ctx->bits[0];
> -  a[15] = ctx->bits[1];
> +        ctx->buf[i] ^= input[i];
>
> -  mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> -  mg_byte_reverse((unsigned char *) ctx->buf, 4);
> -  memcpy(digest, ctx->buf, 16);
> -  memset((char *) ctx, 0, sizeof(*ctx));
> -}
> -#endif
> +        // XOR the cipher's ouptut vector (ectr) with our input
> +        output[i] = (uchar) (ectr[i] ^ input[i]);
> +      }
> +    }
> +    gcm_mult(ctx, ctx->buf, ctx->buf);  // perform a GHASH operation
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/mqtt.c"
> -#endif
> +    length -= use_len;  // drop the remaining byte count to process
> +    input += use_len;   // bump our input pointer forward
> +    output += use_len;  // bump our output pointer forward
> +  }
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_FINISH
> + *
> + *  This is called once after all calls to GCM_UPDATE to finalize the GCM.
> + *  It performs the final GHASH to produce the resulting authentication TAG.
> + *
> + ******************************************************************************/
> +int gcm_finish(gcm_context *ctx,  // pointer to user-provided GCM context
> +               uchar *tag,        // pointer to buffer which receives the tag
> +               size_t tag_len)    // length, in bytes, of the tag-receiving buf
> +{
> +  uchar work_buf[16];
> +  uint64_t orig_len = ctx->len * 8;
> +  uint64_t orig_add_len = ctx->add_len * 8;
> +  size_t i;
>
> +  if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
>
> +  if (orig_len || orig_add_len) {
> +    memset(work_buf, 0x00, 16);
>
> +    PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
> +    PUT_UINT32_BE((orig_add_len), work_buf, 4);
> +    PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
> +    PUT_UINT32_BE((orig_len), work_buf, 12);
>
> +    for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
> +    gcm_mult(ctx, ctx->buf, ctx->buf);
> +    for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
> +  }
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_CRYPT_AND_TAG
> + *
> + *  This either encrypts or decrypts the user-provided data and, either
> + *  way, generates an authentication tag of the requested length. It must be
> + *  called with a GCM context whose key has already been set with GCM_SETKEY.
> + *
> + *  The user would typically call this explicitly to ENCRYPT a buffer of data
> + *  and optional associated data, and produce its an authentication tag.
> + *
> + *  To reverse the process the user would typically call the companion
> + *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
> + *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function
> + *  to perform its decryption and tag generation, which it then compares.
> + *
> + ******************************************************************************/
> +int gcm_crypt_and_tag(
> +    gcm_context *ctx,    // gcm context with key already setup
> +    int mode,            // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
> +    const uchar *iv,     // pointer to the 12-byte initialization vector
> +    size_t iv_len,       // byte length if the IV. should always be 12
> +    const uchar *add,    // pointer to the non-ciphered additional data
> +    size_t add_len,      // byte length of the additional AEAD data
> +    const uchar *input,  // pointer to the cipher data source
> +    uchar *output,       // pointer to the cipher data destination
> +    size_t length,       // byte length of the cipher data
> +    uchar *tag,          // pointer to the tag to be generated
> +    size_t tag_len)      // byte length of the tag to be generated
> +{                        /*
> +                            assuming that the caller has already invoked gcm_setkey to
> +                            prepare the gcm context with the keying material, we simply
> +                            invoke each of the three GCM sub-functions in turn...
> +                         */
> +  gcm_start(ctx, mode, iv, iv_len, add, add_len);
> +  gcm_update(ctx, length, input, output);
> +  gcm_finish(ctx, tag, tag_len);
> +  return (0);
> +}
> +
> +/******************************************************************************
> + *
> + *  GCM_ZERO_CTX
> + *
> + *  The GCM context contains both the GCM context and the AES context.
> + *  This includes keying and key-related material which is security-
> + *  sensitive, so it MUST be zeroed after use. This function does that.
> + *
> + ******************************************************************************/
> +void gcm_zero_ctx(gcm_context *ctx) {
> +  // zero the context originally provided to us
> +  memset(ctx, 0, sizeof(gcm_context));
> +}
> +//
> +//  aes-gcm.c
> +//  Pods
> +//
> +//  Created by Markus Kosmal on 20/11/14.
> +//
> +//
>
> +int mg_aes_gcm_encrypt(unsigned char *output,  //
> +                       const unsigned char *input, size_t input_length,
> +                       const unsigned char *key, const size_t key_len,
> +                       const unsigned char *iv, const size_t iv_len,
> +                       unsigned char *aead, size_t aead_len, unsigned char *tag,
> +                       const size_t tag_len) {
> +  int ret = 0;      // our return value
> +  gcm_context ctx;  // includes the AES context structure
>
> +  gcm_setkey(&ctx, key, (uint) key_len);
>
> +  ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead, aead_len, input,
> +                          output, input_length, tag, tag_len);
>
> -#define MQTT_CLEAN_SESSION 0x02
> -#define MQTT_HAS_WILL 0x04
> -#define MQTT_WILL_RETAIN 0x20
> -#define MQTT_HAS_PASSWORD 0x40
> -#define MQTT_HAS_USER_NAME 0x80
> +  gcm_zero_ctx(&ctx);
>
> -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags,
> -                         uint32_t len) {
> -  uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> -  buf[0] = (uint8_t) ((cmd << 4) | flags);
> -  do {
> -    *vlen = len % 0x80;
> -    len /= 0x80;
> -    if (len > 0) *vlen |= 0x80;
> -    vlen++;
> -  } while (len > 0 && vlen < &buf[sizeof(buf)]);
> -  mg_send(c, buf, (size_t) (vlen - buf));
> +  return (ret);
>   }
>
> -static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> -  mg_send(c, &value, sizeof(value));
> +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
> +                       size_t input_length, const unsigned char *key,
> +                       const size_t key_len, const unsigned char *iv,
> +                       const size_t iv_len) {
> +  int ret = 0;      // our return value
> +  gcm_context ctx;  // includes the AES context structure
> +
> +  size_t tag_len = 0;
> +  unsigned char *tag_buf = NULL;
> +
> +  gcm_setkey(&ctx, key, (uint) key_len);
> +
> +  ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0, input, output,
> +                          input_length, tag_buf, tag_len);
> +
> +  gcm_zero_ctx(&ctx);
> +
> +  return (ret);
>   }
> +#endif
> +// End of aes128 PD
>
> -void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
> -  char rnd[10], client_id[21], zero = 0;
> -  struct mg_str cid = opts->client_id;
> -  uint32_t total_len = 7 + 1 + 2 + 2;
> -  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/tls_builtin.c"
> +#endif
>
> -  if (cid.len == 0) {
> -    mg_random(rnd, sizeof(rnd));
> -    mg_hex(rnd, sizeof(rnd), client_id);
> -    client_id[sizeof(client_id) - 1] = '\0';
> -    cid = mg_str(client_id);
> -  }
>
> -  if (hdr[6] == 0) hdr[6] = 4;  // If version is not set, use 4 (3.1.1)
> -  c->is_mqtt5 = hdr[6] == 5;    // Set version 5 flag
> -  hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3);  // Connection flags
> -  if (opts->user.len > 0) {
> -    total_len += 2 + (uint32_t) opts->user.len;
> -    hdr[7] |= MQTT_HAS_USER_NAME;
> +
> +
> +#if MG_TLS == MG_TLS_BUILTIN
> +
> +/* TLS 1.3 Record Content Type (RFC8446 B.1) */
> +#define MG_TLS_CHANGE_CIPHER 20
> +#define MG_TLS_ALERT 21
> +#define MG_TLS_HANDSHAKE 22
> +#define MG_TLS_APP_DATA 23
> +#define MG_TLS_HEARTBEAT 24
> +
> +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
> +#define MG_TLS_CLIENT_HELLO 1
> +#define MG_TLS_SERVER_HELLO 2
> +#define MG_TLS_ENCRYPTED_EXTENSIONS 8
> +#define MG_TLS_CERTIFICATE 11
> +#define MG_TLS_CERTIFICATE_VERIFY 15
> +#define MG_TLS_FINISHED 20
> +
> +// handshake is re-entrant, so we need to keep track of its state state names
> +// refer to RFC8446#A.1
> +enum mg_tls_hs_state {
> +  // Client state machine:
> +  MG_TLS_STATE_CLIENT_START,          // Send ClientHello
> +  MG_TLS_STATE_CLIENT_WAIT_SH,        // Wait for ServerHello
> +  MG_TLS_STATE_CLIENT_WAIT_EE,        // Wait for EncryptedExtensions
> +  MG_TLS_STATE_CLIENT_WAIT_CERT,      // Wait for Certificate
> +  MG_TLS_STATE_CLIENT_WAIT_CV,        // Wait for CertificateVerify
> +  MG_TLS_STATE_CLIENT_WAIT_FINISHED,  // Wait for Finished
> +  MG_TLS_STATE_CLIENT_CONNECTED,      // Done
> +
> +  // Server state machine:
> +  MG_TLS_STATE_SERVER_START,       // Wait for ClientHello
> +  MG_TLS_STATE_SERVER_NEGOTIATED,  // Wait for Finished
> +  MG_TLS_STATE_SERVER_CONNECTED    // Done
> +};
> +
> +// per-connection TLS data
> +struct tls_data {
> +  enum mg_tls_hs_state state;  // keep track of connection handshake progress
> +
> +  struct mg_iobuf send;  // For the receive path, we're reusing c->rtls
> +  struct mg_iobuf recv;  // While c->rtls contains full records, recv reuses
> +                         // the same underlying buffer but points at individual
> +                         // decrypted messages
> +  uint8_t content_type;  // Last received record content type
> +
> +  mg_sha256_ctx sha256;  // incremental SHA-256 hash for TLS handshake
> +
> +  uint32_t sseq;  // server sequence number, used in encryption
> +  uint32_t cseq;  // client sequence number, used in decryption
> +
> +  uint8_t random[32];      // client random from ClientHello
> +  uint8_t session_id[32];  // client session ID between the handshake states
> +  uint8_t x25519_cli[32];  // client X25519 key between the handshake states
> +  uint8_t x25519_sec[32];  // x25519 secret between the handshake states
> +
> +  int skip_verification;          // perform checks on server certificate?
> +  struct mg_str server_cert_der;  // server certificate in DER format
> +  uint8_t server_key[32];         // server EC private key
> +  char hostname[254];             // server hostname (client extension)
> +
> +  uint8_t certhash[32];  // certificate message hash
> +  uint8_t pubkey[64];    // server EC public key to verify cert
> +  uint8_t sighash[32];   // server EC public key to verify cert
> +
> +  // keys for AES encryption
> +  uint8_t handshake_secret[32];
> +  uint8_t server_write_key[16];
> +  uint8_t server_write_iv[12];
> +  uint8_t server_finished_key[32];
> +  uint8_t client_write_key[16];
> +  uint8_t client_write_iv[12];
> +  uint8_t client_finished_key[32];
> +};
> +
> +#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) | MG_U8P(p)[1]))
> +#define MG_LOAD_BE24(p) \
> +  ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) | MG_U8P(p)[2]))
> +#define MG_STORE_BE16(p, n)           \
> +  do {                                \
> +    MG_U8P(p)[0] = ((n) >> 8U) & 255; \
> +    MG_U8P(p)[1] = (n) & 255;         \
> +  } while (0)
> +
> +#define TLS_RECHDR_SIZE 5  // 1 byte type, 2 bytes version, 2 bytes length
> +#define TLS_MSGHDR_SIZE 4  // 1 byte type, 3 bytes length
> +
> +#if 1
> +static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
> +                           uint8_t *secret, size_t secretsz) {
> +  (void) label;
> +  (void) client_random;
> +  (void) secret;
> +  (void) secretsz;
> +}
> +#else
> +#include <stdio.h>
> +static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
> +                           uint8_t *secret, size_t secretsz) {
> +  char *keylogfile = getenv("SSLKEYLOGFILE");
> +  if (keylogfile == NULL) {
> +    return;
>     }
> -  if (opts->pass.len > 0) {
> -    total_len += 2 + (uint32_t) opts->pass.len;
> -    hdr[7] |= MQTT_HAS_PASSWORD;
> +  FILE *f = fopen(keylogfile, "a");
> +  fprintf(f, "%s ", label);
> +  for (int i = 0; i < 32; i++) {
> +    fprintf(f, "%02x", client_random[i]);
>     }
> -  if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
> -    total_len +=
> -        4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len;
> -    hdr[7] |= MQTT_HAS_WILL;
> +  fprintf(f, " ");
> +  for (unsigned int i = 0; i < secretsz; i++) {
> +    fprintf(f, "%02x", secret[i]);
>     }
> -  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> -  if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
> -  total_len += (uint32_t) cid.len;
> -  if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U : 0);
> +  fprintf(f, "\n");
> +  fclose(f);
> +}
> +#endif
>
> -  mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
> -  mg_send(c, hdr, sizeof(hdr));
> -  // keepalive == 0 means "do not disconnect us!"
> -  mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> +// for derived tls keys we need SHA256([0]*32)
> +static uint8_t zeros[32] = {0};
> +static uint8_t zeros_sha256_digest[32] = {
> +    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
> +    0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
> +    0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
>
> -  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // V5 properties
> -  mg_send_u16(c, mg_htons((uint16_t) cid.len));
> -  mg_send(c, cid.ptr, cid.len);
> +// helper to hexdump buffers inline
> +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) {
> +  MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
> +}
>
> -  if (hdr[7] & MQTT_HAS_WILL) {
> -    if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // will props
> -    mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
> -    mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
> -    mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
> -    mg_send(c, opts->will_message.ptr, opts->will_message.len);
> +// helper utilities to parse ASN.1 DER
> +struct mg_der_tlv {
> +  uint8_t type;
> +  uint32_t len;
> +  uint8_t *value;
> +};
> +
> +// parse DER into a TLV record
> +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) {
> +  if (dersz < 2) {
> +    return -1;
>     }
> -  if (opts->user.len > 0) {
> -    mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> -    mg_send(c, opts->user.ptr, opts->user.len);
> +  tlv->type = der[0];
> +  tlv->len = der[1];
> +  tlv->value = der + 2;
> +  if (tlv->len > 0x7f) {
> +    uint32_t i, n = tlv->len - 0x80;
> +    tlv->len = 0;
> +    for (i = 0; i < n; i++) {
> +      tlv->len = (tlv->len << 8) | (der[2 + i]);
> +    }
> +    tlv->value = der + 2 + n;
>     }
> -  if (opts->pass.len > 0) {
> -    mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> -    mg_send(c, opts->pass.ptr, opts->pass.len);
> +  if (der + dersz < tlv->value + tlv->len) {
> +    return -1;
>     }
> +  return 0;
>   }
>
> -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> -                 struct mg_str data, int qos, bool retain) {
> -  uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)), zero = 0;
> -  uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
> -  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) topic.ptr,
> -            (int) data.len, (char *) data.ptr));
> -  if (qos > 0) len += 2;
> -  if (c->is_mqtt5) len++;
> -  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
> -  mg_send_u16(c, mg_htons((uint16_t) topic.len));
> -  mg_send(c, topic.ptr, topic.len);
> -  if (qos > 0) {
> -    if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> -    mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid, size_t oidsz,
> +                       struct mg_der_tlv *tlv) {
> +  uint8_t *p, *end;
> +  struct mg_der_tlv child = {0, 0, NULL};
> +  if (mg_der_to_tlv(der, dersz, tlv) < 0) {
> +    return -1;                  // invalid DER
> +  } else if (tlv->type == 6) {  // found OID, check value
> +    return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
> +  } else if ((tlv->type & 0x20) == 0) {
> +    return 0;  // Primitive, but not OID: not found
> +  }
> +  // Constructed object: scan children
> +  p = tlv->value;
> +  end = tlv->value + tlv->len;
> +  while (end > p) {
> +    int r;
> +    mg_der_to_tlv(p, (size_t) (end - p), &child);
> +    r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
> +    if (r < 0) return -1;  // error
> +    if (r > 0) return 1;   // found OID!
> +    p = child.value + child.len;
>     }
> -  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> -  mg_send(c, data.ptr, data.len);
> +  return 0;  // not found
>   }
>
> -void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) {
> -  uint8_t qos_ = qos & 3, zero = 0;
> -  uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5 ? 1 : 0);
> -  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
> -  if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> -  mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> -  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> -  mg_send_u16(c, mg_htons((uint16_t) topic.len));
> -  mg_send(c, topic.ptr, topic.len);
> -  mg_send(c, &qos_, sizeof(qos_));
> +// Did we receive a full TLS record in the c->rtls buffer?
> +static bool mg_tls_got_record(struct mg_connection *c) {
> +  return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
> +         c->rtls.len >=
> +             (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
>   }
>
> -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> -                  struct mg_mqtt_message *m) {
> -  uint8_t lc = 0, *p, *end;
> -  uint32_t n = 0, len_len = 0;
> +// Remove a single TLS record from the recv buffer
> +static void mg_tls_drop_record(struct mg_connection *c) {
> +  struct mg_iobuf *rio = &c->rtls;
> +  uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
> +  mg_iobuf_del(rio, 0, n);
> +}
>
> -  memset(m, 0, sizeof(*m));
> -  m->dgram.ptr = (char *) buf;
> -  if (len < 2) return MQTT_INCOMPLETE;
> -  m->cmd = (uint8_t) (buf[0] >> 4);
> -  m->qos = (buf[0] >> 1) & 3;
> +// Remove a single TLS message from decrypted buffer, remove the wrapping
> +// record if it was the last message within a record
> +static void mg_tls_drop_message(struct mg_connection *c) {
> +  uint32_t len;
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (tls->recv.len == 0) {
> +    return;
> +  }
> +  len = MG_LOAD_BE24(tls->recv.buf + 1);
> +  mg_sha256_update(&tls->sha256, tls->recv.buf, len + TLS_MSGHDR_SIZE);
> +  tls->recv.buf += len + TLS_MSGHDR_SIZE;
> +  tls->recv.len -= len + TLS_MSGHDR_SIZE;
> +  if (tls->recv.len == 0) {
> +    mg_tls_drop_record(c);
> +  }
> +}
>
> -  n = len_len = 0;
> -  p = (uint8_t *) buf + 1;
> -  while ((size_t) (p - buf) < len) {
> -    lc = *((uint8_t *) p++);
> -    n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> -    len_len++;
> -    if (!(lc & 0x80)) break;
> -    if (len_len >= 4) return MQTT_MALFORMED;
> +// TLS1.3 secret derivation based on the key label
> +static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz,
> +                                 uint8_t *data, size_t datasz, uint8_t *hash,
> +                                 size_t hashsz) {
> +  size_t labelsz = strlen(label);
> +  uint8_t secret[32];
> +  uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
> +  // TODO: assert lengths of label, key, data and hash
> +  if (labelsz > 0) memmove(packed + 3, label, labelsz);
> +  packed[3 + labelsz] = (uint8_t) datasz;
> +  if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
> +  packed[4 + labelsz + datasz] = 1;
> +
> +  mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
> +  memmove(hash, secret, hashsz);
> +}
> +
> +// at this point we have x25519 shared secret, we can generate a set of derived
> +// handshake encryption keys
> +static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +
> +  mg_sha256_ctx sha256;
> +  uint8_t early_secret[32];
> +  uint8_t pre_extract_secret[32];
> +  uint8_t hello_hash[32];
> +  uint8_t server_hs_secret[32];
> +  uint8_t client_hs_secret[32];
> +
> +  mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
> +  mg_tls_derive_secret("tls13 derived", early_secret, 32, zeros_sha256_digest,
> +                       32, pre_extract_secret, 32);
> +  mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
> +                 sizeof(pre_extract_secret), tls->x25519_sec,
> +                 sizeof(tls->x25519_sec));
> +  mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
> +
> +  // mg_sha256_final is not idempotent, need to copy sha256 context to calculate
> +  // the digest
> +  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> +  mg_sha256_final(hello_hash, &sha256);
> +
> +  mg_tls_hexdump("hello hash", hello_hash, 32);
> +  // derive keys needed for the rest of the handshake
> +  mg_tls_derive_secret("tls13 s hs traffic", tls->handshake_secret, 32,
> +                       hello_hash, 32, server_hs_secret, 32);
> +  mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
> +                       tls->server_write_key, 16);
> +  mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
> +                       tls->server_write_iv, 12);
> +  mg_tls_derive_secret("tls13 finished", server_hs_secret, 32, NULL, 0,
> +                       tls->server_finished_key, 32);
> +
> +  mg_tls_derive_secret("tls13 c hs traffic", tls->handshake_secret, 32,
> +                       hello_hash, 32, client_hs_secret, 32);
> +  mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
> +                       tls->client_write_key, 16);
> +  mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
> +                       tls->client_write_iv, 12);
> +  mg_tls_derive_secret("tls13 finished", client_hs_secret, 32, NULL, 0,
> +                       tls->client_finished_key, 32);
> +
> +  mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
> +  mg_tls_hexdump("s key", tls->server_write_key, 16);
> +  mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> +  mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> +  mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
> +  mg_tls_hexdump("c key", tls->client_write_key, 16);
> +  mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> +  mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> +
> +  mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> +                 server_hs_secret, 32);
> +  mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> +                 client_hs_secret, 32);
> +}
> +
> +static void mg_tls_generate_application_keys(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  uint8_t hash[32];
> +  uint8_t premaster_secret[32];
> +  uint8_t master_secret[32];
> +  uint8_t server_secret[32];
> +  uint8_t client_secret[32];
> +
> +  mg_sha256_ctx sha256;
> +  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> +  mg_sha256_final(hash, &sha256);
> +
> +  mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
> +                       zeros_sha256_digest, 32, premaster_secret, 32);
> +  mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
> +
> +  mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32,
> +                       server_secret, 32);
> +  mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
> +                       tls->server_write_key, 16);
> +  mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
> +                       tls->server_write_iv, 12);
> +  mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32,
> +                       client_secret, 32);
> +  mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
> +                       tls->client_write_key, 16);
> +  mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
> +                       tls->client_write_iv, 12);
> +
> +  mg_tls_hexdump("s ap traffic", server_secret, 32);
> +  mg_tls_hexdump("s key", tls->server_write_key, 16);
> +  mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> +  mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> +  mg_tls_hexdump("c ap traffic", client_secret, 32);
> +  mg_tls_hexdump("c key", tls->client_write_key, 16);
> +  mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> +  mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> +  tls->sseq = tls->cseq = 0;
> +
> +  mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random, server_secret, 32);
> +  mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random, client_secret, 32);
> +}
> +
> +// AES GCM encryption of the message + put encoded data into the write buffer
> +static void mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
> +                           size_t msgsz, uint8_t msgtype) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *wio = &tls->send;
> +  uint8_t *outmsg;
> +  uint8_t *tag;
> +  size_t encsz = msgsz + 16 + 1;
> +  uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> +                    (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
> +  uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> +                                (uint8_t) ((encsz >> 8) & 0xff),
> +                                (uint8_t) (encsz & 0xff)};
> +  uint8_t nonce[12];
> +
> +  mg_gcm_initialize();
> +
> +  if (c->is_client) {
> +    memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
> +    nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> +    nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> +    nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> +    nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> +  } else {
> +    memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
> +    nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> +    nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> +    nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> +    nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
>     }
> -  end = p + n;
> -  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> -  m->dgram.len = (size_t) (end - buf);
>
> -  switch (m->cmd) {
> -    case MQTT_CMD_CONNACK:
> -      if (end - p < 2) return MQTT_MALFORMED;
> -      m->ack = p[1];
> -      break;
> -    case MQTT_CMD_PUBACK:
> -    case MQTT_CMD_PUBREC:
> -    case MQTT_CMD_PUBREL:
> -    case MQTT_CMD_PUBCOMP:
> -    case MQTT_CMD_SUBSCRIBE:
> -    case MQTT_CMD_SUBACK:
> -    case MQTT_CMD_UNSUBSCRIBE:
> -    case MQTT_CMD_UNSUBACK:
> -      if (p + 2 > end) return MQTT_MALFORMED;
> -      m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> -      p += 2;
> -      break;
> -    case MQTT_CMD_PUBLISH: {
> -      if (p + 2 > end) return MQTT_MALFORMED;
> -      m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> -      m->topic.ptr = (char *) p + 2;
> -      p += 2 + m->topic.len;
> -      if (p > end) return MQTT_MALFORMED;
> -      if (m->qos > 0) {
> -        if (p + 2 > end) return MQTT_MALFORMED;
> -        m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> -        p += 2;
> -      }
> -      if (p > end) return MQTT_MALFORMED;
> -      if (version == 5 && p + 2 < end) p += 1 + p[0];  // Skip options
> -      if (p > end) return MQTT_MALFORMED;
> -      m->data.ptr = (char *) p;
> -      m->data.len = (size_t) (end - p);
> -      break;
> +  mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
> +  mg_iobuf_resize(wio, wio->len + encsz);
> +  outmsg = wio->buf + wio->len;
> +  tag = wio->buf + wio->len + msgsz + 1;
> +  memmove(outmsg, msg, msgsz);
> +  outmsg[msgsz] = msgtype;
> +  if (c->is_client) {
> +    mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->client_write_key,
> +                       sizeof(tls->client_write_key), nonce, sizeof(nonce),
> +                       associated_data, sizeof(associated_data), tag, 16);
> +    tls->cseq++;
> +  } else {
> +    mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->server_write_key,
> +                       sizeof(tls->server_write_key), nonce, sizeof(nonce),
> +                       associated_data, sizeof(associated_data), tag, 16);
> +    tls->sseq++;
> +  }
> +  wio->len += encsz;
> +}
> +
> +// read an encrypted record, decrypt it in place
> +static int mg_tls_recv_record(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *rio = &c->rtls;
> +  uint16_t msgsz;
> +  uint8_t *msg;
> +  uint8_t nonce[12];
> +  int r;
> +  if (tls->recv.len > 0) {
> +    return 0; /* some data from previous record is still present */
> +  }
> +  for (;;) {
> +    if (!mg_tls_got_record(c)) {
> +      return MG_IO_WAIT;
>       }
> -    default:
> +    if (rio->buf[0] == MG_TLS_APP_DATA) {
>         break;
> +    } else if (rio->buf[0] ==
> +               MG_TLS_CHANGE_CIPHER) {  // Skip ChangeCipher messages
> +      mg_tls_drop_record(c);
> +    } else if (rio->buf[0] == MG_TLS_ALERT) {  // Skip Alerts
> +      MG_INFO(("TLS ALERT packet received"));
> +      mg_tls_drop_record(c);
> +    } else {
> +      mg_error(c, "unexpected packet");
> +      return -1;
> +    }
>     }
> -  return MQTT_OK;
> +
> +  mg_gcm_initialize();
> +  msgsz = MG_LOAD_BE16(rio->buf + 3);
> +  msg = rio->buf + 5;
> +  if (c->is_client) {
> +    memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
> +    nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> +    nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> +    nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> +    nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
> +    mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
> +                       sizeof(tls->server_write_key), nonce, sizeof(nonce));
> +    tls->sseq++;
> +  } else {
> +    memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
> +    nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> +    nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> +    nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> +    nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> +    mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
> +                       sizeof(tls->client_write_key), nonce, sizeof(nonce));
> +    tls->cseq++;
> +  }
> +  r = msgsz - 16 - 1;
> +  tls->content_type = msg[msgsz - 16 - 1];
> +  tls->recv.buf = msg;
> +  tls->recv.size = tls->recv.len = msgsz - 16 - 1;
> +  return r;
>   }
>
> -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
> -                    void *fn_data) {
> -  if (ev == MG_EV_READ) {
> -    for (;;) {
> -      uint8_t version = c->is_mqtt5 ? 5 : 4;
> -      struct mg_mqtt_message mm;
> -      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> -      if (rc == MQTT_MALFORMED) {
> -        MG_ERROR(("%lu MQTT malformed message", c->id));
> -        c->is_closing = 1;
> -        break;
> -      } else if (rc == MQTT_OK) {
> -        MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> -                    (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
> -        switch (mm.cmd) {
> -          case MQTT_CMD_CONNACK:
> -            mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> -            if (mm.ack == 0) {
> -              MG_DEBUG(("%lu Connected", c->id));
> -            } else {
> -              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> -              c->is_closing = 1;
> -            }
> -            break;
> -          case MQTT_CMD_PUBLISH: {
> -            MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> -                      mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
> -            if (mm.qos > 0) {
> -              uint16_t id = mg_htons(mm.id);
> -              mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
> -              mg_send(c, &id, sizeof(id));
> -            }
> -            mg_call(c, MG_EV_MQTT_MSG, &mm);
> -            break;
> -          }
> -        }
> -        mg_call(c, MG_EV_MQTT_CMD, &mm);
> -        mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> -      } else {
> -        break;
> +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
> +                                         uint8_t hash[32]) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  uint8_t sig_content[130] = {
> +      "                                "
> +      "                                "
> +      "TLS 1.3, server CertificateVerify\0"};
> +  mg_sha256_ctx sha256;
> +  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> +  mg_sha256_final(sig_content + 98, &sha256);
> +
> +  mg_sha256_init(&sha256);
> +  mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
> +  mg_sha256_final(hash, &sha256);
> +}
> +
> +// read and parse ClientHello record
> +static int mg_tls_server_recv_hello(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *rio = &c->rtls;
> +  uint8_t session_id_len;
> +  uint16_t j;
> +  uint16_t cipher_suites_len;
> +  uint16_t ext_len;
> +  uint8_t *ext;
> +  uint16_t msgsz;
> +
> +  if (!mg_tls_got_record(c)) {
> +    return MG_IO_WAIT;
> +  }
> +  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
> +    mg_error(c, "not a client hello packet");
> +    return -1;
> +  }
> +  msgsz = MG_LOAD_BE16(rio->buf + 3);
> +  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> +  // store client random
> +  memmove(tls->random, rio->buf + 11, sizeof(tls->random));
> +  // store session_id
> +  session_id_len = rio->buf[43];
> +  if (session_id_len == sizeof(tls->session_id)) {
> +    memmove(tls->session_id, rio->buf + 44, session_id_len);
> +  } else if (session_id_len != 0) {
> +    MG_INFO(("bad session id len"));
> +  }
> +  cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
> +  ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len);
> +  ext = rio->buf + 50 + session_id_len + cipher_suites_len;
> +  for (j = 0; j < ext_len;) {
> +    uint16_t k;
> +    uint16_t key_exchange_len;
> +    uint8_t *key_exchange;
> +    uint16_t n = MG_LOAD_BE16(ext + j + 2);
> +    if (ext[j] != 0x00 ||
> +        ext[j + 1] != 0x33) {  // not a key share extension, ignore
> +      j += (uint16_t) (n + 4);
> +      continue;
> +    }
> +    key_exchange_len = MG_LOAD_BE16(ext + j + 5);
> +    key_exchange = ext + j + 6;
> +    for (k = 0; k < key_exchange_len;) {
> +      uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
> +      if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) {
> +        memmove(tls->x25519_cli, key_exchange + k + 4, m);
> +        mg_tls_drop_record(c);
> +        return 0;
>         }
> +      k += (uint16_t) (m + 4);
>       }
> +    j += (uint16_t) (n + 4);
>     }
> -  (void) ev_data;
> -  (void) fn_data;
> -}
> -
> -void mg_mqtt_ping(struct mg_connection *nc) {
> -  mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> -}
> -
> -void mg_mqtt_pong(struct mg_connection *nc) {
> -  mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> +  mg_error(c, "bad client hello");
> +  return -1;
>   }
>
> -void mg_mqtt_disconnect(struct mg_connection *nc) {
> -  mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
> -}
> +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
> +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
> +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
> +
> +// put ServerHello record into wio buffer
> +static void mg_tls_server_send_hello(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *wio = &tls->send;
> +
> +  uint8_t msg_server_hello[122] = {
> +    // server hello, tls 1.2
> +    0x02,
> +    0x00,
> +    0x00,
> +    0x76,
> +    0x03,
> +    0x03,
> +    // random (32 bytes)
> +    PLACEHOLDER_32B,
> +    // session ID length + session ID (32 bytes)
> +    0x20,
> +    PLACEHOLDER_32B,
> +#if defined(CHACHA20) && CHACHA20
> +    // TLS_CHACHA20_POLY1305_SHA256 + no compression
> +    0x13,
> +    0x03,
> +    0x00,
> +#else
> +    // TLS_AES_128_GCM_SHA256 + no compression
> +    0x13,
> +    0x01,
> +    0x00,
> +#endif
> +    // extensions + keyshare
> +    0x00,
> +    0x2e,
> +    0x00,
> +    0x33,
> +    0x00,
> +    0x24,
> +    0x00,
> +    0x1d,
> +    0x00,
> +    0x20,
> +    // x25519 keyshare
> +    PLACEHOLDER_32B,
> +    // supported versions (tls1.3 == 0x304)
> +    0x00,
> +    0x2b,
> +    0x00,
> +    0x02,
> +    0x03,
> +    0x04
> +  };
>
> -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
> -                                      const struct mg_mqtt_opts *opts,
> -                                      mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> -  if (c != NULL) {
> -    struct mg_mqtt_opts empty;
> -    memset(&empty, 0, sizeof(empty));
> -    mg_mqtt_login(c, opts == NULL ? &empty : opts);
> -    c->pfn = mqtt_cb;
> +  // calculate keyshare
> +  uint8_t x25519_pub[X25519_BYTES];
> +  uint8_t x25519_prv[X25519_BYTES];
> +  mg_random(x25519_prv, sizeof(x25519_prv));
> +  mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
> +  mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
> +  mg_tls_hexdump("s x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec));
> +
> +  // fill in the gaps: random + session ID + keyshare
> +  memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
> +  memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id));
> +  memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
> +
> +  // server hello message
> +  mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
> +  mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello));
> +  mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello));
> +
> +  // change cipher message
> +  mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
> +}
> +
> +static void mg_tls_server_send_ext(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  // server extensions
> +  uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
> +  mg_sha256_update(&tls->sha256, ext, sizeof(ext));
> +  mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
> +}
> +
> +static void mg_tls_server_send_cert(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  // server DER certificate (empty)
> +  size_t n = tls->server_cert_der.len;
> +  uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
> +  if (cert == NULL) {
> +    mg_error(c, "tls cert oom");
> +    return;
>     }
> -  return c;
> -}
> +  cert[0] = 0x0b;                                // handshake header
> +  cert[1] = (uint8_t) (((n + 9) >> 16) & 255U);  // 3 bytes: payload length
> +  cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
> +  cert[3] = (uint8_t) ((n + 9) & 255U);
> +  cert[4] = 0;                                   // request context
> +  cert[5] = (uint8_t) (((n + 5) >> 16) & 255U);  // 3 bytes: cert (s) length
> +  cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
> +  cert[7] = (uint8_t) ((n + 5) & 255U);
> +  cert[8] =
> +      (uint8_t) (((n) >> 16) & 255U);  // 3 bytes: first (and only) cert len
> +  cert[9] = (uint8_t) (((n) >> 8) & 255U);
> +  cert[10] = (uint8_t) (n & 255U);
> +  // bytes 11+ are certificate in DER format
> +  memmove(cert + 11, tls->server_cert_der.buf, n);
> +  cert[11 + n] = cert[12 + n] = 0;  // certificate extensions (none)
> +  mg_sha256_update(&tls->sha256, cert, 13 + n);
> +  mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
> +  free(cert);
> +}
> +
> +// type adapter between uECC hash context and our sha256 implementation
> +typedef struct SHA256_HashContext {
> +  MG_UECC_HashContext uECC;
> +  mg_sha256_ctx ctx;
> +} SHA256_HashContext;
> +
> +static void init_SHA256(const MG_UECC_HashContext *base) {
> +  SHA256_HashContext *c = (SHA256_HashContext *) base;
> +  mg_sha256_init(&c->ctx);
> +}
> +
> +static void update_SHA256(const MG_UECC_HashContext *base,
> +                          const uint8_t *message, unsigned message_size) {
> +  SHA256_HashContext *c = (SHA256_HashContext *) base;
> +  mg_sha256_update(&c->ctx, message, message_size);
> +}
> +static void finish_SHA256(const MG_UECC_HashContext *base,
> +                          uint8_t *hash_result) {
> +  SHA256_HashContext *c = (SHA256_HashContext *) base;
> +  mg_sha256_final(hash_result, &c->ctx);
> +}
> +
> +static void mg_tls_server_send_cert_verify(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  // server certificate verify packet
> +  uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
> +  size_t sigsz, verifysz = 0;
> +  uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
> +  struct SHA256_HashContext ctx = {
> +      {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
> +      {{0}, 0, 0, {0}}};
> +  int neg1, neg2;
> +  uint8_t sig[64] = {0};
> +
> +  mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
> +
> +  mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash), &ctx.uECC,
> +                             sig, mg_uecc_secp256r1());
> +
> +  neg1 = !!(sig[0] & 0x80);
> +  neg2 = !!(sig[32] & 0x80);
> +  verify[8] = 0x30;  // ASN.1 SEQUENCE
> +  verify[9] = (uint8_t) (68 + neg1 + neg2);
> +  verify[10] = 0x02;  // ASN.1 INTEGER
> +  verify[11] = (uint8_t) (32 + neg1);
> +  memmove(verify + 12 + neg1, sig, 32);
> +  verify[12 + 32 + neg1] = 0x02;  // ASN.1 INTEGER
> +  verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
> +  memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
> +
> +  sigsz = (size_t) (70 + neg1 + neg2);
> +  verifysz = 8U + sigsz;
> +  verify[3] = (uint8_t) (sigsz + 4);
> +  verify[7] = (uint8_t) sigsz;
> +
> +  mg_sha256_update(&tls->sha256, verify, verifysz);
> +  mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
> +}
> +
> +static void mg_tls_server_send_finish(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *wio = &tls->send;
> +  mg_sha256_ctx sha256;
> +  uint8_t hash[32];
> +  uint8_t finish[36] = {0x14, 0, 0, 32};
> +  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> +  mg_sha256_final(hash, &sha256);
> +  mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash, 32);
> +  mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> +  mg_io_send(c, wio->buf, wio->len);
> +  wio->len = 0;
> +
> +  mg_sha256_update(&tls->sha256, finish, sizeof(finish));
> +}
> +
> +static int mg_tls_server_recv_finish(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  // we have to backup sha256 value to restore it later, since Finished record
> +  // is exceptional and is not supposed to be added to the rolling hash
> +  // calculation.
> +  mg_sha256_ctx sha256 = tls->sha256;
> +  if (mg_tls_recv_record(c) < 0) {
> +    return -1;
> +  }
> +  if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> +    mg_error(c, "expected Finish but got msg 0x%02x", tls->recv.buf[0]);
> +    return -1;
> +  }
> +  mg_tls_drop_message(c);
>
> -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
> -                                     mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> -  if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> -  return c;
> +  // restore hash
> +  tls->sha256 = sha256;
> +  return 0;
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/net.c"
> +static void mg_tls_client_send_hello(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *wio = &tls->send;
> +
> +  const char *hostname = tls->hostname;
> +  size_t hostnamesz = strlen(tls->hostname);
> +  uint8_t x25519_pub[X25519_BYTES];
> +
> +  uint8_t msg_client_hello[162 + 32] = {
> +    // TLS Client Hello header reported as TLS1.2 (5)
> +    0x16,
> +    0x03,
> +    0x01,
> +    0x00,
> +    0xfe,
> +    // server hello, tls 1.2 (6)
> +    0x01,
> +    0x00,
> +    0x00,
> +    0x8c,
> +    0x03,
> +    0x03,
> +    // random (32 bytes)
> +    PLACEHOLDER_32B,
> +    // session ID length + session ID (32 bytes)
> +    0x20,
> +    PLACEHOLDER_32B,
> +#if defined(CHACHA20) && CHACHA20
> +    // TLS_CHACHA20_POLY1305_SHA256 + no compression
> +    0x13,
> +    0x03,
> +    0x00,
> +#else
> +    0x00,
> +    0x02,  // size = 2 bytes
> +    0x13,
> +    0x01,  // TLS_AES_128_GCM_SHA256
> +    0x01,
> +    0x00,  // no compression
>   #endif
>
> +    // extensions + keyshare
> +    0x00,
> +    0xfe,
> +    // x25519 keyshare
> +    0x00,
> +    0x33,
> +    0x00,
> +    0x26,
> +    0x00,
> +    0x24,
> +    0x00,
> +    0x1d,
> +    0x00,
> +    0x20,
> +    PLACEHOLDER_32B,
> +    // supported groups (x25519)
> +    0x00,
> +    0x0a,
> +    0x00,
> +    0x04,
> +    0x00,
> +    0x02,
> +    0x00,
> +    0x1d,
> +    // supported versions (tls1.3 == 0x304)
> +    0x00,
> +    0x2b,
> +    0x00,
> +    0x03,
> +    0x02,
> +    0x03,
> +    0x04,
> +    // session ticket (none)
> +    0x00,
> +    0x23,
> +    0x00,
> +    0x00,
> +    // signature algorithms (we don't care, so list all the common ones)
> +    0x00,
> +    0x0d,
> +    0x00,
> +    0x24,
> +    0x00,
> +    0x22,
> +    0x04,
> +    0x03,
> +    0x05,
> +    0x03,
> +    0x06,
> +    0x03,
> +    0x08,
> +    0x07,
> +    0x08,
> +    0x08,
> +    0x08,
> +    0x1a,
> +    0x08,
> +    0x1b,
> +    0x08,
> +    0x1c,
> +    0x08,
> +    0x09,
> +    0x08,
> +    0x0a,
> +    0x08,
> +    0x0b,
> +    0x08,
> +    0x04,
> +    0x08,
> +    0x05,
> +    0x08,
> +    0x06,
> +    0x04,
> +    0x01,
> +    0x05,
> +    0x01,
> +    0x06,
> +    0x01,
> +    // server name
> +    0x00,
> +    0x00,
> +    0x00,
> +    0xfe,
> +    0x00,
> +    0xfe,
> +    0x00,
> +    0x00,
> +    0xfe
> +  };
>
> +  // patch ClientHello with correct hostname length + offset:
> +  MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
> +  MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
> +  MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
> +  MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
> +  MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
> +  MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
> +
> +  // calculate keyshare
> +  mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
> +  mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
> +
> +  // fill in the gaps: random + session ID + keyshare
> +  mg_random(tls->session_id, sizeof(tls->session_id));
> +  mg_random(tls->random, sizeof(tls->random));
> +  memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
> +  memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
> +  memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
> +
> +  // server hello message
> +  mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello));
> +  mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
> +  mg_sha256_update(&tls->sha256, msg_client_hello + 5,
> +                   sizeof(msg_client_hello) - 5);
> +  mg_sha256_update(&tls->sha256, (uint8_t *) hostname, strlen(hostname));
> +
> +  // change cipher message
> +  mg_iobuf_add(wio, wio->len, (const char *) "\x14\x03\x03\x00\x01\x01", 6);
> +  mg_io_send(c, wio->buf, wio->len);
> +  wio->len = 0;
> +}
> +
> +static int mg_tls_client_recv_hello(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *rio = &c->rtls;
> +  uint16_t msgsz;
> +  uint8_t *ext;
> +  uint16_t ext_len;
> +  int j;
> +
> +  if (!mg_tls_got_record(c)) {
> +    return MG_IO_WAIT;
> +  }
> +  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_SERVER_HELLO) {
> +    if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
> +      mg_error(c, "tls alert %d", rio->buf[6]);
> +      return -1;
> +    }
> +    MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0], rio->buf[5]));
> +    mg_error(c, "not a server hello packet");
> +    return -1;
> +  }
>
> -
> -
> -
> -
> -size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) {
> -  size_t old = c->send.len;
> -  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> -  return c->send.len - old;
> -}
> -
> -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> -  size_t len = 0;
> -  va_list ap;
> -  va_start(ap, fmt);
> -  len = mg_vprintf(c, fmt, &ap);
> -  va_end(ap);
> -  return len;
> -}
> -
> -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> -  if (mg_vcasecmp(&str, "localhost") != 0) return false;
> -  addr->ip = mg_htonl(0x7f000001);
> -  addr->is_ip6 = false;
> -  return true;
> +  msgsz = MG_LOAD_BE16(rio->buf + 3);
> +  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> +
> +  ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
> +  ext = rio->buf + 5 + 39 + 32 + 3 + 2;
> +
> +  for (j = 0; j < ext_len;) {
> +    uint16_t ext_type = MG_LOAD_BE16(ext + j);
> +    uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
> +    uint16_t group;
> +    uint8_t *key_exchange;
> +    uint16_t key_exchange_len;
> +    if (ext_type != 0x0033) {  // not a key share extension, ignore
> +      j += (uint16_t) (ext_len2 + 4);
> +      continue;
> +    }
> +    group = MG_LOAD_BE16(ext + j + 4);
> +    if (group != 0x001d) {
> +      mg_error(c, "bad key exchange group");
> +      return -1;
> +    }
> +    key_exchange_len = MG_LOAD_BE16(ext + j + 6);
> +    key_exchange = ext + j + 8;
> +    if (key_exchange_len != 32) {
> +      mg_error(c, "bad key exchange length");
> +      return -1;
> +    }
> +    mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
> +    mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
> +    mg_tls_drop_record(c);
> +    /* generate handshake keys */
> +    mg_tls_generate_handshake_keys(c);
> +    return 0;
> +  }
> +  mg_error(c, "bad client hello");
> +  return -1;
>   }
>
> -static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> -  if (str.len > 0) return false;
> -  addr->ip = 0;
> -  addr->is_ip6 = false;
> -  return true;
> +static int mg_tls_client_recv_ext(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (mg_tls_recv_record(c) < 0) {
> +    return -1;
> +  }
> +  if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
> +    mg_error(c, "expected server extensions but got msg 0x%02x",
> +             tls->recv.buf[0]);
> +    return -1;
> +  }
> +  mg_tls_drop_message(c);
> +  return 0;
>   }
>
> -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> -  uint8_t data[4] = {0, 0, 0, 0};
> -  size_t i, num_dots = 0;
> -  for (i = 0; i < str.len; i++) {
> -    if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> -      int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
> -      if (octet > 255) return false;
> -      data[num_dots] = (uint8_t) octet;
> -    } else if (str.ptr[i] == '.') {
> -      if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false;
> -      num_dots++;
> -    } else {
> -      return false;
> -    }
> +static int mg_tls_client_recv_cert(struct mg_connection *c) {
> +  uint8_t *cert;
> +  uint32_t certsz;
> +  struct mg_der_tlv oid, pubkey, seq, subj;
> +  int subj_match = 0;
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (mg_tls_recv_record(c) < 0) {
> +    return -1;
> +  }
> +  if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
> +    mg_error(c, "expected server certificate but got msg 0x%02x",
> +             tls->recv.buf[0]);
> +    return -1;
> +  }
> +  if (tls->skip_verification) {
> +    mg_tls_drop_message(c);
> +    return 0;
>     }
> -  if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
> -  memcpy(&addr->ip, data, sizeof(data));
> -  addr->is_ip6 = false;
> -  return true;
> -}
>
> -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> -  int i;
> -  if (str.len < 14) return false;
> -  if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false;
> -  for (i = 2; i < 6; i++) {
> -    if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
> +  if (tls->recv.len < 11) {
> +    mg_error(c, "certificate list too short");
> +    return -1;
>     }
> -  if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false;
> -  memset(addr->ip6, 0, sizeof(addr->ip6));
> -  addr->ip6[10] = addr->ip6[11] = 255;
> -  memcpy(&addr->ip6[12], &addr->ip, 4);
> -  addr->is_ip6 = true;
> -  return true;
> -}
>
> -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> -  size_t i, j = 0, n = 0, dc = 42;
> -  if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
> -  if (mg_v4mapped(str, addr)) return true;
> -  for (i = 0; i < str.len; i++) {
> -    if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
> -        (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
> -        (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
> -      unsigned long val;
> -      if (i > j + 3) return false;
> -      // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j]));
> -      val = mg_unhexn(&str.ptr[j], i - j + 1);
> -      addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
> -      addr->ip6[n + 1] = (uint8_t) (val & 255);
> -    } else if (str.ptr[i] == ':') {
> -      j = i + 1;
> -      if (i > 0 && str.ptr[i - 1] == ':') {
> -        dc = n;  // Double colon
> -        if (i > 1 && str.ptr[i - 2] == ':') return false;
> -      } else if (i > 0) {
> -        n += 2;
> +  cert = tls->recv.buf + 11;
> +  certsz = MG_LOAD_BE24(tls->recv.buf + 8);
> +  if (certsz > tls->recv.len - 11) {
> +    mg_error(c, "certificate too long: %d vs %d", certsz, tls->recv.len - 11);
> +    return -1;
> +  }
> +
> +  do {
> +    // secp256r1 public key
> +    if (mg_der_find(cert, certsz,
> +                    (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
> +                    &oid) < 0) {
> +      mg_error(c, "certificate secp256r1 public key OID not found");
> +      return -1;
> +    }
> +    if (mg_der_to_tlv(oid.value + oid.len,
> +                      (size_t) (cert + certsz - (oid.value + oid.len)),
> +                      &pubkey) < 0) {
> +      mg_error(c, "certificate secp256r1 public key not found");
> +      return -1;
> +    }
> +
> +    // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32 content bytes
> +    if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] != 0 ||
> +        pubkey.value[1] != 4) {
> +      mg_error(c, "unsupported public key bitstring encoding");
> +      return -1;
> +    }
> +    memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
> +  } while (0);
> +
> +  // Subject Alternative Names
> +  do {
> +    if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3, &oid) < 0) {
> +      mg_error(c, "certificate does not contain subject alternative names");
> +      return -1;
> +    }
> +    if (mg_der_to_tlv(oid.value + oid.len,
> +                      (size_t) (cert + certsz - (oid.value + oid.len)),
> +                      &seq) < 0) {
> +      mg_error(c, "certificate subject alternative names not found");
> +      return -1;
> +    }
> +    if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
> +      mg_error(
> +          c,
> +          "certificate subject alternative names is not a constructed object");
> +      return -1;
> +    }
> +    MG_VERBOSE(("verify hostname %s", tls->hostname));
> +    while (seq.len > 0) {
> +      if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
> +        mg_error(c, "bad subject alternative name");
> +        return -1;
>         }
> -      if (n > 14) return false;
> -      addr->ip6[n] = addr->ip6[n + 1] = 0;  // For trailing ::
> -    } else {
> -      return false;
> +      MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
> +      if (mg_match(mg_str((const char *) tls->hostname),
> +                   mg_str_n((const char *) subj.value, subj.len), NULL)) {
> +        subj_match = 1;
> +        break;
> +      }
> +      seq.len = (uint32_t) (seq.value + seq.len - (subj.value + subj.len));
> +      seq.value = subj.value + subj.len;
> +    }
> +    if (!subj_match) {
> +      mg_error(c, "certificate did not match the hostname");
> +      return -1;
>       }
> +  } while (0);
> +
> +  mg_tls_drop_message(c);
> +  mg_tls_calc_cert_verify_hash(c, tls->sighash);
> +  return 0;
> +}
> +
> +static int mg_tls_client_recv_cert_verify(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (mg_tls_recv_record(c) < 0) {
> +    return -1;
>     }
> -  if (n < 14 && dc == 42) return false;
> -  if (n < 14) {
> -    memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
> -    memset(&addr->ip6[dc], 0, 14 - n);
> +  if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
> +    mg_error(c, "expected server certificate verify but got msg 0x%02x",
> +             tls->recv.buf[0]);
> +    return -1;
> +  }
> +  // Ignore CertificateVerify is strict checks are not required
> +  if (tls->skip_verification) {
> +    mg_tls_drop_message(c);
> +    return 0;
>     }
> -  addr->is_ip6 = true;
> -  return true;
> -}
>
> -bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> -  // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
> -  return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
> -         mg_aton6(str, addr);
> +  // Extract certificate signature and verify it using pubkey and sighash
> +  do {
> +    uint8_t sig[64];
> +    struct mg_der_tlv seq, a, b;
> +    if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) < 0) {
> +      mg_error(c, "verification message is not an ASN.1 DER sequence");
> +      return -1;
> +    }
> +    if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
> +      mg_error(c, "missing first part of the signature");
> +      return -1;
> +    }
> +    if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
> +      mg_error(c, "missing second part of the signature");
> +      return -1;
> +    }
> +    // Integers may be padded with zeroes
> +    if (a.len > 32) {
> +      a.value = a.value + (a.len - 32);
> +      a.len = 32;
> +    }
> +    if (b.len > 32) {
> +      b.value = b.value + (b.len - 32);
> +      b.len = 32;
> +    }
> +
> +    memmove(sig, a.value, a.len);
> +    memmove(sig + 32, b.value, b.len);
> +
> +    if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig,
> +                       mg_uecc_secp256r1()) != 1) {
> +      mg_error(c, "failed to verify certificate");
> +      return -1;
> +    }
> +  } while (0);
> +
> +  mg_tls_drop_message(c);
> +  return 0;
>   }
>
> -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> -  struct mg_connection *c =
> -      (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
> -  if (c != NULL) {
> -    c->mgr = mgr;
> -    c->send.align = c->recv.align = MG_IO_SIZE;
> -    c->id = ++mgr->nextid;
> +static int mg_tls_client_recv_finish(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (mg_tls_recv_record(c) < 0) {
> +    return -1;
>     }
> -  return c;
> +  if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> +    mg_error(c, "expected server finished but got msg 0x%02x",
> +             tls->recv.buf[0]);
> +    return -1;
> +  }
> +  mg_tls_drop_message(c);
> +  return 0;
>   }
>
> -void mg_close_conn(struct mg_connection *c) {
> -  mg_resolve_cancel(c);  // Close any pending DNS query
> -  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> -  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> -  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> -  // Order of operations is important. `MG_EV_CLOSE` event must be fired
> -  // before we deallocate received data, see #1331
> -  mg_call(c, MG_EV_CLOSE, NULL);
> -  MG_DEBUG(("%lu %p closed", c->id, c->fd));
> -
> -  mg_tls_free(c);
> -  mg_iobuf_free(&c->recv);
> -  mg_iobuf_free(&c->send);
> -  memset(c, 0, sizeof(*c));
> -  free(c);
> +static void mg_tls_client_send_finish(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  struct mg_iobuf *wio = &tls->send;
> +  mg_sha256_ctx sha256;
> +  uint8_t hash[32];
> +  uint8_t finish[36] = {0x14, 0, 0, 32};
> +  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> +  mg_sha256_final(hash, &sha256);
> +  mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash, 32);
> +  mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> +  mg_io_send(c, wio->buf, wio->len);
> +  wio->len = 0;
> +}
> +
> +static void mg_tls_client_handshake(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  switch (tls->state) {
> +    case MG_TLS_STATE_CLIENT_START:
> +      mg_tls_client_send_hello(c);
> +      tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
> +      // Fallthrough
> +    case MG_TLS_STATE_CLIENT_WAIT_SH:
> +      if (mg_tls_client_recv_hello(c) < 0) {
> +        break;
> +      }
> +      tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
> +      // Fallthrough
> +    case MG_TLS_STATE_CLIENT_WAIT_EE:
> +      if (mg_tls_client_recv_ext(c) < 0) {
> +        break;
> +      }
> +      tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
> +      // Fallthrough
> +    case MG_TLS_STATE_CLIENT_WAIT_CERT:
> +      if (mg_tls_client_recv_cert(c) < 0) {
> +        break;
> +      }
> +      tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
> +      // Fallthrough
> +    case MG_TLS_STATE_CLIENT_WAIT_CV:
> +      if (mg_tls_client_recv_cert_verify(c) < 0) {
> +        break;
> +      }
> +      tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
> +      // Fallthrough
> +    case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
> +      if (mg_tls_client_recv_finish(c) < 0) {
> +        break;
> +      }
> +      mg_tls_client_send_finish(c);
> +      mg_tls_generate_application_keys(c);
> +      tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
> +      c->is_tls_hs = 0;
> +      break;
> +    default: mg_error(c, "unexpected client state: %d", tls->state); break;
> +  }
>   }
>
> -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
> -                                 mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = NULL;
> -  if (url == NULL || url[0] == '\0') {
> -    MG_ERROR(("null url"));
> -  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> -    MG_ERROR(("OOM"));
> -  } else {
> -    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> -    c->is_udp = (strncmp(url, "udp:", 4) == 0);
> -    c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> -    c->fn = fn;
> -    c->is_client = true;
> -    c->fn_data = fn_data;
> -    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> -    mg_call(c, MG_EV_OPEN, NULL);
> -    mg_resolve(c, url);
> +static void mg_tls_server_handshake(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  switch (tls->state) {
> +    case MG_TLS_STATE_SERVER_START:
> +      if (mg_tls_server_recv_hello(c) < 0) {
> +        return;
> +      }
> +      mg_tls_server_send_hello(c);
> +      mg_tls_generate_handshake_keys(c);
> +      mg_tls_server_send_ext(c);
> +      mg_tls_server_send_cert(c);
> +      mg_tls_server_send_cert_verify(c);
> +      mg_tls_server_send_finish(c);
> +      tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
> +      // fallthrough
> +    case MG_TLS_STATE_SERVER_NEGOTIATED:
> +      if (mg_tls_server_recv_finish(c) < 0) {
> +        return;
> +      }
> +      mg_tls_generate_application_keys(c);
> +      tls->state = MG_TLS_STATE_SERVER_CONNECTED;
> +      c->is_tls_hs = 0;
> +      return;
> +    default: mg_error(c, "unexpected server state: %d", tls->state); break;
>     }
> -  return c;
>   }
>
> -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
> -                                mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = NULL;
> -  if ((c = mg_alloc_conn(mgr)) == NULL) {
> -    MG_ERROR(("OOM %s", url));
> -  } else if (!mg_open_listener(c, url)) {
> -    MG_ERROR(("Failed: %s, errno %d", url, errno));
> -    free(c);
> -    c = NULL;
> +void mg_tls_handshake(struct mg_connection *c) {
> +  if (c->is_client) {
> +    mg_tls_client_handshake(c);
>     } else {
> -    c->is_listening = 1;
> -    c->is_udp = strncmp(url, "udp:", 4) == 0;
> -    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> -    c->fn = fn;
> -    c->fn_data = fn_data;
> -    mg_call(c, MG_EV_OPEN, NULL);
> -    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> +    mg_tls_server_handshake(c);
>     }
> -  return c;
>   }
>
> -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> -                                mg_event_handler_t fn, void *fn_data) {
> -  struct mg_connection *c = mg_alloc_conn(mgr);
> -  if (c != NULL) {
> -    c->fd = (void *) (size_t) fd;
> -    c->fn = fn;
> -    c->fn_data = fn_data;
> -    MG_EPOLL_ADD(c);
> -    mg_call(c, MG_EV_OPEN, NULL);
> -    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> +static int mg_parse_pem(const struct mg_str pem, const struct mg_str label,
> +                        struct mg_str *der) {
> +  size_t n = 0, m = 0;
> +  char *s;
> +  const char *c;
> +  struct mg_str caps[5];
> +  if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END #-----#"), caps)) {
> +    der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
> +    der->len = pem.len;
> +    return 0;
> +  }
> +  if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label) != 0) {
> +    return -1;  // bad label
> +  }
> +  if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
> +    return -1;
>     }
> -  return c;
> -}
>
> -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
> -                              unsigned flags, void (*fn)(void *), void *arg) {
> -  struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> -  if (t != NULL) {
> -    mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> -    t->id = mgr->timerid++;
> +  for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
> +    if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
> +      continue;
> +    }
> +    s[n++] = *c;
>     }
> -  return t;
> +  m = mg_base64_decode(s, n, s, n);
> +  if (m == 0) {
> +    free(s);
> +    return -1;
> +  }
> +  der->buf = s;
> +  der->len = m;
> +  return 0;
>   }
>
> -void mg_mgr_free(struct mg_mgr *mgr) {
> -  struct mg_connection *c;
> -  struct mg_timer *tmp, *t = mgr->timers;
> -  while (t != NULL) tmp = t->next, free(t), t = tmp;
> -  mgr->timers = NULL;  // Important. Next call to poll won't touch timers
> -  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> -  mg_mgr_poll(mgr, 0);
> -#if MG_ENABLE_FREERTOS_TCP
> -  FreeRTOS_DeleteSocketSet(mgr->ss);
> -#endif
> -  MG_DEBUG(("All connections closed"));
> -#if MG_ENABLE_EPOLL
> -  if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> -#endif
> -}
> +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> +  struct mg_str key;
> +  struct tls_data *tls = (struct tls_data *) calloc(1, sizeof(struct tls_data));
> +  if (tls == NULL) {
> +    mg_error(c, "tls oom");
> +    return;
> +  }
>
> -void mg_mgr_init(struct mg_mgr *mgr) {
> -  memset(mgr, 0, sizeof(*mgr));
> -#if MG_ENABLE_EPOLL
> -  if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll: %d", errno));
> -#else
> -  mgr->epoll_fd = -1;
> -#endif
> -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> -  // clang-format off
> -  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> -  // clang-format on
> -#elif MG_ENABLE_FREERTOS_TCP
> -  mgr->ss = FreeRTOS_CreateSocketSet();
> -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> -  // Ignore SIGPIPE signal, so if client cancels the request, it
> -  // won't kill the whole process.
> -  signal(SIGPIPE, SIG_IGN);
> -#endif
> -  mgr->dnstimeout = 3000;
> -  mgr->dns4.url = "udp://8.8.8.8:53";
> -  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> -}
> +  tls->state =
> +      c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/rpc.c"
> -#endif
> +  tls->skip_verification = opts->skip_verification;
> +  tls->send.align = MG_IO_SIZE;
>
> +  c->tls = tls;
> +  c->is_tls = c->is_tls_hs = 1;
> +  mg_sha256_init(&tls->sha256);
>
> -void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> -                void (*fn)(struct mg_rpc_req *), void *fn_data) {
> -  struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> -  if (rpc != NULL) {
> -    rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data;
> -    rpc->next = *head, *head = rpc;
> +  // save hostname (client extension)
> +  if (opts->name.len > 0) {
> +    if (opts->name.len >= sizeof(tls->hostname) - 1) {
> +      mg_error(c, "hostname too long");
> +    }
> +    strncpy((char *) tls->hostname, opts->name.buf, sizeof(tls->hostname) - 1);
> +    tls->hostname[opts->name.len] = 0;
>     }
> -}
>
> -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
> -  struct mg_rpc *r;
> -  while ((r = *head) != NULL) {
> -    if (r->fn == fn || fn == NULL) {
> -      *head = r->next;
> -      free((void *) r->method.ptr);
> -      free(r);
> -    } else {
> -      head = &(*head)->next;
> -    }
> +  if (c->is_client) {
> +    tls->server_cert_der.buf = NULL;
> +    return;
>     }
> -}
>
> -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
> -  struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> -  while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
> -  if (h != NULL) {
> -    r->rpc = h;
> -    h->fn(r);
> +  // parse PEM or DER certificate
> +  if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"), &tls->server_cert_der) <
> +      0) {
> +    MG_ERROR(("Failed to load certificate"));
> +    return;
> +  }
> +
> +  // parse PEM or DER EC key
> +  if (opts->key.buf == NULL) {
> +    mg_error(c, "certificate provided without a private key");
> +    return;
> +  }
> +
> +  if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) == 0) {
> +    if (key.len < 39) {
> +      MG_ERROR(("EC private key too short"));
> +      return;
> +    }
> +    // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
> +    // 30 nn 02 01 01 04 20 [key] ...
> +    if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
> +      MG_ERROR(("EC private key: ASN.1 bad sequence"));
> +      return;
> +    }
> +    if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
> +      MG_ERROR(("EC private key: ASN.1 bad data"));
> +    }
> +    memmove(tls->server_key, key.buf + 7, 32);
> +    free((void *) key.buf);
> +  } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) {
> +    mg_error(c, "PKCS8 private key format is not supported");
>     } else {
> -    mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr);
> +    mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
>     }
>   }
>
> -void mg_rpc_process(struct mg_rpc_req *r) {
> -  int len, off = mg_json_get(r->frame, "$.method", &len);
> -  if (off > 0 && r->frame.ptr[off] == '"') {
> -    struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2);
> -    mg_rpc_call(r, method);
> -  } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> -             (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> -    mg_rpc_call(r, mg_str(""));  // JSON response! call "" method handler
> -  } else {
> -    mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len, r->frame.ptr);  // Invalid
> +void mg_tls_free(struct mg_connection *c) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  if (tls != NULL) {
> +    mg_iobuf_free(&tls->send);
> +    free((void *) tls->server_cert_der.buf);
>     }
> +  free(c->tls);
> +  c->tls = NULL;
>   }
>
> -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
> -  int len, off = mg_json_get(r->frame, "$.id", &len);
> -  if (off > 0) {
> -    mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
> -               &r->frame.ptr[off], "result");
> -    mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> -    mg_xprintf(r->pfn, r->pfn_data, "}");
> +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  long n = MG_IO_WAIT;
> +  if (len > MG_IO_SIZE) len = MG_IO_SIZE;
> +  mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
> +  while (tls->send.len > 0 &&
> +         (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
> +    mg_iobuf_del(&tls->send, 0, (size_t) n);
>     }
> +  if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
> +  return (long) len;
>   }
>
> -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> -  va_list ap;
> -  va_start(ap, fmt);
> -  mg_rpc_vok(r, fmt, &ap);
> -  va_end(ap);
> -}
> +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> +  int r = 0;
> +  struct tls_data *tls = (struct tls_data *) c->tls;
> +  size_t minlen;
>
> -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) {
> -  int len, off = mg_json_get(r->frame, "$.id", &len);
> -  mg_xprintf(r->pfn, r->pfn_data, "{");
> -  if (off > 0) {
> -    mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len, &r->frame.ptr[off]);
> +  r = mg_tls_recv_record(c);
> +  if (r < 0) {
> +    return r;
>     }
> -  mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error", "code", code,
> -             "message");
> -  mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> -  mg_xprintf(r->pfn, r->pfn_data, "}}");
> +  if (tls->content_type != MG_TLS_APP_DATA) {
> +    tls->recv.len = 0;
> +    mg_tls_drop_record(c);
> +    return MG_IO_WAIT;
> +  }
> +  minlen = len < tls->recv.len ? len : tls->recv.len;
> +  memmove(buf, tls->recv.buf, minlen);
> +  tls->recv.buf += minlen;
> +  tls->recv.len -= minlen;
> +  if (tls->recv.len == 0) {
> +    mg_tls_drop_record(c);
> +  }
> +  return (long) minlen;
>   }
>
> -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
> -  va_list ap;
> -  va_start(ap, fmt);
> -  mg_rpc_verr(r, code, fmt, &ap);
> -  va_end(ap);
> +size_t mg_tls_pending(struct mg_connection *c) {
> +  return mg_tls_got_record(c) ? 1 : 0;
>   }
>
> -static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
> -  struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
> -  size_t len = 0;
> -  for (h = *head; h != NULL; h = h->next) {
> -    if (h->method.len == 0) continue;  // Ignore response handler
> -    len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
> -                      (int) h->method.len, h->method.ptr);
> -  }
> -  return len;
> +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> +  (void) mgr;
>   }
>
> -void mg_rpc_list(struct mg_rpc_req *r) {
> -  mg_rpc_ok(r, "[%M]", print_methods, r->head);
> +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> +  (void) mgr;
>   }
> +#endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/sha1.c"
> +#line 1 "src/tls_dummy.c"
> +#endif
> +
> +
> +#if MG_TLS == MG_TLS_NONE
> +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> +  (void) opts;
> +  mg_error(c, "TLS is not enabled");
> +}
> +void mg_tls_handshake(struct mg_connection *c) {
> +  (void) c;
> +}
> +void mg_tls_free(struct mg_connection *c) {
> +  (void) c;
> +}
> +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> +  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> +}
> +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> +  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> +}
> +size_t mg_tls_pending(struct mg_connection *c) {
> +  (void) c;
> +  return 0;
> +}
> +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> +  (void) mgr;
> +}
> +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> +  (void) mgr;
> +}
>   #endif
> -/* Copyright(c) By Steve Reid <steve@edmweb.com> */
> -/* 100% Public Domain */
>
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/tls_mbed.c"
> +#endif
>
>
> -union char64long16 {
> -  unsigned char c[64];
> -  uint32_t l[16];
> -};
>
> -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
> +#if MG_TLS == MG_TLS_MBED
>
> -static uint32_t blk0(union char64long16 *block, int i) {
> -  if (MG_BIG_ENDIAN) {
> -  } else {
> -    block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> -                  (rol(block->l[i], 8) & 0x00FF00FF);
> +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
> +#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
> +#else
> +#define MG_MBEDTLS_RNG_GET
> +#endif
> +
> +static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> +  mg_random(buf, len);
> +  (void) ctx;
> +  return 0;
> +}
> +
> +static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
> +  int rc;
> +  if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true;
> +  if (str.buf[0] == '-') str.len++;  // PEM, include trailing NUL
> +  if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf, str.len)) != 0) {
> +    MG_ERROR(("cert err %#x", -rc));
> +    return false;
>     }
> -  return block->l[i];
> +  return true;
>   }
>
> -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
> -#undef blk
> -#undef R0
> -#undef R1
> -#undef R2
> -#undef R3
> -#undef R4
> +static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
> +  int rc;
> +  if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true;
> +  if (str.buf[0] == '-') str.len++;  // PEM, include trailing NUL
> +  if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len, NULL,
> +                                 0 MG_MBEDTLS_RNG_GET)) != 0) {
> +    MG_ERROR(("key err %#x", -rc));
> +    return false;
> +  }
> +  return true;
> +}
>
> -#define blk(i)                                                               \
> -  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
> -                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
> -                          1))
> -#define R0(v, w, x, y, z, i)                                          \
> -  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
> -  w = rol(w, 30);
> -#define R1(v, w, x, y, z, i)                                  \
> -  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> -  w = rol(w, 30);
> -#define R2(v, w, x, y, z, i)                          \
> -  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> -  w = rol(w, 30);
> -#define R3(v, w, x, y, z, i)                                        \
> -  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
> -  w = rol(w, 30);
> -#define R4(v, w, x, y, z, i)                          \
> -  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> -  w = rol(w, 30);
> +void mg_tls_free(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  if (tls != NULL) {
> +    mbedtls_ssl_free(&tls->ssl);
> +    mbedtls_pk_free(&tls->pk);
> +    mbedtls_x509_crt_free(&tls->ca);
> +    mbedtls_x509_crt_free(&tls->cert);
> +    mbedtls_ssl_config_free(&tls->conf);
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +    mbedtls_ssl_ticket_free(&tls->ticket);
> +#endif
> +    free(tls);
> +    c->tls = NULL;
> +  }
> +}
>
> -static void mg_sha1_transform(uint32_t state[5],
> -                              const unsigned char buffer[64]) {
> -  uint32_t a, b, c, d, e;
> -  union char64long16 block[1];
> +static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
> +  long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> +  MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *) ctx)->id, n, errno));
> +  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> +  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> +  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> +  return (int) n;
> +}
>
> -  memcpy(block, buffer, 64);
> -  a = state[0];
> -  b = state[1];
> -  c = state[2];
> -  d = state[3];
> -  e = state[4];
> -  R0(a, b, c, d, e, 0);
> -  R0(e, a, b, c, d, 1);
> -  R0(d, e, a, b, c, 2);
> -  R0(c, d, e, a, b, 3);
> -  R0(b, c, d, e, a, 4);
> -  R0(a, b, c, d, e, 5);
> -  R0(e, a, b, c, d, 6);
> -  R0(d, e, a, b, c, 7);
> -  R0(c, d, e, a, b, 8);
> -  R0(b, c, d, e, a, 9);
> -  R0(a, b, c, d, e, 10);
> -  R0(e, a, b, c, d, 11);
> -  R0(d, e, a, b, c, 12);
> -  R0(c, d, e, a, b, 13);
> -  R0(b, c, d, e, a, 14);
> -  R0(a, b, c, d, e, 15);
> -  R1(e, a, b, c, d, 16);
> -  R1(d, e, a, b, c, 17);
> -  R1(c, d, e, a, b, 18);
> -  R1(b, c, d, e, a, 19);
> -  R2(a, b, c, d, e, 20);
> -  R2(e, a, b, c, d, 21);
> -  R2(d, e, a, b, c, 22);
> -  R2(c, d, e, a, b, 23);
> -  R2(b, c, d, e, a, 24);
> -  R2(a, b, c, d, e, 25);
> -  R2(e, a, b, c, d, 26);
> -  R2(d, e, a, b, c, 27);
> -  R2(c, d, e, a, b, 28);
> -  R2(b, c, d, e, a, 29);
> -  R2(a, b, c, d, e, 30);
> -  R2(e, a, b, c, d, 31);
> -  R2(d, e, a, b, c, 32);
> -  R2(c, d, e, a, b, 33);
> -  R2(b, c, d, e, a, 34);
> -  R2(a, b, c, d, e, 35);
> -  R2(e, a, b, c, d, 36);
> -  R2(d, e, a, b, c, 37);
> -  R2(c, d, e, a, b, 38);
> -  R2(b, c, d, e, a, 39);
> -  R3(a, b, c, d, e, 40);
> -  R3(e, a, b, c, d, 41);
> -  R3(d, e, a, b, c, 42);
> -  R3(c, d, e, a, b, 43);
> -  R3(b, c, d, e, a, 44);
> -  R3(a, b, c, d, e, 45);
> -  R3(e, a, b, c, d, 46);
> -  R3(d, e, a, b, c, 47);
> -  R3(c, d, e, a, b, 48);
> -  R3(b, c, d, e, a, 49);
> -  R3(a, b, c, d, e, 50);
> -  R3(e, a, b, c, d, 51);
> -  R3(d, e, a, b, c, 52);
> -  R3(c, d, e, a, b, 53);
> -  R3(b, c, d, e, a, 54);
> -  R3(a, b, c, d, e, 55);
> -  R3(e, a, b, c, d, 56);
> -  R3(d, e, a, b, c, 57);
> -  R3(c, d, e, a, b, 58);
> -  R3(b, c, d, e, a, 59);
> -  R4(a, b, c, d, e, 60);
> -  R4(e, a, b, c, d, 61);
> -  R4(d, e, a, b, c, 62);
> -  R4(c, d, e, a, b, 63);
> -  R4(b, c, d, e, a, 64);
> -  R4(a, b, c, d, e, 65);
> -  R4(e, a, b, c, d, 66);
> -  R4(d, e, a, b, c, 67);
> -  R4(c, d, e, a, b, 68);
> -  R4(b, c, d, e, a, 69);
> -  R4(a, b, c, d, e, 70);
> -  R4(e, a, b, c, d, 71);
> -  R4(d, e, a, b, c, 72);
> -  R4(c, d, e, a, b, 73);
> -  R4(b, c, d, e, a, 74);
> -  R4(a, b, c, d, e, 75);
> -  R4(e, a, b, c, d, 76);
> -  R4(d, e, a, b, c, 77);
> -  R4(c, d, e, a, b, 78);
> -  R4(b, c, d, e, a, 79);
> -  state[0] += a;
> -  state[1] += b;
> -  state[2] += c;
> -  state[3] += d;
> -  state[4] += e;
> -  /* Erase working structures. The order of operations is important,
> -   * used to ensure that compiler doesn't optimize those out. */
> -  memset(block, 0, sizeof(block));
> -  a = b = c = d = e = 0;
> -  (void) a;
> -  (void) b;
> -  (void) c;
> -  (void) d;
> -  (void) e;
> +static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> +  long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> +  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> +  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> +  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> +  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> +  return (int) n;
> +}
> +
> +void mg_tls_handshake(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  int rc = mbedtls_ssl_handshake(&tls->ssl);
> +  if (rc == 0) {  // Success
> +    MG_DEBUG(("%lu success", c->id));
> +    c->is_tls_hs = 0;
> +    mg_call(c, MG_EV_TLS_HS, NULL);
> +  } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> +             rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
> +    MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
> +                c->is_tls_hs, rc, -rc));
> +  } else {
> +    mg_error(c, "TLS handshake: -%#x", -rc);  // Error
> +  }
>   }
>
> -void mg_sha1_init(mg_sha1_ctx *context) {
> -  context->state[0] = 0x67452301;
> -  context->state[1] = 0xEFCDAB89;
> -  context->state[2] = 0x98BADCFE;
> -  context->state[3] = 0x10325476;
> -  context->state[4] = 0xC3D2E1F0;
> -  context->count[0] = context->count[1] = 0;
> +static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
> +  n = (int) strlen(s2) - 1;
> +  MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
> +  (void) s;
>   }
>
> -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
> -                    size_t len) {
> -  size_t i, j;
> +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> +  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> +  int rc = 0;
> +  c->tls = tls;
> +  if (c->tls == NULL) {
> +    mg_error(c, "TLS OOM");
> +    goto fail;
> +  }
> +  if (c->is_listening) goto fail;
> +  MG_DEBUG(("%lu Setting TLS", c->id));
> +  MG_PROF_ADD(c, "mbedtls_init_start");
> +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 && \
> +    defined(MBEDTLS_PSA_CRYPTO_C)
> +  psa_crypto_init();  // https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711
> +#endif
> +  mbedtls_ssl_init(&tls->ssl);
> +  mbedtls_ssl_config_init(&tls->conf);
> +  mbedtls_x509_crt_init(&tls->ca);
> +  mbedtls_x509_crt_init(&tls->cert);
> +  mbedtls_pk_init(&tls->pk);
> +  mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> +#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> +  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> +#endif
> +  if ((rc = mbedtls_ssl_config_defaults(
> +           &tls->conf,
> +           c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> +           MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> +    mg_error(c, "tls defaults %#x", -rc);
> +    goto fail;
> +  }
> +  mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
>
> -  j = context->count[0];
> -  if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
> -  context->count[1] += (uint32_t) (len >> 29);
> -  j = (j >> 3) & 63;
> -  if ((j + len) > 63) {
> -    memcpy(&context->buffer[j], data, (i = 64 - j));
> -    mg_sha1_transform(context->state, context->buffer);
> -    for (; i + 63 < len; i += 64) {
> -      mg_sha1_transform(context->state, &data[i]);
> +  if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> +    // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client side
> +    // See https://github.com/Mbed-TLS/mbedtls/issues/7075
> +    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> +  } else {
> +    if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
> +    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> +    if (c->is_client && opts->name.buf != NULL && opts->name.buf[0] != '\0') {
> +      char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
> +      mbedtls_ssl_set_hostname(&tls->ssl, host);
> +      MG_DEBUG(("%lu hostname verification: %s", c->id, host));
> +      free(host);
>       }
> -    j = 0;
> -  } else
> -    i = 0;
> -  memcpy(&context->buffer[j], &data[i], len - i);
> +    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
> +  }
> +  if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
> +  if (!mg_load_key(opts->key, &tls->pk)) goto fail;
> +  if (tls->cert.version &&
> +      (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk)) != 0) {
> +    mg_error(c, "own cert %#x", -rc);
> +    goto fail;
> +  }
> +
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +  mbedtls_ssl_conf_session_tickets_cb(
> +      &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
> +      &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
> +#endif
> +
> +  if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> +    mg_error(c, "setup err %#x", -rc);
> +    goto fail;
> +  }
> +  c->is_tls = 1;
> +  c->is_tls_hs = 1;
> +  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> +  MG_PROF_ADD(c, "mbedtls_init_end");
> +  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> +    mg_tls_handshake(c);
> +  }
> +  return;
> +fail:
> +  mg_tls_free(c);
>   }
>
> -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
> -  unsigned i;
> -  unsigned char finalcount[8], c;
> +size_t mg_tls_pending(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> +}
>
> -  for (i = 0; i < 8; i++) {
> -    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
> -                                      ((3 - (i & 3)) * 8)) &
> -                                     255);
> +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> +  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> +    return MG_IO_WAIT;
> +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
> +  if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
> +    return MG_IO_WAIT;
>     }
> -  c = 0200;
> -  mg_sha1_update(context, &c, 1);
> -  while ((context->count[0] & 504) != 448) {
> -    c = 0000;
> -    mg_sha1_update(context, &c, 1);
> +#endif
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
> +}
> +
> +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> +  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> +    return MG_IO_WAIT;
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
> +}
> +
> +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> +  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1, sizeof(*ctx));
> +  if (ctx == NULL) {
> +    MG_ERROR(("TLS context init OOM"));
> +  } else {
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +    int rc;
> +    mbedtls_ssl_ticket_init(&ctx->tickets);
> +    if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng, NULL,
> +                                       MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
> +        0) {
> +      MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
> +    }
> +#endif
> +    mgr->tls_ctx = ctx;
>     }
> -  mg_sha1_update(context, finalcount, 8);
> -  for (i = 0; i < 20; i++) {
> -    digest[i] =
> -        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
> +}
> +
> +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> +  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
> +  if (ctx != NULL) {
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +    mbedtls_ssl_ticket_free(&ctx->tickets);
> +#endif
> +    free(ctx);
> +    mgr->tls_ctx = NULL;
>     }
> -  memset(context, '\0', sizeof(*context));
> -  memset(&finalcount, '\0', sizeof(finalcount));
>   }
> +#endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/sntp.c"
> +#line 1 "src/tls_openssl.c"
>   #endif
>
>
>
> +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>
> +static int tls_err_cb(const char *s, size_t len, void *c) {
> +  int n = (int) len - 1;
> +  MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
> +  return 0;  // undocumented
> +}
>
> +static int mg_tls_err(struct mg_connection *c, struct mg_tls *tls, int res) {
> +  int err = SSL_get_error(tls->ssl, res);
> +  // We've just fetched the last error from the queue.
> +  // Now we need to clear the error queue. If we do not, then the following
> +  // can happen (actually reported):
> +  //  - A new connection is accept()-ed with cert error (e.g. self-signed cert)
> +  //  - Since all accept()-ed connections share listener's context,
> +  //  - *ALL* SSL accepted connection report read error on the next poll cycle.
> +  //    Thus a single errored connection can close all the rest, unrelated ones.
> +  // Clearing the error keeps the shared SSL_CTX in an OK state.
>
> -#define SNTP_TIME_OFFSET 2208988800U  // (1970 - 1900) in seconds
> -#define SNTP_MAX_FRAC 4294967295.0    // 2 ** 32 - 1
> +  if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
> +  ERR_clear_error();
> +  if (err == SSL_ERROR_WANT_READ) return 0;
> +  if (err == SSL_ERROR_WANT_WRITE) return 0;
> +  return err;
> +}
>
> -static int64_t gettimestamp(const uint32_t *data) {
> -  uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> -  if (sec) sec -= SNTP_TIME_OFFSET;
> -  return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0);
> +static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
> +  BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
> +  STACK_OF(X509_INFO) *certs =
> +      bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
> +  if (bio) BIO_free(bio);
> +  return certs;
>   }
>
> -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> -  int64_t res = -1;
> -  int mode = len > 0 ? buf[0] & 7 : 0;
> -  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> -  if (len < 48) {
> -    MG_ERROR(("%s", "corrupt packet"));
> -  } else if (mode != 4 && mode != 5) {
> -    MG_ERROR(("%s", "not a server reply"));
> -  } else if (buf[1] == 0) {
> -    MG_ERROR(("%s", "server sent a kiss of death"));
> -  } else if (version == 4 || version == 3) {
> -    // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> -    int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> -    int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> -    int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> -    int64_t t3 = (int64_t) mg_millis();
> -    int64_t delta = (t3 - t0) - (t2 - t1);
> -    MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
> -    res = t2 + delta / 2;
> -  } else {
> -    MG_ERROR(("unexpected version: %d", version));
> +static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) * certs) {
> +  X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
> +  for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
> +    X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
> +    if (cert_info->x509 && !X509_STORE_add_cert(cert_store, cert_info->x509))
> +      return false;
> +  }
> +  return true;
> +}
> +
> +static EVP_PKEY *load_key(struct mg_str s) {
> +  BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> +  EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0, NULL) : NULL;
> +  if (bio) BIO_free(bio);
> +  return key;
> +}
> +
> +static X509 *load_cert(struct mg_str s) {
> +  BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> +  X509 *cert = bio == NULL ? NULL
> +               : s.buf[0] == '-'
> +                   ? PEM_read_bio_X509(bio, NULL, NULL, NULL)  // PEM
> +                   : d2i_X509_bio(bio, NULL);                  // DER
> +  if (bio) BIO_free(bio);
> +  return cert;
> +}
> +
> +static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
> +  long ret = 0;
> +  if (cmd == BIO_CTRL_PUSH) ret = 1;
> +  if (cmd == BIO_CTRL_POP) ret = 1;
> +  if (cmd == BIO_CTRL_FLUSH) ret = 1;
> +#if MG_TLS == MG_TLS_OPENSSL
> +  if (cmd == BIO_C_SET_NBIO) ret = 1;
> +#endif
> +  // MG_DEBUG(("%d -> %ld", cmd, ret));
> +  (void) b, (void) cmd, (void) larg, (void) pargs;
> +  return ret;
> +}
> +
> +static int mg_bio_read(BIO *bio, char *buf, int len) {
> +  struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
> +  long res = mg_io_recv(c, buf, (size_t) len);
> +  // MG_DEBUG(("%p %d %ld", buf, len, res));
> +  len = res > 0 ? (int) res : -1;
> +  if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
> +  return len;
> +}
> +
> +static int mg_bio_write(BIO *bio, const char *buf, int len) {
> +  struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
> +  long res = mg_io_send(c, buf, (size_t) len);
> +  // MG_DEBUG(("%p %d %ld", buf, len, res));
> +  len = res > 0 ? (int) res : -1;
> +  if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
> +  return len;
> +}
> +
> +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> +  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> +  const char *id = "mongoose";
> +  static unsigned char s_initialised = 0;
> +  BIO *bio = NULL;
> +  int rc;
> +
> +  if (tls == NULL) {
> +    mg_error(c, "TLS OOM");
> +    goto fail;
> +  }
> +
> +  if (!s_initialised) {
> +    SSL_library_init();
> +    s_initialised++;
> +  }
> +  MG_DEBUG(("%lu Setting TLS", c->id));
> +  tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> +                          : SSL_CTX_new(SSLv23_server_method());
> +  if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> +    mg_error(c, "SSL_new");
> +    goto fail;
> +  }
> +  SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> +                             (unsigned) strlen(id));
> +  // Disable deprecated protocols
> +  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> +  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> +  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> +  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> +  SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> +#endif
> +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> +  SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> +#endif
> +
> +#if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS)
> +  if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> +    // Older versions require that either the CA is loaded or SSL_VERIFY_NONE
> +    // explicitly set
> +    SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
> +  }
> +#endif
> +  if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
> +    SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> +                   NULL);
> +    STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
> +    rc = add_ca_certs(tls->ctx, certs);
> +    sk_X509_INFO_pop_free(certs, X509_INFO_free);
> +    if (!rc) {
> +      mg_error(c, "CA err");
> +      goto fail;
> +    }
> +  }
> +  if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
> +    X509 *cert = load_cert(opts->cert);
> +    rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
> +    X509_free(cert);
> +    if (cert == NULL || rc != 1) {
> +      mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
> +      goto fail;
> +    }
> +  }
> +  if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
> +    EVP_PKEY *key = load_key(opts->key);
> +    rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
> +    EVP_PKEY_free(key);
> +    if (key == NULL || rc != 1) {
> +      mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
> +      goto fail;
> +    }
> +  }
> +
> +  SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> +#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER > 0x10002000L
> +  (void) SSL_set_ecdh_auto(tls->ssl, 1);
> +#endif
> +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> +  if (opts->name.len > 0) {
> +    char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf);
> +#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002
> +    SSL_set1_host(tls->ssl, s);
> +#else
> +    X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
> +#endif
> +    SSL_set_tlsext_host_name(tls->ssl, s);
> +    free(s);
>     }
> -  return res;
> -}
> +#endif
> +#if MG_TLS == MG_TLS_WOLFSSL
> +  tls->bm = BIO_meth_new(0, "bio_mg");
> +#else
> +  tls->bm = BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "bio_mg");
> +#endif
> +  BIO_meth_set_write(tls->bm, mg_bio_write);
> +  BIO_meth_set_read(tls->bm, mg_bio_read);
> +  BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
>
> -static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
> -  if (ev == MG_EV_READ) {
> -    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> -    if (milliseconds > 0) {
> -      MG_INFO(("%lu got time: %lld ms from epoch", c->id, milliseconds));
> -      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> -      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> -                  (unsigned) (milliseconds % 1000)));
> -    }
> -    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
> -  } else if (ev == MG_EV_CONNECT) {
> -    mg_sntp_request(c);
> -  } else if (ev == MG_EV_CLOSE) {
> +  bio = BIO_new(tls->bm);
> +  BIO_set_data(bio, c);
> +  SSL_set_bio(tls->ssl, bio, bio);
> +
> +  c->tls = tls;
> +  c->is_tls = 1;
> +  c->is_tls_hs = 1;
> +  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> +    mg_tls_handshake(c);
>     }
> -  (void) fnd;
> -  (void) evd;
> +  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
> +  return;
> +fail:
> +  free(tls);
>   }
>
> -void mg_sntp_request(struct mg_connection *c) {
> -  if (c->is_resolving) {
> -    MG_ERROR(("%lu wait until resolved", c->id));
> +void mg_tls_handshake(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
> +  if (rc == 1) {
> +    MG_DEBUG(("%lu success", c->id));
> +    c->is_tls_hs = 0;
> +    mg_call(c, MG_EV_TLS_HS, NULL);
>     } else {
> -    int64_t now = (int64_t) mg_millis();  // Use int64_t, for vc98
> -    uint8_t buf[48] = {0};
> -    uint32_t *t = (uint32_t *) &buf[40];
> -    double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> -    buf[0] = (0 << 6) | (4 << 3) | 3;
> -    t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> -    t[1] = mg_htonl((uint32_t) frac);
> -    mg_send(c, buf, sizeof(buf));
> +    int code = mg_tls_err(c, tls, rc);
> +    if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
>     }
>   }
>
> -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
> -                                      mg_event_handler_t fn, void *fnd) {
> -  struct mg_connection *c = NULL;
> -  if (url == NULL) url = "udp://time.google.com:123";
> -  if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> -  return c;
> +void mg_tls_free(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  if (tls == NULL) return;
> +  SSL_free(tls->ssl);
> +  SSL_CTX_free(tls->ctx);
> +  BIO_meth_free(tls->bm);
> +  free(tls);
> +  c->tls = NULL;
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/sock.c"
> -#endif
> -
> +size_t mg_tls_pending(struct mg_connection *c) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> +}
>
> +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  int n = SSL_read(tls->ssl, buf, (int) len);
> +  if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
> +}
>
> +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> +  struct mg_tls *tls = (struct mg_tls *) c->tls;
> +  int n = SSL_write(tls->ssl, buf, (int) len);
> +  if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> +  if (n <= 0) return MG_IO_ERR;
> +  return n;
> +}
>
> +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> +  (void) mgr;
> +}
>
> +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> +  (void) mgr;
> +}
> +#endif
>
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/tls_uecc.c"
> +#endif
> +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */
>
>
>
>
> -#if MG_ENABLE_SOCKET
> +#if MG_TLS == MG_TLS_BUILTIN
>
> -#ifndef closesocket
> -#define closesocket(x) close(x)
> +#ifndef MG_UECC_RNG_MAX_TRIES
> +#define MG_UECC_RNG_MAX_TRIES 64
>   #endif
>
> -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> -#define S2PTR(s_) ((void *) (size_t) (s_))
> -
> -#ifndef MSG_NONBLOCKING
> -#define MSG_NONBLOCKING 0
> +#if MG_UECC_ENABLE_VLI_API
> +#define MG_UECC_VLI_API
> +#else
> +#define MG_UECC_VLI_API static
>   #endif
>
> -#ifndef AF_INET6
> -#define AF_INET6 10
> +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM == mg_uecc_arm) || \
> +    (MG_UECC_PLATFORM == mg_uecc_arm_thumb) ||                                \
> +    (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
> +#define CONCATX(a, ...) a##__VA_ARGS__
> +#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
> +
> +#define STRX(a) #a
> +#define STR(a) STRX(a)
> +
> +#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
> +#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
> +#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
> +#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
> +#define EVAL4(...) __VA_ARGS__
> +
> +#define DEC_1 0
> +#define DEC_2 1
> +#define DEC_3 2
> +#define DEC_4 3
> +#define DEC_5 4
> +#define DEC_6 5
> +#define DEC_7 6
> +#define DEC_8 7
> +#define DEC_9 8
> +#define DEC_10 9
> +#define DEC_11 10
> +#define DEC_12 11
> +#define DEC_13 12
> +#define DEC_14 13
> +#define DEC_15 14
> +#define DEC_16 15
> +#define DEC_17 16
> +#define DEC_18 17
> +#define DEC_19 18
> +#define DEC_20 19
> +#define DEC_21 20
> +#define DEC_22 21
> +#define DEC_23 22
> +#define DEC_24 23
> +#define DEC_25 24
> +#define DEC_26 25
> +#define DEC_27 26
> +#define DEC_28 27
> +#define DEC_29 28
> +#define DEC_30 29
> +#define DEC_31 30
> +#define DEC_32 31
> +
> +#define DEC(N) CONCAT(DEC_, N)
> +
> +#define SECOND_ARG(_, val, ...) val
> +#define SOME_CHECK_0 ~, 0
> +#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
> +#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
> +
> +#define EMPTY(...)
> +#define DEFER(...) __VA_ARGS__ EMPTY()
> +
> +#define REPEAT_NAME_0() REPEAT_0
> +#define REPEAT_NAME_SOME() REPEAT_SOME
> +#define REPEAT_0(...)
> +#define REPEAT_SOME(N, stuff) \
> +  DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff) stuff
> +#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
> +
> +#define REPEATM_NAME_0() REPEATM_0
> +#define REPEATM_NAME_SOME() REPEATM_SOME
> +#define REPEATM_0(...)
> +#define REPEATM_SOME(N, macro) \
> +  macro(N) DEFER(CONCAT(REPEATM_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), macro)
> +#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
>   #endif
>
> -union usa {
> -  struct sockaddr sa;
> -  struct sockaddr_in sin;
> -#if MG_ENABLE_IPV6
> -  struct sockaddr_in6 sin6;
> -#endif
> -};
> +//
>
> -static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> -  socklen_t len = sizeof(usa->sin);
> -  memset(usa, 0, sizeof(*usa));
> -  usa->sin.sin_family = AF_INET;
> -  usa->sin.sin_port = a->port;
> -  *(uint32_t *) &usa->sin.sin_addr = a->ip;
> -#if MG_ENABLE_IPV6
> -  if (a->is_ip6) {
> -    usa->sin.sin_family = AF_INET6;
> -    usa->sin6.sin6_port = a->port;
> -    memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
> -    len = sizeof(usa->sin6);
> -  }
> +#if (MG_UECC_WORD_SIZE == 1)
> +#if MG_UECC_SUPPORTS_secp160r1
> +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
>   #endif
> -  return len;
> -}
> -
> -static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
> -  a->is_ip6 = is_ip6;
> -  a->port = usa->sin.sin_port;
> -  memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
> -#if MG_ENABLE_IPV6
> -  if (is_ip6) {
> -    memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
> -    a->port = usa->sin6.sin6_port;
> -  }
> +#if MG_UECC_SUPPORTS_secp192r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 24
>   #endif
> -}
> -
> -static bool mg_sock_would_block(void) {
> -  int err = MG_SOCKET_ERRNO;
> -  return err == EINPROGRESS || err == EWOULDBLOCK
> -#ifndef WINCE
> -         || err == EAGAIN || err == EINTR
> +#if MG_UECC_SUPPORTS_secp224r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 28
>   #endif
> -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> -         || err == WSAEINTR || err == WSAEWOULDBLOCK
> +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 32
>   #endif
> -      ;
> -}
> -
> -static bool mg_sock_conn_reset(void) {
> -  int err = MG_SOCKET_ERRNO;
> -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> -  return err == WSAECONNRESET;
> -#else
> -  return err == EPIPE || err == ECONNRESET;
> +#elif (MG_UECC_WORD_SIZE == 4)
> +#if MG_UECC_SUPPORTS_secp160r1
> +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
>   #endif
> -}
> +#if MG_UECC_SUPPORTS_secp192r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 6
> +#endif
> +#if MG_UECC_SUPPORTS_secp224r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 7
> +#endif
> +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 8
> +#endif
> +#elif (MG_UECC_WORD_SIZE == 8)
> +#if MG_UECC_SUPPORTS_secp160r1
> +#define MG_UECC_MAX_WORDS 3
> +#endif
> +#if MG_UECC_SUPPORTS_secp192r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 3
> +#endif
> +#if MG_UECC_SUPPORTS_secp224r1
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 4
> +#endif
> +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> +#undef MG_UECC_MAX_WORDS
> +#define MG_UECC_MAX_WORDS 4
> +#endif
> +#endif /* MG_UECC_WORD_SIZE */
> +
> +#define BITS_TO_WORDS(num_bits)                                \
> +  ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
> +                  (MG_UECC_WORD_SIZE * 8)))
> +#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
> +
> +struct MG_UECC_Curve_t {
> +  wordcount_t num_words;
> +  wordcount_t num_bytes;
> +  bitcount_t num_n_bits;
> +  mg_uecc_word_t p[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t n[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
> +  mg_uecc_word_t b[MG_UECC_MAX_WORDS];
> +  void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                          mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +  void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
> +#endif
> +  void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> +                 MG_UECC_Curve curve);
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +  void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t *product);
> +#endif
> +};
>
> -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> -  union usa usa;
> -  socklen_t n = sizeof(usa);
> -  if (getsockname(fd, &usa.sa, &n) == 0) {
> -    tomgaddr(&usa, addr, n != sizeof(usa.sin));
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +static void bcopy(uint8_t *dst, const uint8_t *src, unsigned num_bytes) {
> +  while (0 != num_bytes) {
> +    num_bytes--;
> +    dst[num_bytes] = src[num_bytes];
>     }
>   }
> +#endif
>
> -static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
> -  if (n == MG_IO_WAIT) {
> -    // Do nothing
> -  } else if (n <= 0) {
> -    c->is_closing = 1;  // Termination. Don't call mg_error(): #1529
> -  } else if (n > 0) {
> -    if (c->is_hexdumping) {
> -      union usa usa;
> -      socklen_t slen = sizeof(usa.sin);
> -      if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0;  // Ignore result
> -      MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
> -               r ? "<-" : "->", 4, &c->rem.ip, n));
> +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
> +                                          const mg_uecc_word_t *right,
> +                                          wordcount_t num_words);
>
> -      mg_hexdump(buf, (size_t) n);
> -    }
> -    if (r) {
> -      c->recv.len += (size_t) n;
> -      mg_call(c, MG_EV_READ, &n);
> -    } else {
> -      mg_iobuf_del(&c->send, 0, (size_t) n);
> -      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> -      if (c->send.len == 0) {
> -        MG_EPOLL_MOD(c, 0);
> -      }
> -      mg_call(c, MG_EV_WRITE, &n);
> -    }
> -  }
> -}
> +#if (MG_UECC_PLATFORM == mg_uecc_arm ||       \
> +     MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
> +     MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
>
> -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> -  long n;
> -  if (c->is_udp) {
> -    union usa usa;
> -    socklen_t slen = tousa(&c->rem, &usa);
> -    n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
> -    if (n > 0) setlocaddr(FD(c), &c->loc);
> -  } else {
> -    n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> -#if MG_ARCH == MG_ARCH_RTX
> -    if (n == EWOULDBLOCK) return MG_IO_WAIT;
>   #endif
> -  }
> -  if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> -  if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
> -}
>
> -bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> -  if (c->is_udp) {
> -    long n = mg_io_send(c, buf, len);
> -    MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
> -              (int) c->recv.len, n, MG_SOCKET_ERRNO));
> -    iolog(c, (char *) buf, n, false);
> -    return n > 0;
> -  } else {
> -    return mg_iobuf_add(&c->send, c->send.len, buf, len);
> -  }
> -}
> +#if (MG_UECC_PLATFORM == mg_uecc_avr)
>
> -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> -#if defined(MG_CUSTOM_NONBLOCK)
> -  MG_CUSTOM_NONBLOCK(fd);
> -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> -  unsigned long on = 1;
> -  ioctlsocket(fd, FIONBIO, &on);
> -#elif MG_ARCH == MG_ARCH_RTX
> -  unsigned long on = 1;
> -  ioctlsocket(fd, FIONBIO, &on);
> -#elif MG_ENABLE_FREERTOS_TCP
> -  const BaseType_t off = 0;
> -  if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0;
> -  if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0;
> -#elif MG_ENABLE_LWIP
> -  lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> -#elif MG_ARCH == MG_ARCH_AZURERTOS
> -  fcntl(fd, F_SETFL, O_NONBLOCK);
> -#elif MG_ARCH == MG_ARCH_TIRTOS
> -  int val = 0;
> -  setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> -  // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> -  int sz = sizeof(val);
> -  getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> -  val /= 2;  // set send low-water mark at half send buffer size
> -  setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> -#else
> -  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);  // Non-blocking mode
> -  fcntl(fd, F_SETFD, FD_CLOEXEC);                          // Set close-on-exec
>   #endif
> -}
>
> -bool mg_open_listener(struct mg_connection *c, const char *url) {
> -  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> -  bool success = false;
> -  c->loc.port = mg_htons(mg_url_port(url));
> -  if (!mg_aton(mg_url_host(url), &c->loc)) {
> -    MG_ERROR(("invalid listening URL: %s", url));
> -  } else {
> -    union usa usa;
> -    socklen_t slen = tousa(&c->loc, &usa);
> -    int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> -    int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
> -    int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> -    (void) on;
> +#ifndef asm_clear
> +#define asm_clear 0
> +#endif
> +#ifndef asm_set
> +#define asm_set 0
> +#endif
> +#ifndef asm_add
> +#define asm_add 0
> +#endif
> +#ifndef asm_sub
> +#define asm_sub 0
> +#endif
> +#ifndef asm_mult
> +#define asm_mult 0
> +#endif
> +#ifndef asm_rshift1
> +#define asm_rshift1 0
> +#endif
> +#ifndef asm_mmod_fast_secp256r1
> +#define asm_mmod_fast_secp256r1 0
> +#endif
>
> -    if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> -      MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
> -#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
> -     (defined(LWIP_SOCKET) && SO_REUSE == 1))
> -    } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
> -                          sizeof(on)) != 0) {
> -      // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of
> -      //    SO_REUSEADDR on UNIX and Windows is different. On Windows,
> -      //    SO_REUSEADDR allows to bind a socket to a port without error even
> -      //    if the port is already open by another program. This is not the
> -      //    behavior SO_REUSEADDR was designed for, and leads to hard-to-track
> -      //    failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows
> -      //    unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
> -      // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by
> -      // defining
> -      //    SO_REUSE (in lwipopts.h), otherwise the code below will compile
> -      //    but won't work! (setsockopt will return EINVAL)
> -      MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
> -#endif
> -#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)
> -    } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on,
> -                          sizeof(on)) != 0) {
> -      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> -      MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
> +#if defined(default_RNG_defined) && default_RNG_defined
> +static MG_UECC_RNG_Function g_rng_function = &default_RNG;
> +#else
> +static MG_UECC_RNG_Function g_rng_function = 0;
>   #endif
> -    } else if (bind(fd, &usa.sa, slen) != 0) {
> -      MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> -    } else if ((type == SOCK_STREAM &&
> -                listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
> -      // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> -      // In case port was set to 0, get the real port number
> -      MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
> -    } else {
> -      setlocaddr(fd, &c->loc);
> -      mg_set_non_blocking_mode(fd);
> -      c->fd = S2PTR(fd);
> -      MG_EPOLL_ADD(c);
> -      success = true;
> -    }
> -  }
> -  if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> -  return success;
> +
> +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
> +  g_rng_function = rng_function;
> +}
> +
> +MG_UECC_RNG_Function mg_uecc_get_rng(void) {
> +  return g_rng_function;
>   }
>
> -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> -  long n = 0;
> -  if (c->is_udp) {
> -    union usa usa;
> -    socklen_t slen = tousa(&c->rem, &usa);
> -    n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
> -    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
> -  } else {
> -    n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
> +  return BITS_TO_BYTES(curve->num_n_bits);
> +}
> +
> +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
> +  return 2 * curve->num_bytes;
> +}
> +
> +#if !asm_clear
> +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
> +                                       wordcount_t num_words) {
> +  wordcount_t i;
> +  for (i = 0; i < num_words; ++i) {
> +    vli[i] = 0;
>     }
> -  if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> -  if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
>   }
> +#endif /* !asm_clear */
>
> -// NOTE(lsm): do only one iteration of reads, cause some systems
> -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
> -static void read_conn(struct mg_connection *c) {
> -  long n = -1;
> -  if (c->recv.len >= MG_MAX_RECV_SIZE) {
> -    mg_error(c, "max_recv_buf_size reached");
> -  } else if (c->recv.size <= c->recv.len &&
> -             !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
> -    mg_error(c, "oom");
> -  } else {
> -    char *buf = (char *) &c->recv.buf[c->recv.len];
> -    size_t len = c->recv.size - c->recv.len;
> -    n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf, len);
> -    MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> -              (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> -              (long) c->recv.size, n, MG_SOCKET_ERRNO));
> -    iolog(c, buf, n, true);
> +/* Constant-time comparison to zero - secure way to compare long integers */
> +/* Returns 1 if vli == 0, 0 otherwise. */
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
> +                                                  wordcount_t num_words) {
> +  mg_uecc_word_t bits = 0;
> +  wordcount_t i;
> +  for (i = 0; i < num_words; ++i) {
> +    bits |= vli[i];
>     }
> +  return (bits == 0);
>   }
>
> -static void write_conn(struct mg_connection *c) {
> -  char *buf = (char *) c->send.buf;
> -  size_t len = c->send.len;
> -  long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len);
> -  MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> -            (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> -            (long) c->recv.size, n, MG_SOCKET_ERRNO));
> -  iolog(c, buf, n, false);
> +/* Returns nonzero if bit 'bit' of vli is set. */
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli,
> +                                                   bitcount_t bit) {
> +  return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
> +          ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
>   }
>
> -static void close_conn(struct mg_connection *c) {
> -  if (FD(c) != MG_INVALID_SOCKET) {
> -#if MG_ENABLE_EPOLL
> -    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> -#endif
> -    closesocket(FD(c));
> -#if MG_ENABLE_FREERTOS_TCP
> -    FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> -#endif
> +/* Counts the number of words in vli. */
> +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
> +                                 const wordcount_t max_words) {
> +  wordcount_t i;
> +  /* Search from the end until we find a non-zero digit.
> +     We do it in reverse because we expect that most digits will be nonzero. */
> +  for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
>     }
> -  mg_close_conn(c);
> +
> +  return (i + 1);
>   }
>
> -static void connect_conn(struct mg_connection *c) {
> -  union usa usa;
> -  socklen_t n = sizeof(usa);
> -  // Use getpeername() to test whether we have connected
> -  if (getpeername(FD(c), &usa.sa, &n) == 0) {
> -    c->is_connecting = 0;
> -    mg_call(c, MG_EV_CONNECT, NULL);
> -    MG_EPOLL_MOD(c, 0);
> -    if (c->is_tls_hs) mg_tls_handshake(c);
> -  } else {
> -    mg_error(c, "socket error");
> +/* Counts the number of bits required to represent vli. */
> +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
> +                                               const wordcount_t max_words) {
> +  mg_uecc_word_t i;
> +  mg_uecc_word_t digit;
> +
> +  wordcount_t num_digits = vli_numDigits(vli, max_words);
> +  if (num_digits == 0) {
> +    return 0;
>     }
> -}
>
> -static void setsockopts(struct mg_connection *c) {
> -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> -    MG_ARCH == MG_ARCH_TIRTOS
> -  (void) c;
> -#else
> -  int on = 1;
> -#if !defined(SOL_TCP)
> -#define SOL_TCP IPPROTO_TCP
> -#endif
> -  if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0)
> -    (void) 0;
> -  if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) !=
> -      0)
> -    (void) 0;
> -#endif
> +  digit = vli[num_digits - 1];
> +  for (i = 0; digit; ++i) {
> +    digit >>= 1;
> +  }
> +
> +  return (((bitcount_t) ((num_digits - 1) << MG_UECC_WORD_BITS_SHIFT)) +
> +          (bitcount_t) i);
>   }
>
> -void mg_connect_resolved(struct mg_connection *c) {
> -  int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> -  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;  // c->rem has resolved IP
> -  c->fd = S2PTR(socket(af, type, 0));               // Create outbound socket
> -  c->is_resolving = 0;                              // Clear resolving flag
> -  if (FD(c) == MG_INVALID_SOCKET) {
> -    mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
> -  } else if (c->is_udp) {
> -    MG_EPOLL_ADD(c);
> -#if MG_ARCH == MG_ARCH_TIRTOS
> -    union usa usa;  // TI-RTOS NDK requires binding to receive on UDP sockets
> -    socklen_t slen = tousa(&c->loc, &usa);
> -    if (bind(c->fd, &usa.sa, slen) != 0)
> -      MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> -#endif
> -    mg_call(c, MG_EV_RESOLVE, NULL);
> -    mg_call(c, MG_EV_CONNECT, NULL);
> -  } else {
> -    union usa usa;
> -    socklen_t slen = tousa(&c->rem, &usa);
> -    mg_set_non_blocking_mode(FD(c));
> -    setsockopts(c);
> -    MG_EPOLL_ADD(c);
> -    mg_call(c, MG_EV_RESOLVE, NULL);
> -    if ((rc = connect(FD(c), &usa.sa, slen)) == 0) {
> -      mg_call(c, MG_EV_CONNECT, NULL);
> -    } else if (mg_sock_would_block()) {
> -      MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
> -                mg_ntohs(c->rem.port)));
> -      c->is_connecting = 1;
> -    } else {
> -      mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
> +/* Sets dest = src. */
> +#if !asm_set
> +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
> +                                     const mg_uecc_word_t *src,
> +                                     wordcount_t num_words) {
> +  wordcount_t i;
> +  for (i = 0; i < num_words; ++i) {
> +    dest[i] = src[i];
> +  }
> +}
> +#endif /* !asm_set */
> +
> +/* Returns sign of left - right. */
> +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
> +                                          const mg_uecc_word_t *right,
> +                                          wordcount_t num_words) {
> +  wordcount_t i;
> +  for (i = num_words - 1; i >= 0; --i) {
> +    if (left[i] > right[i]) {
> +      return 1;
> +    } else if (left[i] < right[i]) {
> +      return -1;
>       }
>     }
> -  (void) rc;
> +  return 0;
>   }
>
> -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> -                              socklen_t *len) {
> -  MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
> -  do {
> -    memset(usa, 0, sizeof(*usa));
> -    s = accept(sock, &usa->sa, len);
> -  } while (s == MG_INVALID_SOCKET && errno == EINTR);
> -  return s;
> +/* Constant-time comparison function - secure way to compare long integers */
> +/* Returns one if left == right, zero otherwise. */
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
> +                                                 const mg_uecc_word_t *right,
> +                                                 wordcount_t num_words) {
> +  mg_uecc_word_t diff = 0;
> +  wordcount_t i;
> +  for (i = num_words - 1; i >= 0; --i) {
> +    diff |= (left[i] ^ right[i]);
> +  }
> +  return (diff == 0);
> +}
> +
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> +                                               const mg_uecc_word_t *left,
> +                                               const mg_uecc_word_t *right,
> +                                               wordcount_t num_words);
> +
> +/* Returns sign of left - right, in constant time. */
> +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
> +                                            const mg_uecc_word_t *right,
> +                                            wordcount_t num_words) {
> +  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right, num_words);
> +  mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
> +  return (cmpresult_t) (!equal - 2 * neg);
> +}
> +
> +/* Computes vli = vli >> 1. */
> +#if !asm_rshift1
> +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
> +                                         wordcount_t num_words) {
> +  mg_uecc_word_t *end = vli;
> +  mg_uecc_word_t carry = 0;
> +
> +  vli += num_words;
> +  while (vli-- > end) {
> +    mg_uecc_word_t temp = *vli;
> +    *vli = (temp >> 1) | carry;
> +    carry = temp << (MG_UECC_WORD_BITS - 1);
> +  }
>   }
> +#endif /* !asm_rshift1 */
> +
> +/* Computes result = left + right, returning carry. Can modify in place. */
> +#if !asm_add
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
> +                                               const mg_uecc_word_t *left,
> +                                               const mg_uecc_word_t *right,
> +                                               wordcount_t num_words) {
> +  mg_uecc_word_t carry = 0;
> +  wordcount_t i;
> +  for (i = 0; i < num_words; ++i) {
> +    mg_uecc_word_t sum = left[i] + right[i] + carry;
> +    if (sum != left[i]) {
> +      carry = (sum < left[i]);
> +    }
> +    result[i] = sum;
> +  }
> +  return carry;
> +}
> +#endif /* !asm_add */
> +
> +/* Computes result = left - right, returning borrow. Can modify in place. */
> +#if !asm_sub
> +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> +                                               const mg_uecc_word_t *left,
> +                                               const mg_uecc_word_t *right,
> +                                               wordcount_t num_words) {
> +  mg_uecc_word_t borrow = 0;
> +  wordcount_t i;
> +  for (i = 0; i < num_words; ++i) {
> +    mg_uecc_word_t diff = left[i] - right[i] - borrow;
> +    if (diff != left[i]) {
> +      borrow = (diff > left[i]);
> +    }
> +    result[i] = diff;
> +  }
> +  return borrow;
> +}
> +#endif /* !asm_sub */
> +
> +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) ||               \
> +    (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0) && \
> +     ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
> +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0,
> +                   mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> +#if MG_UECC_WORD_SIZE == 8
> +  uint64_t a0 = a & 0xffffffff;
> +  uint64_t a1 = a >> 32;
> +  uint64_t b0 = b & 0xffffffff;
> +  uint64_t b1 = b >> 32;
> +
> +  uint64_t i0 = a0 * b0;
> +  uint64_t i1 = a0 * b1;
> +  uint64_t i2 = a1 * b0;
> +  uint64_t i3 = a1 * b1;
> +
> +  uint64_t p0, p1;
> +
> +  i2 += (i0 >> 32);
> +  i2 += i1;
> +  if (i2 < i1) { /* overflow */
> +    i3 += 0x100000000;
> +  }
>
> -static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
> -  struct mg_connection *c = NULL;
> -  union usa usa;
> -  socklen_t sa_len = sizeof(usa);
> -  MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> -  if (fd == MG_INVALID_SOCKET) {
> -#if MG_ARCH == MG_ARCH_AZURERTOS
> -    // AzureRTOS, in non-block socket mode can mark listening socket readable
> -    // even it is not. See comment for 'select' func implementation in
> -    // nx_bsd.c That's not an error, just should try later
> -    if (MG_SOCKET_ERRNO != EAGAIN)
> -#endif
> -      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCKET_ERRNO));
> -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> -    (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
> -  } else if ((long) fd >= FD_SETSIZE) {
> -    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> -    closesocket(fd);
> +  p0 = (i0 & 0xffffffff) | (i2 << 32);
> +  p1 = i3 + (i2 >> 32);
> +
> +  *r0 += p0;
> +  *r1 += (p1 + (*r0 < p0));
> +  *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> +#else
> +  mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> +  mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0;
> +  r01 += p;
> +  *r2 += (r01 < p);
> +  *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
> +  *r0 = (mg_uecc_word_t) r01;
>   #endif
> -  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> -    MG_ERROR(("%lu OOM", lsn->id));
> -    closesocket(fd);
> -  } else {
> -    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> -    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> -    c->fd = S2PTR(fd);
> -    MG_EPOLL_ADD(c);
> -    mg_set_non_blocking_mode(FD(c));
> -    setsockopts(c);
> -    c->is_accepted = 1;
> -    c->is_hexdumping = lsn->is_hexdumping;
> -    c->loc = lsn->loc;
> -    c->pfn = lsn->pfn;
> -    c->pfn_data = lsn->pfn_data;
> -    c->fn = lsn->fn;
> -    c->fn_data = lsn->fn_data;
> -    MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4, &c->rem.ip,
> -              mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
> -    mg_call(c, MG_EV_OPEN, NULL);
> -    mg_call(c, MG_EV_ACCEPT, NULL);
> +}
> +#endif /* muladd needed */
> +
> +#if !asm_mult
> +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
> +                                      const mg_uecc_word_t *left,
> +                                      const mg_uecc_word_t *right,
> +                                      wordcount_t num_words) {
> +  mg_uecc_word_t r0 = 0;
> +  mg_uecc_word_t r1 = 0;
> +  mg_uecc_word_t r2 = 0;
> +  wordcount_t i, k;
> +
> +  /* Compute each digit of result in sequence, maintaining the carries. */
> +  for (k = 0; k < num_words; ++k) {
> +    for (i = 0; i <= k; ++i) {
> +      muladd(left[i], right[k - i], &r0, &r1, &r2);
> +    }
> +    result[k] = r0;
> +    r0 = r1;
> +    r1 = r2;
> +    r2 = 0;
>     }
> +  for (k = num_words; k < num_words * 2 - 1; ++k) {
> +    for (i = (wordcount_t) ((k + 1) - num_words); i < num_words; ++i) {
> +      muladd(left[i], right[k - i], &r0, &r1, &r2);
> +    }
> +    result[k] = r0;
> +    r0 = r1;
> +    r1 = r2;
> +    r2 = 0;
> +  }
> +  result[num_words * 2 - 1] = r0;
>   }
> +#endif /* !asm_mult */
>
> -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2], bool udp) {
> -  MG_SOCKET_TYPE sock;
> -  socklen_t n = sizeof(usa[0].sin);
> -  bool success = false;
> +#if MG_UECC_SQUARE_FUNC
>
> -  sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
> -  (void) memset(&usa[0], 0, sizeof(usa[0]));
> -  usa[0].sin.sin_family = AF_INET;
> -  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U);  // 127.0.0.1
> -  usa[1] = usa[0];
> +#if !asm_square
> +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0,
> +                    mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> +#if MG_UECC_WORD_SIZE == 8
> +  uint64_t a0 = a & 0xffffffffull;
> +  uint64_t a1 = a >> 32;
> +  uint64_t b0 = b & 0xffffffffull;
> +  uint64_t b1 = b >> 32;
>
> -  if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> -      (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> -      bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 &&
> -      getsockname(sp[0], &usa[0].sa, &n) == 0 &&
> -      getsockname(sp[1], &usa[1].sa, &n) == 0 &&
> -      connect(sp[0], &usa[1].sa, n) == 0 &&
> -      connect(sp[1], &usa[0].sa, n) == 0) {
> -    success = true;
> -  } else if (!udp &&
> -             (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> -             bind(sock, &usa[0].sa, n) == 0 &&
> -             listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
> -             getsockname(sock, &usa[0].sa, &n) == 0 &&
> -             (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> -             connect(sp[0], &usa[0].sa, n) == 0 &&
> -             (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
> -    success = true;
> -  }
> -  if (success) {
> -    mg_set_non_blocking_mode(sp[1]);
> -  } else {
> -    if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> -    if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> -    sp[0] = sp[1] = MG_INVALID_SOCKET;
> +  uint64_t i0 = a0 * b0;
> +  uint64_t i1 = a0 * b1;
> +  uint64_t i2 = a1 * b0;
> +  uint64_t i3 = a1 * b1;
> +
> +  uint64_t p0, p1;
> +
> +  i2 += (i0 >> 32);
> +  i2 += i1;
> +  if (i2 < i1) { /* overflow */
> +    i3 += 0x100000000ull;
>     }
> -  if (sock != MG_INVALID_SOCKET) closesocket(sock);
> -  return success;
> +
> +  p0 = (i0 & 0xffffffffull) | (i2 << 32);
> +  p1 = i3 + (i2 >> 32);
> +
> +  *r2 += (p1 >> 63);
> +  p1 = (p1 << 1) | (p0 >> 63);
> +  p0 <<= 1;
> +
> +  *r0 += p0;
> +  *r1 += (p1 + (*r0 < p0));
> +  *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> +#else
> +  mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> +  mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0;
> +  *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
> +  p *= 2;
> +  r01 += p;
> +  *r2 += (r01 < p);
> +  *r1 = r01 >> MG_UECC_WORD_BITS;
> +  *r0 = (mg_uecc_word_t) r01;
> +#endif
>   }
>
> -int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data,
> -              bool udp) {
> -  union usa usa[2];
> -  MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> -  struct mg_connection *c = NULL;
> -  if (!mg_socketpair(sp, usa, udp)) {
> -    MG_ERROR(("Cannot create socket pair"));
> -  } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) {
> -    closesocket(sp[0]);
> -    closesocket(sp[1]);
> -    sp[0] = sp[1] = MG_INVALID_SOCKET;
> -  } else {
> -    tomgaddr(&usa[0], &c->rem, false);
> -    MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
> +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> +                                        const mg_uecc_word_t *left,
> +                                        wordcount_t num_words) {
> +  mg_uecc_word_t r0 = 0;
> +  mg_uecc_word_t r1 = 0;
> +  mg_uecc_word_t r2 = 0;
> +
> +  wordcount_t i, k;
> +
> +  for (k = 0; k < num_words * 2 - 1; ++k) {
> +    mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
> +    for (i = min; i <= k && i <= k - i; ++i) {
> +      if (i < k - i) {
> +        mul2add(left[i], left[k - i], &r0, &r1, &r2);
> +      } else {
> +        muladd(left[i], left[k - i], &r0, &r1, &r2);
> +      }
> +    }
> +    result[k] = r0;
> +    r0 = r1;
> +    r1 = r2;
> +    r2 = 0;
>     }
> -  return (int) sp[0];
> -}
>
> -static bool can_read(const struct mg_connection *c) {
> -  return c->is_full == false;
> +  result[num_words * 2 - 1] = r0;
>   }
> +#endif /* !asm_square */
>
> -static bool can_write(const struct mg_connection *c) {
> -  return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
> -}
> +#else /* MG_UECC_SQUARE_FUNC */
>
> -static bool skip_iotest(const struct mg_connection *c) {
> -  return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) ||
> -         (can_read(c) == false && can_write(c) == false);
> +#if MG_UECC_ENABLE_VLI_API
> +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> +                                        const mg_uecc_word_t *left,
> +                                        wordcount_t num_words) {
> +  mg_uecc_vli_mult(result, left, left, num_words);
>   }
> +#endif /* MG_UECC_ENABLE_VLI_API */
>
> -static void mg_iotest(struct mg_mgr *mgr, int ms) {
> -#if MG_ENABLE_FREERTOS_TCP
> -  struct mg_connection *c;
> -  for (c = mgr->conns; c != NULL; c = c->next) {
> -    c->is_readable = c->is_writable = 0;
> -    if (skip_iotest(c)) continue;
> -    if (can_read(c))
> -      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> -    if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> -  }
> -  FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> -  for (c = mgr->conns; c != NULL; c = c->next) {
> -    EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> -    c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> -    c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> -    FreeRTOS_FD_CLR(c->fd, mgr->ss,
> -                    eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> +#endif /* MG_UECC_SQUARE_FUNC */
> +
> +/* Computes result = (left + right) % mod.
> +   Assumes that left < mod and right < mod, and that result does not overlap
> +   mod. */
> +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
> +                                        const mg_uecc_word_t *left,
> +                                        const mg_uecc_word_t *right,
> +                                        const mg_uecc_word_t *mod,
> +                                        wordcount_t num_words) {
> +  mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right, num_words);
> +  if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) != 1) {
> +    /* result > mod (result = mod + remainder), so subtract mod to get
> +     * remainder. */
> +    mg_uecc_vli_sub(result, result, mod, num_words);
>     }
> -#elif MG_ENABLE_EPOLL
> -  size_t max = 1;
> -  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> -    c->is_readable = c->is_writable = 0;
> -    if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
> -    if (can_write(c)) MG_EPOLL_MOD(c, 1);
> -    max++;
> +}
> +
> +/* Computes result = (left - right) % mod.
> +   Assumes that left < mod and right < mod, and that result does not overlap
> +   mod. */
> +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
> +                                        const mg_uecc_word_t *left,
> +                                        const mg_uecc_word_t *right,
> +                                        const mg_uecc_word_t *mod,
> +                                        wordcount_t num_words) {
> +  mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right, num_words);
> +  if (l_borrow) {
> +    /* In this case, result == -diff == (max int) - diff. Since -x % d == d - x,
> +       we can get the correct result from result + mod (with overflow). */
> +    mg_uecc_vli_add(result, result, mod, num_words);
>     }
> -  struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));
> -  int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> -  for (int i = 0; i < n; i++) {
> -    struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
> -    if (evs[i].events & EPOLLERR) {
> -      mg_error(c, "socket error");
> -    } else if (c->is_readable == 0) {
> -      bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> -      bool wr = evs[i].events & EPOLLOUT;
> -      c->is_readable = can_read(c) && rd ? 1U : 0;
> -      c->is_writable = can_write(c) && wr ? 1U : 0;
> +}
> +
> +/* Computes result = product % mod, where product is 2N words long. */
> +/* Currently only designed to work for curve_p or curve_n. */
> +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
> +                                      mg_uecc_word_t *product,
> +                                      const mg_uecc_word_t *mod,
> +                                      wordcount_t num_words) {
> +  mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t *v[2] = {tmp, product};
> +  mg_uecc_word_t index;
> +
> +  /* Shift mod so its highest set bit is at the maximum position. */
> +  bitcount_t shift = (bitcount_t) ((num_words * 2 * MG_UECC_WORD_BITS) -
> +                                   mg_uecc_vli_numBits(mod, num_words));
> +  wordcount_t word_shift = (wordcount_t) (shift / MG_UECC_WORD_BITS);
> +  wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
> +  mg_uecc_word_t carry = 0;
> +  mg_uecc_vli_clear(mod_multiple, word_shift);
> +  if (bit_shift > 0) {
> +    for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
> +      mod_multiple[(mg_uecc_word_t) word_shift + index] =
> +          (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
> +      carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
>       }
> +  } else {
> +    mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
>     }
> -  (void) skip_iotest;
> -#elif MG_ENABLE_POLL
> -  nfds_t n = 0;
> -  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
> -  struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> -  memset(fds, 0, n * sizeof(fds[0]));
> -  n = 0;
> -  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> -    c->is_readable = c->is_writable = 0;
> -    if (skip_iotest(c)) {
> -      // Socket not valid, ignore
> -    } else if (mg_tls_pending(c) > 0) {
> -      ms = 1;  // Don't wait if TLS is ready
> -    } else {
> -      fds[n].fd = FD(c);
> -      if (can_read(c)) fds[n].events |= POLLIN;
> -      if (can_write(c)) fds[n].events |= POLLOUT;
> -      n++;
> +
> +  for (index = 1; shift >= 0; --shift) {
> +    mg_uecc_word_t borrow = 0;
> +    wordcount_t i;
> +    for (i = 0; i < num_words * 2; ++i) {
> +      mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
> +      if (diff != v[index][i]) {
> +        borrow = (diff > v[index][i]);
> +      }
> +      v[1 - index][i] = diff;
>       }
> +    index = !(index ^ borrow); /* Swap the index if there was no borrow */
> +    mg_uecc_vli_rshift1(mod_multiple, num_words);
> +    mod_multiple[num_words - 1] |= mod_multiple[num_words]
> +                                   << (MG_UECC_WORD_BITS - 1);
> +    mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
> +  }
> +  mg_uecc_vli_set(result, v[index], num_words);
> +}
> +
> +/* Computes result = (left * right) % mod. */
> +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
> +                                         const mg_uecc_word_t *left,
> +                                         const mg_uecc_word_t *right,
> +                                         const mg_uecc_word_t *mod,
> +                                         wordcount_t num_words) {
> +  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_vli_mult(product, left, right, num_words);
> +  mg_uecc_vli_mmod(result, product, mod, num_words);
> +}
> +
> +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
> +                                              const mg_uecc_word_t *left,
> +                                              const mg_uecc_word_t *right,
> +                                              MG_UECC_Curve curve) {
> +  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_vli_mult(product, left, right, curve->num_words);
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +  curve->mmod_fast(result, product);
> +#else
> +  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> +#endif
> +}
> +
> +#if MG_UECC_SQUARE_FUNC
> +
> +#if MG_UECC_ENABLE_VLI_API
> +/* Computes result = left^2 % mod. */
> +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> +                                           const mg_uecc_word_t *left,
> +                                           const mg_uecc_word_t *mod,
> +                                           wordcount_t num_words) {
> +  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_vli_square(product, left, num_words);
> +  mg_uecc_vli_mmod(result, product, mod, num_words);
> +}
> +#endif /* MG_UECC_ENABLE_VLI_API */
> +
> +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> +                                                const mg_uecc_word_t *left,
> +                                                MG_UECC_Curve curve) {
> +  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> +  mg_uecc_vli_square(product, left, curve->num_words);
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +  curve->mmod_fast(result, product);
> +#else
> +  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> +#endif
> +}
> +
> +#else /* MG_UECC_SQUARE_FUNC */
> +
> +#if MG_UECC_ENABLE_VLI_API
> +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> +                                           const mg_uecc_word_t *left,
> +                                           const mg_uecc_word_t *mod,
> +                                           wordcount_t num_words) {
> +  mg_uecc_vli_modMult(result, left, left, mod, num_words);
> +}
> +#endif /* MG_UECC_ENABLE_VLI_API */
> +
> +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> +                                                const mg_uecc_word_t *left,
> +                                                MG_UECC_Curve curve) {
> +  mg_uecc_vli_modMult_fast(result, left, left, curve);
> +}
> +
> +#endif /* MG_UECC_SQUARE_FUNC */
> +
> +#define EVEN(vli) (!(vli[0] & 1))
> +static void vli_modInv_update(mg_uecc_word_t *uv, const mg_uecc_word_t *mod,
> +                              wordcount_t num_words) {
> +  mg_uecc_word_t carry = 0;
> +  if (!EVEN(uv)) {
> +    carry = mg_uecc_vli_add(uv, uv, mod, num_words);
>     }
> +  mg_uecc_vli_rshift1(uv, num_words);
> +  if (carry) {
> +    uv[num_words - 1] |= HIGH_BIT_SET;
> +  }
> +}
>
> -  // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> -  if (poll(fds, n, ms) < 0) {
> -#if MG_ARCH == MG_ARCH_WIN32
> -    if (n == 0) Sleep(ms);  // On Windows, poll fails if no sockets
> -#endif
> -    memset(fds, 0, n * sizeof(fds[0]));
> +/* Computes result = (1 / input) % mod. All VLIs are the same size.
> +   See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" */
> +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
> +                                        const mg_uecc_word_t *input,
> +                                        const mg_uecc_word_t *mod,
> +                                        wordcount_t num_words) {
> +  mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
> +      u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
> +  cmpresult_t cmpResult;
> +
> +  if (mg_uecc_vli_isZero(input, num_words)) {
> +    mg_uecc_vli_clear(result, num_words);
> +    return;
>     }
> -  n = 0;
> -  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> -    if (skip_iotest(c)) {
> -      // Socket not valid, ignore
> -    } else if (mg_tls_pending(c) > 0) {
> -      c->is_readable = 1;
> +
> +  mg_uecc_vli_set(a, input, num_words);
> +  mg_uecc_vli_set(b, mod, num_words);
> +  mg_uecc_vli_clear(u, num_words);
> +  u[0] = 1;
> +  mg_uecc_vli_clear(v, num_words);
> +  while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) != 0) {
> +    if (EVEN(a)) {
> +      mg_uecc_vli_rshift1(a, num_words);
> +      vli_modInv_update(u, mod, num_words);
> +    } else if (EVEN(b)) {
> +      mg_uecc_vli_rshift1(b, num_words);
> +      vli_modInv_update(v, mod, num_words);
> +    } else if (cmpResult > 0) {
> +      mg_uecc_vli_sub(a, a, b, num_words);
> +      mg_uecc_vli_rshift1(a, num_words);
> +      if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
> +        mg_uecc_vli_add(u, u, mod, num_words);
> +      }
> +      mg_uecc_vli_sub(u, u, v, num_words);
> +      vli_modInv_update(u, mod, num_words);
>       } else {
> -      if (fds[n].revents & POLLERR) {
> -        mg_error(c, "socket error");
> -      } else {
> -        c->is_readable =
> -            (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> -        c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> +      mg_uecc_vli_sub(b, b, a, num_words);
> +      mg_uecc_vli_rshift1(b, num_words);
> +      if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
> +        mg_uecc_vli_add(v, v, mod, num_words);
>         }
> -      n++;
> +      mg_uecc_vli_sub(v, v, u, num_words);
> +      vli_modInv_update(v, mod, num_words);
>       }
>     }
> -#else
> -  struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0};
> -  struct mg_connection *c;
> -  fd_set rset, wset, eset;
> -  MG_SOCKET_TYPE maxfd = 0;
> -  int rc;
> +  mg_uecc_vli_set(result, u, num_words);
> +}
>
> -  FD_ZERO(&rset);
> -  FD_ZERO(&wset);
> -  FD_ZERO(&eset);
> -  for (c = mgr->conns; c != NULL; c = c->next) {
> -    c->is_readable = c->is_writable = 0;
> -    if (skip_iotest(c)) continue;
> -    FD_SET(FD(c), &eset);
> -    if (can_read(c)) FD_SET(FD(c), &rset);
> -    if (can_write(c)) FD_SET(FD(c), &wset);
> -    if (mg_tls_pending(c) > 0) tv = tv_zero;
> -    if (FD(c) > maxfd) maxfd = FD(c);
> +/* ------ Point operations ------ */
> +
> +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
> +
> +#ifndef _UECC_CURVE_SPECIFIC_H_
> +#define _UECC_CURVE_SPECIFIC_H_
> +
> +#define num_bytes_secp160r1 20
> +#define num_bytes_secp192r1 24
> +#define num_bytes_secp224r1 28
> +#define num_bytes_secp256r1 32
> +#define num_bytes_secp256k1 32
> +
> +#if (MG_UECC_WORD_SIZE == 1)
> +
> +#define num_words_secp160r1 20
> +#define num_words_secp192r1 24
> +#define num_words_secp224r1 28
> +#define num_words_secp256r1 32
> +#define num_words_secp256k1 32
> +
> +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
> +  0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
> +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
> +
> +#elif (MG_UECC_WORD_SIZE == 4)
> +
> +#define num_words_secp160r1 5
> +#define num_words_secp192r1 6
> +#define num_words_secp224r1 7
> +#define num_words_secp256r1 8
> +#define num_words_secp256k1 8
> +
> +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 0x##h##g##f##e
> +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
> +
> +#elif (MG_UECC_WORD_SIZE == 8)
> +
> +#define num_words_secp160r1 3
> +#define num_words_secp192r1 3
> +#define num_words_secp224r1 4
> +#define num_words_secp256r1 4
> +#define num_words_secp256k1 4
> +
> +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##h##g##f##e##d##c##b##a##U
> +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
> +
> +#endif /* MG_UECC_WORD_SIZE */
> +
> +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> +    MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
> +static void double_jacobian_default(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                                    mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> +  /* t1 = X, t2 = Y, t3 = Z */
> +  mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
> +  wordcount_t num_words = curve->num_words;
> +
> +  if (mg_uecc_vli_isZero(Z1, num_words)) {
> +    return;
>     }
>
> -  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) < 0) {
> -#if MG_ARCH == MG_ARCH_WIN32
> -    if (maxfd == 0) Sleep(ms);  // On Windows, select fails if no sockets
> +  mg_uecc_vli_modSquare_fast(t4, Y1, curve);   /* t4 = y1^2 */
> +  mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */
> +  mg_uecc_vli_modSquare_fast(t4, t4, curve);   /* t4 = y1^4 */
> +  mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
> +  mg_uecc_vli_modSquare_fast(Z1, Z1, curve);   /* t3 = z1^2 */
> +
> +  mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */
> +  mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */
> +  mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */
> +  mg_uecc_vli_modMult_fast(X1, X1, Z1, curve);         /* t1 = x1^2 - z1^4 */
> +
> +  mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
> +                     num_words); /* t3 = 2*(x1^2 - z1^4) */
> +  mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
> +                     num_words); /* t1 = 3*(x1^2 - z1^4) */
> +  if (mg_uecc_vli_testBit(X1, 0)) {
> +    mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p, num_words);
> +    mg_uecc_vli_rshift1(X1, num_words);
> +    X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
> +  } else {
> +    mg_uecc_vli_rshift1(X1, num_words);
> +  }
> +  /* t1 = 3/2*(x1^2 - z1^4) = B */
> +
> +  mg_uecc_vli_modSquare_fast(Z1, X1, curve);           /* t3 = B^2 */
> +  mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */
> +  mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */
> +  mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */
> +  mg_uecc_vli_modMult_fast(X1, X1, t5, curve);         /* t1 = B * (A - x3) */
> +  mg_uecc_vli_modSub(t4, X1, t4, curve->p,
> +                     num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
> +
> +  mg_uecc_vli_set(X1, Z1, num_words);
> +  mg_uecc_vli_set(Z1, Y1, num_words);
> +  mg_uecc_vli_set(Y1, t4, num_words);
> +}
> +
> +/* Computes result = x^3 + ax + b. result must not overlap x. */
> +static void x_side_default(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> +                           MG_UECC_Curve curve) {
> +  mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
> +  wordcount_t num_words = curve->num_words;
> +
> +  mg_uecc_vli_modSquare_fast(result, x, curve);                /* r = x^2 */
> +  mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */
> +  mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */
> +  mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> +                     num_words); /* r = x^3 - 3x + b */
> +}
> +#endif /* MG_UECC_SUPPORTS_secp... */
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> +    MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
> +/* Compute a = sqrt(a) (mod curve_p). */
> +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> +  bitcount_t i;
> +  mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
> +  mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
> +  wordcount_t num_words = curve->num_words;
> +
> +  /* When curve->p == 3 (mod 4), we can compute
> +     sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
> +  mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */
> +  for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
> +    mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
> +    if (mg_uecc_vli_testBit(p1, i)) {
> +      mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
> +    }
> +  }
> +  mg_uecc_vli_set(a, l_result, num_words);
> +}
> +#endif /* MG_UECC_SUPPORTS_secp... */
> +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> +
> +#if MG_UECC_SUPPORTS_secp160r1
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product);
> +#endif
> +
> +static const struct MG_UECC_Curve_t curve_secp160r1 = {
> +    num_words_secp160r1,
> +    num_bytes_secp160r1,
> +    161, /* num_n_bits */
> +    {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
> +     BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
> +    {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
> +     BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
> +     BYTES_TO_WORDS_4(68, B5, 96, 4A),
> +
> +     BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
> +     BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
> +     BYTES_TO_WORDS_4(55, 28, A6, 23)},
> +    {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
> +     BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
> +     BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
> +    &double_jacobian_default,
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +    &mod_sqrt_default,
> +#endif
> +    &x_side_default,
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +    &vli_mmod_fast_secp160r1
> +#endif
> +};
> +
> +MG_UECC_Curve mg_uecc_secp160r1(void) {
> +  return &curve_secp160r1;
> +}
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
> +/* Computes result = product % curve_p
> +    see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354
> +
> +    Note that this only works if log2(omega) < log2(p) / 2 */
> +static void omega_mult_secp160r1(mg_uecc_word_t *result,
> +                                 const mg_uecc_word_t *right);
> +#if MG_UECC_WORD_SIZE == 8
> +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product) {
> +  mg_uecc_word_t tmp[2 * num_words_secp160r1];
> +  mg_uecc_word_t copy;
> +
> +  mg_uecc_vli_clear(tmp, num_words_secp160r1);
> +  mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> +
> +  omega_mult_secp160r1(tmp,
> +                       product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
> +
> +  product[num_words_secp160r1 - 1] &= 0xffffffff;
> +  copy = tmp[num_words_secp160r1 - 1];
> +  tmp[num_words_secp160r1 - 1] &= 0xffffffff;
> +  mg_uecc_vli_add(result, product, tmp,
> +                  num_words_secp160r1); /* (C, r) = r + q */
> +  mg_uecc_vli_clear(product, num_words_secp160r1);
> +  tmp[num_words_secp160r1 - 1] = copy;
> +  omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c */
> +  mg_uecc_vli_add(result, result, product,
> +                  num_words_secp160r1); /* (C1, r) = r + Rq*c */
> +
> +  while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
> +                                num_words_secp160r1) > 0) {
> +    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
> +  }
> +}
> +
> +static void omega_mult_secp160r1(uint64_t *result, const uint64_t *right) {
> +  uint32_t carry;
> +  unsigned i;
> +
> +  /* Multiply by (2^31 + 1). */
> +  carry = 0;
> +  for (i = 0; i < num_words_secp160r1; ++i) {
> +    uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
> +    result[i] = (tmp << 31) + tmp + carry;
> +    carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i] == tmp));
> +  }
> +  result[i] = carry;
> +}
>   #else
> -    MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
> +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product) {
> +  mg_uecc_word_t tmp[2 * num_words_secp160r1];
> +  mg_uecc_word_t carry;
> +
> +  mg_uecc_vli_clear(tmp, num_words_secp160r1);
> +  mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> +
> +  omega_mult_secp160r1(tmp,
> +                       product + num_words_secp160r1); /* (Rq, q) = q * c */
> +
> +  carry = mg_uecc_vli_add(result, product, tmp,
> +                          num_words_secp160r1); /* (C, r) = r + q */
> +  mg_uecc_vli_clear(product, num_words_secp160r1);
> +  omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */
> +  carry += mg_uecc_vli_add(result, result, product,
> +                           num_words_secp160r1); /* (C1, r) = r + Rq*c */
> +
> +  while (carry > 0) {
> +    --carry;
> +    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
> +  }
> +  if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) >
> +      0) {
> +    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
> +  }
> +}
>   #endif
> -    FD_ZERO(&rset);
> -    FD_ZERO(&wset);
> -    FD_ZERO(&eset);
> +
> +#if MG_UECC_WORD_SIZE == 1
> +static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) {
> +  uint8_t carry;
> +  uint8_t i;
> +
> +  /* Multiply by (2^31 + 1). */
> +  mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */
> +  mg_uecc_vli_rshift1(result + 4, num_words_secp160r1);    /* 2^31 */
> +  result[3] = right[0] << 7; /* get last bit from shift */
> +
> +  carry = mg_uecc_vli_add(result, result, right,
> +                          num_words_secp160r1); /* 2^31 + 1 */
> +  for (i = num_words_secp160r1; carry; ++i) {
> +    uint16_t sum = (uint16_t) result[i] + carry;
> +    result[i] = (uint8_t) sum;
> +    carry = sum >> 8;
>     }
> +}
> +#elif MG_UECC_WORD_SIZE == 4
> +static void omega_mult_secp160r1(uint32_t *result, const uint32_t *right) {
> +  uint32_t carry;
> +  unsigned i;
>
> -  for (c = mgr->conns; c != NULL; c = c->next) {
> -    if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> -      mg_error(c, "socket error");
> -    } else {
> -      c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
> -      c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
> -      if (mg_tls_pending(c) > 0) c->is_readable = 1;
> -    }
> +  /* Multiply by (2^31 + 1). */
> +  mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */
> +  mg_uecc_vli_rshift1(result + 1, num_words_secp160r1);    /* 2^31 */
> +  result[0] = right[0] << 31; /* get last bit from shift */
> +
> +  carry = mg_uecc_vli_add(result, result, right,
> +                          num_words_secp160r1); /* 2^31 + 1 */
> +  for (i = num_words_secp160r1; carry; ++i) {
> +    uint64_t sum = (uint64_t) result[i] + carry;
> +    result[i] = (uint32_t) sum;
> +    carry = sum >> 32;
>     }
> +}
> +#endif /* MG_UECC_WORD_SIZE */
> +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */
> +
> +#endif /* MG_UECC_SUPPORTS_secp160r1 */
> +
> +#if MG_UECC_SUPPORTS_secp192r1
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product);
>   #endif
> +
> +static const struct MG_UECC_Curve_t curve_secp192r1 = {
> +    num_words_secp192r1,
> +    num_bytes_secp192r1,
> +    192, /* num_n_bits */
> +    {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
> +     BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
> +     BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
> +     BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
> +
> +     BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
> +     BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
> +     BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
> +    {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
> +     BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
> +     BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
> +    &double_jacobian_default,
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +    &mod_sqrt_default,
> +#endif
> +    &x_side_default,
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +    &vli_mmod_fast_secp192r1
> +#endif
> +};
> +
> +MG_UECC_Curve mg_uecc_secp192r1(void) {
> +  return &curve_secp192r1;
>   }
>
> -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> -  struct mg_connection *c, *tmp;
> -  uint64_t now;
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +/* Computes result = product % curve_p.
> +   See algorithm 5 and 6 from http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
> + */
> +#if MG_UECC_WORD_SIZE == 1
> +static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) {
> +  uint8_t tmp[num_words_secp192r1];
> +  uint8_t carry;
> +
> +  mg_uecc_vli_set(result, product, num_words_secp192r1);
> +
> +  mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
> +  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> +  tmp[8] = product[24];
> +  tmp[9] = product[25];
> +  tmp[10] = product[26];
> +  tmp[11] = product[27];
> +  tmp[12] = product[28];
> +  tmp[13] = product[29];
> +  tmp[14] = product[30];
> +  tmp[15] = product[31];
> +  tmp[16] = product[32];
> +  tmp[17] = product[33];
> +  tmp[18] = product[34];
> +  tmp[19] = product[35];
> +  tmp[20] = product[36];
> +  tmp[21] = product[37];
> +  tmp[22] = product[38];
> +  tmp[23] = product[39];
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  tmp[0] = tmp[8] = product[40];
> +  tmp[1] = tmp[9] = product[41];
> +  tmp[2] = tmp[10] = product[42];
> +  tmp[3] = tmp[11] = product[43];
> +  tmp[4] = tmp[12] = product[44];
> +  tmp[5] = tmp[13] = product[45];
> +  tmp[6] = tmp[14] = product[46];
> +  tmp[7] = tmp[15] = product[47];
> +  tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] =
> +      tmp[23] = 0;
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> +                                         num_words_secp192r1) != 1) {
> +    carry -=
> +        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
> +  }
> +}
> +#elif MG_UECC_WORD_SIZE == 4
> +static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t *product) {
> +  uint32_t tmp[num_words_secp192r1];
> +  int carry;
>
> -  mg_iotest(mgr, ms);
> -  now = mg_millis();
> -  mg_timer_poll(&mgr->timers, now);
> +  mg_uecc_vli_set(result, product, num_words_secp192r1);
>
> -  for (c = mgr->conns; c != NULL; c = tmp) {
> -    bool is_resp = c->is_resp;
> -    tmp = c->next;
> -    mg_call(c, MG_EV_POLL, &now);
> -    if (is_resp && !c->is_resp) {
> -      long n = 0;
> -      mg_call(c, MG_EV_READ, &n);
> -    }
> -    MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
> -                c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
> -                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> -                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> -    if (c->is_resolving || c->is_closing) {
> -      // Do nothing
> -    } else if (c->is_listening && c->is_udp == 0) {
> -      if (c->is_readable) accept_conn(mgr, c);
> -    } else if (c->is_connecting) {
> -      if (c->is_readable || c->is_writable) connect_conn(c);
> -    } else if (c->is_tls_hs) {
> -      if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> -    } else {
> -      if (c->is_readable) read_conn(c);
> -      if (c->is_writable) write_conn(c);
> -    }
> +  mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
> +  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
>
> -    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> -    if (c->is_closing) close_conn(c);
> +  tmp[0] = tmp[1] = 0;
> +  tmp[2] = product[6];
> +  tmp[3] = product[7];
> +  tmp[4] = product[8];
> +  tmp[5] = product[9];
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  tmp[0] = tmp[2] = product[10];
> +  tmp[1] = tmp[3] = product[11];
> +  tmp[4] = tmp[5] = 0;
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> +                                         num_words_secp192r1) != 1) {
> +    carry -=
> +        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
> +  }
> +}
> +#else
> +static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t *product) {
> +  uint64_t tmp[num_words_secp192r1];
> +  int carry;
> +
> +  mg_uecc_vli_set(result, product, num_words_secp192r1);
> +
> +  mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
> +  carry = (int) mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  tmp[0] = 0;
> +  tmp[1] = product[3];
> +  tmp[2] = product[4];
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  tmp[0] = tmp[1] = product[5];
> +  tmp[2] = 0;
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> +
> +  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> +                                         num_words_secp192r1) != 1) {
> +    carry -=
> +        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
>     }
>   }
> +#endif /* MG_UECC_WORD_SIZE */
> +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
> +
> +#endif /* MG_UECC_SUPPORTS_secp192r1 */
> +
> +#if MG_UECC_SUPPORTS_secp224r1
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve);
> +#endif
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product);
>   #endif
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/ssi.c"
> +static const struct MG_UECC_Curve_t curve_secp224r1 = {
> +    num_words_secp224r1,
> +    num_bytes_secp224r1,
> +    224, /* num_n_bits */
> +    {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
> +     BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
> +     BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
> +     BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
> +     BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
> +
> +     BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
> +     BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
> +     BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
> +     BYTES_TO_WORDS_4(88, 63, 37, BD)},
> +    {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
> +     BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
> +     BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
> +     BYTES_TO_WORDS_4(85, 0A, 05, B4)},
> +    &double_jacobian_default,
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +    &mod_sqrt_secp224r1,
>   #endif
> +    &x_side_default,
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +    &vli_mmod_fast_secp224r1
> +#endif
> +};
> +
> +MG_UECC_Curve mg_uecc_secp224r1(void) {
> +  return &curve_secp224r1;
> +}
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +/* Routine 3.2.4 RS;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
> +                                  mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> +                                  const mg_uecc_word_t *e0,
> +                                  const mg_uecc_word_t *f0) {
> +  mg_uecc_word_t t[num_words_secp224r1];
> +
> +  mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1);    /* t <-- d0 ^ 2 */
> +  mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */
> +  mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
> +                     num_words_secp224r1); /* d1 <-- t  + f0 */
> +  mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
> +                     num_words_secp224r1);               /* e1 <-- e1 + e1 */
> +  mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t  * f0 */
> +  mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> +                     num_words_secp224r1); /* f1 <-- f1 + f1 */
> +  mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> +                     num_words_secp224r1); /* f1 <-- f1 + f1 */
> +}
> +
> +/* Routine 3.2.5 RSS;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
> +                                   mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> +                                   const mg_uecc_word_t *e0,
> +                                   const mg_uecc_word_t *f0,
> +                                   const bitcount_t j) {
> +  bitcount_t i;
> +
> +  mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
> +  mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
> +  mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
> +  for (i = 1; i <= j; i++) {
> +    mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS (d1,e1,f1,d1,e1,f1) */
> +  }
> +}
> +
> +/* Routine 3.2.6 RM;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2, mg_uecc_word_t *e2,
> +                                  mg_uecc_word_t *f2, const mg_uecc_word_t *c,
> +                                  const mg_uecc_word_t *d0,
> +                                  const mg_uecc_word_t *e0,
> +                                  const mg_uecc_word_t *d1,
> +                                  const mg_uecc_word_t *e1) {
> +  mg_uecc_word_t t1[num_words_secp224r1];
> +  mg_uecc_word_t t2[num_words_secp224r1];
> +
> +  mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */
> +  mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1);  /* t1 <-- t1 * c */
> +  /* t1 <-- p  - t1 */
> +  mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
> +                     num_words_secp224r1);
> +  mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */
> +  mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
> +                     num_words_secp224r1);                /* t2 <-- t2 + t1 */
> +  mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */
> +  mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */
> +  mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
> +                     num_words_secp224r1);               /* e2 <-- e2 + t1 */
> +  mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1);  /* f2 <-- e2^2 */
> +  mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */
> +  /* f2 <-- p  - f2 */
> +  mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
> +                     num_words_secp224r1);
> +  mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
> +}
> +
> +/* Routine 3.2.7 RP;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
> +                                  mg_uecc_word_t *f1, const mg_uecc_word_t *c,
> +                                  const mg_uecc_word_t *r) {
> +  wordcount_t i;
> +  wordcount_t pow2i = 1;
> +  mg_uecc_word_t d0[num_words_secp224r1];
> +  mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
> +  mg_uecc_word_t f0[num_words_secp224r1];
> +
> +  mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
> +  /* f0 <-- p  - c */
> +  mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
> +                     num_words_secp224r1);
> +  for (i = 0; i <= 6; i++) {
> +    mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
> +                           pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
> +    mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
> +                          e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
> +    mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> +    mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> +    mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> +    pow2i *= 2;
> +  }
> +}
>
> +/* Compute a = sqrt(a) (mod curve_p). */
> +/* Routine 3.2.8 mp_mod_sqrt_224; from
> + * http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> +  (void) curve;
> +  bitcount_t i;
> +  mg_uecc_word_t e1[num_words_secp224r1];
> +  mg_uecc_word_t f1[num_words_secp224r1];
> +  mg_uecc_word_t d0[num_words_secp224r1];
> +  mg_uecc_word_t e0[num_words_secp224r1];
> +  mg_uecc_word_t f0[num_words_secp224r1];
> +  mg_uecc_word_t d1[num_words_secp224r1];
> +
> +  /* s = a; using constant instead of random value */
> +  mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */
> +  mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> +                        f0); /* RS (d1, e1, f1, d0, e0, f0) */
> +  for (i = 1; i <= 95; i++) {
> +    mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> +    mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> +    mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> +    mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> +                          f0); /* RS (d1, e1, f1, d0, e0, f0) */
> +    if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */
> +      break;
> +    }
> +  }
> +  mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
> +                     num_words_secp224r1);               /* f1 <-- 1 / e0 */
> +  mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a  <-- d0 / e0 */
> +}
> +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +/* Computes result = product % curve_p
> +   from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +#if MG_UECC_WORD_SIZE == 1
> +static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) {
> +  uint8_t tmp[num_words_secp224r1];
> +  int8_t carry;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp224r1);
> +
> +  /* s1 */
> +  tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> +  tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> +  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> +  tmp[12] = product[28];
> +  tmp[13] = product[29];
> +  tmp[14] = product[30];
> +  tmp[15] = product[31];
> +  tmp[16] = product[32];
> +  tmp[17] = product[33];
> +  tmp[18] = product[34];
> +  tmp[19] = product[35];
> +  tmp[20] = product[36];
> +  tmp[21] = product[37];
> +  tmp[22] = product[38];
> +  tmp[23] = product[39];
> +  tmp[24] = product[40];
> +  tmp[25] = product[41];
> +  tmp[26] = product[42];
> +  tmp[27] = product[43];
> +  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* s2 */
> +  tmp[12] = product[44];
> +  tmp[13] = product[45];
> +  tmp[14] = product[46];
> +  tmp[15] = product[47];
> +  tmp[16] = product[48];
> +  tmp[17] = product[49];
> +  tmp[18] = product[50];
> +  tmp[19] = product[51];
> +  tmp[20] = product[52];
> +  tmp[21] = product[53];
> +  tmp[22] = product[54];
> +  tmp[23] = product[55];
> +  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* d1 */
> +  tmp[0] = product[28];
> +  tmp[1] = product[29];
> +  tmp[2] = product[30];
> +  tmp[3] = product[31];
> +  tmp[4] = product[32];
> +  tmp[5] = product[33];
> +  tmp[6] = product[34];
> +  tmp[7] = product[35];
> +  tmp[8] = product[36];
> +  tmp[9] = product[37];
> +  tmp[10] = product[38];
> +  tmp[11] = product[39];
> +  tmp[12] = product[40];
> +  tmp[13] = product[41];
> +  tmp[14] = product[42];
> +  tmp[15] = product[43];
> +  tmp[16] = product[44];
> +  tmp[17] = product[45];
> +  tmp[18] = product[46];
> +  tmp[19] = product[47];
> +  tmp[20] = product[48];
> +  tmp[21] = product[49];
> +  tmp[22] = product[50];
> +  tmp[23] = product[51];
> +  tmp[24] = product[52];
> +  tmp[25] = product[53];
> +  tmp[26] = product[54];
> +  tmp[27] = product[55];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  /* d2 */
> +  tmp[0] = product[44];
> +  tmp[1] = product[45];
> +  tmp[2] = product[46];
> +  tmp[3] = product[47];
> +  tmp[4] = product[48];
> +  tmp[5] = product[49];
> +  tmp[6] = product[50];
> +  tmp[7] = product[51];
> +  tmp[8] = product[52];
> +  tmp[9] = product[53];
> +  tmp[10] = product[54];
> +  tmp[11] = product[55];
> +  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> +  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> +  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> +  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> +                               num_words_secp224r1);
> +    } while (carry < 0);
> +  } else {
> +    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> +                                           num_words_secp224r1) != 1) {
> +      carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> +                               num_words_secp224r1);
> +    }
> +  }
> +}
> +#elif MG_UECC_WORD_SIZE == 4
> +static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) {
> +  uint32_t tmp[num_words_secp224r1];
> +  int carry;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp224r1);
> +
> +  /* s1 */
> +  tmp[0] = tmp[1] = tmp[2] = 0;
> +  tmp[3] = product[7];
> +  tmp[4] = product[8];
> +  tmp[5] = product[9];
> +  tmp[6] = product[10];
> +  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* s2 */
> +  tmp[3] = product[11];
> +  tmp[4] = product[12];
> +  tmp[5] = product[13];
> +  tmp[6] = 0;
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* d1 */
> +  tmp[0] = product[7];
> +  tmp[1] = product[8];
> +  tmp[2] = product[9];
> +  tmp[3] = product[10];
> +  tmp[4] = product[11];
> +  tmp[5] = product[12];
> +  tmp[6] = product[13];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  /* d2 */
> +  tmp[0] = product[11];
> +  tmp[1] = product[12];
> +  tmp[2] = product[13];
> +  tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> +                               num_words_secp224r1);
> +    } while (carry < 0);
> +  } else {
> +    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> +                                           num_words_secp224r1) != 1) {
> +      carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> +                               num_words_secp224r1);
> +    }
> +  }
> +}
> +#else
> +static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) {
> +  uint64_t tmp[num_words_secp224r1];
> +  int carry = 0;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp224r1);
> +  result[num_words_secp224r1 - 1] &= 0xffffffff;
> +
> +  /* s1 */
> +  tmp[0] = 0;
> +  tmp[1] = product[3] & 0xffffffff00000000ull;
> +  tmp[2] = product[4];
> +  tmp[3] = product[5] & 0xffffffff;
> +  mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* s2 */
> +  tmp[1] = product[5] & 0xffffffff00000000ull;
> +  tmp[2] = product[6];
> +  tmp[3] = 0;
> +  mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> +
> +  /* d1 */
> +  tmp[0] = (product[3] >> 32) | (product[4] << 32);
> +  tmp[1] = (product[4] >> 32) | (product[5] << 32);
> +  tmp[2] = (product[5] >> 32) | (product[6] << 32);
> +  tmp[3] = product[6] >> 32;
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  /* d2 */
> +  tmp[0] = (product[5] >> 32) | (product[6] << 32);
> +  tmp[1] = product[6] >> 32;
> +  tmp[2] = tmp[3] = 0;
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> +                               num_words_secp224r1);
> +    } while (carry < 0);
> +  } else {
> +    while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> +                                  num_words_secp224r1) != 1) {
> +      mg_uecc_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1);
> +    }
> +  }
> +}
> +#endif /* MG_UECC_WORD_SIZE */
> +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
>
> +#endif /* MG_UECC_SUPPORTS_secp224r1 */
>
> +#if MG_UECC_SUPPORTS_secp256r1
>
> -#ifndef MG_MAX_SSI_DEPTH
> -#define MG_MAX_SSI_DEPTH 5
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product);
>   #endif
>
> -#ifndef MG_SSI_BUFSIZ
> -#define MG_SSI_BUFSIZ 1024
> +static const struct MG_UECC_Curve_t curve_secp256r1 = {
> +    num_words_secp256r1,
> +    num_bytes_secp256r1,
> +    256, /* num_n_bits */
> +    {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
> +     BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
> +     BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
> +     BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
> +     BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
> +
> +     BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
> +     BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
> +     BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
> +     BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
> +    {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
> +     BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
> +     BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
> +     BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
> +    &double_jacobian_default,
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +    &mod_sqrt_default,
> +#endif
> +    &x_side_default,
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +    &vli_mmod_fast_secp256r1
>   #endif
> +};
>
> -#if MG_ENABLE_SSI
> -static char *mg_ssi(const char *path, const char *root, int depth) {
> -  struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> -  FILE *fp = fopen(path, "rb");
> -  if (fp != NULL) {
> -    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> -    int ch, intag = 0;
> -    size_t len = 0;
> -    buf[0] = arg[0] = '\0';
> -    while ((ch = fgetc(fp)) != EOF) {
> -      if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
> -        buf[len++] = (char) (ch & 0xff);
> -        buf[len] = '\0';
> -        if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> -          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> -              *p = (char *) path + strlen(path), *data;
> -          while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> -          mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
> -          if (depth < MG_MAX_SSI_DEPTH &&
> -              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> -            mg_iobuf_add(&b, b.len, data, strlen(data));
> -            free(data);
> -          } else {
> -            MG_ERROR(("%s: file=%s error or too deep", path, arg));
> -          }
> -        } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> -          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> -          mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> -          if (depth < MG_MAX_SSI_DEPTH &&
> -              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> -            mg_iobuf_add(&b, b.len, data, strlen(data));
> -            free(data);
> -          } else {
> -            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> -          }
> -        } else {
> -          // Unknown SSI tag
> -          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> -          mg_iobuf_add(&b, b.len, buf, len);
> -        }
> -        intag = 0;
> -        len = 0;
> -      } else if (ch == '<') {
> -        intag = 1;
> -        if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> -        len = 0;
> -        buf[len++] = (char) (ch & 0xff);
> -      } else if (intag) {
> -        if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> -          intag = 0;
> -        } else if (len >= sizeof(buf) - 2) {
> -          MG_ERROR(("%s: SSI tag is too large", path));
> -          len = 0;
> -        }
> -        buf[len++] = (char) (ch & 0xff);
> -      } else {
> -        buf[len++] = (char) (ch & 0xff);
> -        if (len >= sizeof(buf)) {
> -          mg_iobuf_add(&b, b.len, buf, len);
> -          len = 0;
> -        }
> -      }
> +MG_UECC_Curve mg_uecc_secp256r1(void) {
> +  return &curve_secp256r1;
> +}
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
> +/* Computes result = product % curve_p
> +   from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> +#if MG_UECC_WORD_SIZE == 1
> +static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) {
> +  uint8_t tmp[num_words_secp256r1];
> +  int8_t carry;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp256r1);
> +
> +  /* s1 */
> +  tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> +  tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> +  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> +  tmp[12] = product[44];
> +  tmp[13] = product[45];
> +  tmp[14] = product[46];
> +  tmp[15] = product[47];
> +  tmp[16] = product[48];
> +  tmp[17] = product[49];
> +  tmp[18] = product[50];
> +  tmp[19] = product[51];
> +  tmp[20] = product[52];
> +  tmp[21] = product[53];
> +  tmp[22] = product[54];
> +  tmp[23] = product[55];
> +  tmp[24] = product[56];
> +  tmp[25] = product[57];
> +  tmp[26] = product[58];
> +  tmp[27] = product[59];
> +  tmp[28] = product[60];
> +  tmp[29] = product[61];
> +  tmp[30] = product[62];
> +  tmp[31] = product[63];
> +  carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s2 */
> +  tmp[12] = product[48];
> +  tmp[13] = product[49];
> +  tmp[14] = product[50];
> +  tmp[15] = product[51];
> +  tmp[16] = product[52];
> +  tmp[17] = product[53];
> +  tmp[18] = product[54];
> +  tmp[19] = product[55];
> +  tmp[20] = product[56];
> +  tmp[21] = product[57];
> +  tmp[22] = product[58];
> +  tmp[23] = product[59];
> +  tmp[24] = product[60];
> +  tmp[25] = product[61];
> +  tmp[26] = product[62];
> +  tmp[27] = product[63];
> +  tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
> +  carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s3 */
> +  tmp[0] = product[32];
> +  tmp[1] = product[33];
> +  tmp[2] = product[34];
> +  tmp[3] = product[35];
> +  tmp[4] = product[36];
> +  tmp[5] = product[37];
> +  tmp[6] = product[38];
> +  tmp[7] = product[39];
> +  tmp[8] = product[40];
> +  tmp[9] = product[41];
> +  tmp[10] = product[42];
> +  tmp[11] = product[43];
> +  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> +  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> +  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> +  tmp[24] = product[56];
> +  tmp[25] = product[57];
> +  tmp[26] = product[58];
> +  tmp[27] = product[59];
> +  tmp[28] = product[60];
> +  tmp[29] = product[61];
> +  tmp[30] = product[62];
> +  tmp[31] = product[63];
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s4 */
> +  tmp[0] = product[36];
> +  tmp[1] = product[37];
> +  tmp[2] = product[38];
> +  tmp[3] = product[39];
> +  tmp[4] = product[40];
> +  tmp[5] = product[41];
> +  tmp[6] = product[42];
> +  tmp[7] = product[43];
> +  tmp[8] = product[44];
> +  tmp[9] = product[45];
> +  tmp[10] = product[46];
> +  tmp[11] = product[47];
> +  tmp[12] = product[52];
> +  tmp[13] = product[53];
> +  tmp[14] = product[54];
> +  tmp[15] = product[55];
> +  tmp[16] = product[56];
> +  tmp[17] = product[57];
> +  tmp[18] = product[58];
> +  tmp[19] = product[59];
> +  tmp[20] = product[60];
> +  tmp[21] = product[61];
> +  tmp[22] = product[62];
> +  tmp[23] = product[63];
> +  tmp[24] = product[52];
> +  tmp[25] = product[53];
> +  tmp[26] = product[54];
> +  tmp[27] = product[55];
> +  tmp[28] = product[32];
> +  tmp[29] = product[33];
> +  tmp[30] = product[34];
> +  tmp[31] = product[35];
> +  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* d1 */
> +  tmp[0] = product[44];
> +  tmp[1] = product[45];
> +  tmp[2] = product[46];
> +  tmp[3] = product[47];
> +  tmp[4] = product[48];
> +  tmp[5] = product[49];
> +  tmp[6] = product[50];
> +  tmp[7] = product[51];
> +  tmp[8] = product[52];
> +  tmp[9] = product[53];
> +  tmp[10] = product[54];
> +  tmp[11] = product[55];
> +  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> +  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> +  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> +  tmp[24] = product[32];
> +  tmp[25] = product[33];
> +  tmp[26] = product[34];
> +  tmp[27] = product[35];
> +  tmp[28] = product[40];
> +  tmp[29] = product[41];
> +  tmp[30] = product[42];
> +  tmp[31] = product[43];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d2 */
> +  tmp[0] = product[48];
> +  tmp[1] = product[49];
> +  tmp[2] = product[50];
> +  tmp[3] = product[51];
> +  tmp[4] = product[52];
> +  tmp[5] = product[53];
> +  tmp[6] = product[54];
> +  tmp[7] = product[55];
> +  tmp[8] = product[56];
> +  tmp[9] = product[57];
> +  tmp[10] = product[58];
> +  tmp[11] = product[59];
> +  tmp[12] = product[60];
> +  tmp[13] = product[61];
> +  tmp[14] = product[62];
> +  tmp[15] = product[63];
> +  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> +  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> +  tmp[24] = product[36];
> +  tmp[25] = product[37];
> +  tmp[26] = product[38];
> +  tmp[27] = product[39];
> +  tmp[28] = product[44];
> +  tmp[29] = product[45];
> +  tmp[30] = product[46];
> +  tmp[31] = product[47];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d3 */
> +  tmp[0] = product[52];
> +  tmp[1] = product[53];
> +  tmp[2] = product[54];
> +  tmp[3] = product[55];
> +  tmp[4] = product[56];
> +  tmp[5] = product[57];
> +  tmp[6] = product[58];
> +  tmp[7] = product[59];
> +  tmp[8] = product[60];
> +  tmp[9] = product[61];
> +  tmp[10] = product[62];
> +  tmp[11] = product[63];
> +  tmp[12] = product[32];
> +  tmp[13] = product[33];
> +  tmp[14] = product[34];
> +  tmp[15] = product[35];
> +  tmp[16] = product[36];
> +  tmp[17] = product[37];
> +  tmp[18] = product[38];
> +  tmp[19] = product[39];
> +  tmp[20] = product[40];
> +  tmp[21] = product[41];
> +  tmp[22] = product[42];
> +  tmp[23] = product[43];
> +  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> +  tmp[28] = product[48];
> +  tmp[29] = product[49];
> +  tmp[30] = product[50];
> +  tmp[31] = product[51];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d4 */
> +  tmp[0] = product[56];
> +  tmp[1] = product[57];
> +  tmp[2] = product[58];
> +  tmp[3] = product[59];
> +  tmp[4] = product[60];
> +  tmp[5] = product[61];
> +  tmp[6] = product[62];
> +  tmp[7] = product[63];
> +  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> +  tmp[12] = product[36];
> +  tmp[13] = product[37];
> +  tmp[14] = product[38];
> +  tmp[15] = product[39];
> +  tmp[16] = product[40];
> +  tmp[17] = product[41];
> +  tmp[18] = product[42];
> +  tmp[19] = product[43];
> +  tmp[20] = product[44];
> +  tmp[21] = product[45];
> +  tmp[22] = product[46];
> +  tmp[23] = product[47];
> +  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> +  tmp[28] = product[52];
> +  tmp[29] = product[53];
> +  tmp[30] = product[54];
> +  tmp[31] = product[55];
> +  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
> +                               num_words_secp256r1);
> +    } while (carry < 0);
> +  } else {
> +    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> +                                           num_words_secp256r1) != 1) {
> +      carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> +                               num_words_secp256r1);
>       }
> -    if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> -    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1);  // nul-terminate
> -    fclose(fp);
>     }
> -  (void) depth;
> -  (void) root;
> -  return (char *) b.buf;
>   }
> -
> -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> -                       const char *fullpath) {
> -  const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
> -  char *data = mg_ssi(fullpath, root, 0);
> -  mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> -  free(data);
> +#elif MG_UECC_WORD_SIZE == 4
> +static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) {
> +  uint32_t tmp[num_words_secp256r1];
> +  int carry;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp256r1);
> +
> +  /* s1 */
> +  tmp[0] = tmp[1] = tmp[2] = 0;
> +  tmp[3] = product[11];
> +  tmp[4] = product[12];
> +  tmp[5] = product[13];
> +  tmp[6] = product[14];
> +  tmp[7] = product[15];
> +  carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s2 */
> +  tmp[3] = product[12];
> +  tmp[4] = product[13];
> +  tmp[5] = product[14];
> +  tmp[6] = product[15];
> +  tmp[7] = 0;
> +  carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s3 */
> +  tmp[0] = product[8];
> +  tmp[1] = product[9];
> +  tmp[2] = product[10];
> +  tmp[3] = tmp[4] = tmp[5] = 0;
> +  tmp[6] = product[14];
> +  tmp[7] = product[15];
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s4 */
> +  tmp[0] = product[9];
> +  tmp[1] = product[10];
> +  tmp[2] = product[11];
> +  tmp[3] = product[13];
> +  tmp[4] = product[14];
> +  tmp[5] = product[15];
> +  tmp[6] = product[13];
> +  tmp[7] = product[8];
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* d1 */
> +  tmp[0] = product[11];
> +  tmp[1] = product[12];
> +  tmp[2] = product[13];
> +  tmp[3] = tmp[4] = tmp[5] = 0;
> +  tmp[6] = product[8];
> +  tmp[7] = product[10];
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d2 */
> +  tmp[0] = product[12];
> +  tmp[1] = product[13];
> +  tmp[2] = product[14];
> +  tmp[3] = product[15];
> +  tmp[4] = tmp[5] = 0;
> +  tmp[6] = product[9];
> +  tmp[7] = product[11];
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d3 */
> +  tmp[0] = product[13];
> +  tmp[1] = product[14];
> +  tmp[2] = product[15];
> +  tmp[3] = product[8];
> +  tmp[4] = product[9];
> +  tmp[5] = product[10];
> +  tmp[6] = 0;
> +  tmp[7] = product[12];
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d4 */
> +  tmp[0] = product[14];
> +  tmp[1] = product[15];
> +  tmp[2] = 0;
> +  tmp[3] = product[9];
> +  tmp[4] = product[10];
> +  tmp[5] = product[11];
> +  tmp[6] = 0;
> +  tmp[7] = product[13];
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> +                                     num_words_secp256r1);
> +    } while (carry < 0);
> +  } else {
> +    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> +                                           num_words_secp256r1) != 1) {
> +      carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> +                                     num_words_secp256r1);
> +    }
> +  }
>   }
>   #else
> -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> -                       const char *fullpath) {
> -  mg_http_reply(c, 501, NULL, "SSI not enabled");
> -  (void) root, (void) fullpath;
> +static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t *product) {
> +  uint64_t tmp[num_words_secp256r1];
> +  int carry;
> +
> +  /* t */
> +  mg_uecc_vli_set(result, product, num_words_secp256r1);
> +
> +  /* s1 */
> +  tmp[0] = 0;
> +  tmp[1] = product[5] & 0xffffffff00000000U;
> +  tmp[2] = product[6];
> +  tmp[3] = product[7];
> +  carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s2 */
> +  tmp[1] = product[6] << 32;
> +  tmp[2] = (product[6] >> 32) | (product[7] << 32);
> +  tmp[3] = product[7] >> 32;
> +  carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s3 */
> +  tmp[0] = product[4];
> +  tmp[1] = product[5] & 0xffffffff;
> +  tmp[2] = 0;
> +  tmp[3] = product[7];
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* s4 */
> +  tmp[0] = (product[4] >> 32) | (product[5] << 32);
> +  tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
> +  tmp[2] = product[7];
> +  tmp[3] = (product[6] >> 32) | (product[4] << 32);
> +  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> +
> +  /* d1 */
> +  tmp[0] = (product[5] >> 32) | (product[6] << 32);
> +  tmp[1] = (product[6] >> 32);
> +  tmp[2] = 0;
> +  tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d2 */
> +  tmp[0] = product[6];
> +  tmp[1] = product[7];
> +  tmp[2] = 0;
> +  tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d3 */
> +  tmp[0] = (product[6] >> 32) | (product[7] << 32);
> +  tmp[1] = (product[7] >> 32) | (product[4] << 32);
> +  tmp[2] = (product[4] >> 32) | (product[5] << 32);
> +  tmp[3] = (product[6] << 32);
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  /* d4 */
> +  tmp[0] = product[7];
> +  tmp[1] = product[4] & 0xffffffff00000000U;
> +  tmp[2] = product[5];
> +  tmp[3] = product[6] & 0xffffffff00000000U;
> +  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> +
> +  if (carry < 0) {
> +    do {
> +      carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> +                                     num_words_secp256r1);
> +    } while (carry < 0);
> +  } else {
> +    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> +                                           num_words_secp256r1) != 1) {
> +      carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> +                                     num_words_secp256r1);
> +    }
> +  }
>   }
> -#endif
> +#endif /* MG_UECC_WORD_SIZE */
> +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/str.c"
> +#endif /* MG_UECC_SUPPORTS_secp256r1 */
> +
> +#if MG_UECC_SUPPORTS_secp256k1
> +
> +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                                      mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> +static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> +                             MG_UECC_Curve curve);
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product);
>   #endif
>
> +static const struct MG_UECC_Curve_t curve_secp256k1 = {
> +    num_words_secp256k1,
> +    num_bytes_secp256k1,
> +    256, /* num_n_bits */
> +    {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
> +     BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
> +     BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> +     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> +    {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
> +     BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
> +     BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
> +     BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
> +
> +     BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
> +     BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
> +     BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
> +     BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
> +    {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> +     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
> +    &double_jacobian_secp256k1,
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +    &mod_sqrt_default,
> +#endif
> +    &x_side_secp256k1,
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +    &vli_mmod_fast_secp256k1
> +#endif
> +};
>
> -struct mg_str mg_str_s(const char *s) {
> -  struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
> -  return str;
> +MG_UECC_Curve mg_uecc_secp256k1(void) {
> +  return &curve_secp256k1;
>   }
>
> -struct mg_str mg_str_n(const char *s, size_t n) {
> -  struct mg_str str = {s, n};
> -  return str;
> -}
> +/* Double in place */
> +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                                      mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> +  /* t1 = X, t2 = Y, t3 = Z */
> +  mg_uecc_word_t t4[num_words_secp256k1];
> +  mg_uecc_word_t t5[num_words_secp256k1];
>
> -int mg_lower(const char *s) {
> -  int c = *s;
> -  if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> -  return c;
> -}
> +  if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
> +    return;
> +  }
>
> -int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
> -  int diff = 0;
> -  if (len > 0) do {
> -      diff = mg_lower(s1++) - mg_lower(s2++);
> -    } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> -  return diff;
> +  mg_uecc_vli_modSquare_fast(t5, Y1, curve);   /* t5 = y1^2 */
> +  mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */
> +  mg_uecc_vli_modSquare_fast(X1, X1, curve);   /* t1 = x1^2 */
> +  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = y1^4 */
> +  mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
> +
> +  mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
> +                     num_words_secp256k1); /* t2 = 2*x1^2 */
> +  mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
> +                     num_words_secp256k1); /* t2 = 3*x1^2 */
> +  if (mg_uecc_vli_testBit(Y1, 0)) {
> +    mg_uecc_word_t carry =
> +        mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
> +    mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> +    Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
> +  } else {
> +    mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> +  }
> +  /* t2 = 3/2*(x1^2) = B */
> +
> +  mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
> +  mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> +                     num_words_secp256k1); /* t1 = B^2 - A */
> +  mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> +                     num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
> +
> +  mg_uecc_vli_modSub(t4, t4, X1, curve->p,
> +                     num_words_secp256k1);     /* t4 = A - x3 */
> +  mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */
> +  mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
> +                     num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
> +}
> +
> +/* Computes result = x^3 + b. result must not overlap x. */
> +static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> +                             MG_UECC_Curve curve) {
> +  mg_uecc_vli_modSquare_fast(result, x, curve);       /* r = x^2 */
> +  mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
> +  mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> +                     num_words_secp256k1); /* r = x^3 + b */
> +}
> +
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
> +static void omega_mult_secp256k1(mg_uecc_word_t *result,
> +                                 const mg_uecc_word_t *right);
> +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> +                                    mg_uecc_word_t *product) {
> +  mg_uecc_word_t tmp[2 * num_words_secp256k1];
> +  mg_uecc_word_t carry;
> +
> +  mg_uecc_vli_clear(tmp, num_words_secp256k1);
> +  mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
> +
> +  omega_mult_secp256k1(tmp,
> +                       product + num_words_secp256k1); /* (Rq, q) = q * c */
> +
> +  carry = mg_uecc_vli_add(result, product, tmp,
> +                          num_words_secp256k1); /* (C, r) = r + q       */
> +  mg_uecc_vli_clear(product, num_words_secp256k1);
> +  omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */
> +  carry += mg_uecc_vli_add(result, result, product,
> +                           num_words_secp256k1); /* (C1, r) = r + Rq*c */
> +
> +  while (carry > 0) {
> +    --carry;
> +    mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1);
> +  }
> +  if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) >
> +      0) {
> +    mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1);
> +  }
>   }
>
> -int mg_casecmp(const char *s1, const char *s2) {
> -  return mg_ncasecmp(s1, s2, (size_t) ~0);
> +#if MG_UECC_WORD_SIZE == 1
> +static void omega_mult_secp256k1(uint8_t *result, const uint8_t *right) {
> +  /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> +  mg_uecc_word_t r0 = 0;
> +  mg_uecc_word_t r1 = 0;
> +  mg_uecc_word_t r2 = 0;
> +  wordcount_t k;
> +
> +  /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> +  muladd(0xD1, right[0], &r0, &r1, &r2);
> +  result[0] = r0;
> +  r0 = r1;
> +  r1 = r2;
> +  /* r2 is still 0 */
> +
> +  for (k = 1; k < num_words_secp256k1; ++k) {
> +    muladd(0x03, right[k - 1], &r0, &r1, &r2);
> +    muladd(0xD1, right[k], &r0, &r1, &r2);
> +    result[k] = r0;
> +    r0 = r1;
> +    r1 = r2;
> +    r2 = 0;
> +  }
> +  muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
> +  result[num_words_secp256k1] = r0;
> +  result[num_words_secp256k1 + 1] = r1;
> +  /* add the 2^32 multiple */
> +  result[4 + num_words_secp256k1] =
> +      mg_uecc_vli_add(result + 4, result + 4, right, num_words_secp256k1);
> +}
> +#elif MG_UECC_WORD_SIZE == 4
> +static void omega_mult_secp256k1(uint32_t *result, const uint32_t *right) {
> +  /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> +  uint32_t carry = 0;
> +  wordcount_t k;
> +
> +  for (k = 0; k < num_words_secp256k1; ++k) {
> +    uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
> +    result[k] = (uint32_t) p;
> +    carry = p >> 32;
> +  }
> +  result[num_words_secp256k1] = carry;
> +  /* add the 2^32 multiple */
> +  result[1 + num_words_secp256k1] =
> +      mg_uecc_vli_add(result + 1, result + 1, right, num_words_secp256k1);
>   }
> -
> -int mg_vcmp(const struct mg_str *s1, const char *s2) {
> -  size_t n2 = strlen(s2), n1 = s1->len;
> -  int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
> -  if (r == 0) return (int) (n1 - n2);
> -  return r;
> +#else
> +static void omega_mult_secp256k1(uint64_t *result, const uint64_t *right) {
> +  mg_uecc_word_t r0 = 0;
> +  mg_uecc_word_t r1 = 0;
> +  mg_uecc_word_t r2 = 0;
> +  wordcount_t k;
> +
> +  /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> +  for (k = 0; k < num_words_secp256k1; ++k) {
> +    muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
> +    result[k] = r0;
> +    r0 = r1;
> +    r1 = r2;
> +    r2 = 0;
> +  }
> +  result[num_words_secp256k1] = r0;
>   }
> +#endif /* MG_UECC_WORD_SIZE */
> +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&  && !asm_mmod_fast_secp256k1) */
>
> -int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
> -  size_t n2 = strlen(str2), n1 = str1->len;
> -  int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
> -  if (r == 0) return (int) (n1 - n2);
> -  return r;
> +#endif /* MG_UECC_SUPPORTS_secp256k1 */
> +
> +#endif /* _UECC_CURVE_SPECIFIC_H_ */
> +
> +/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
> +#define EccPoint_isZero(point, curve) \
> +  mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words * 2))
> +
> +/* Point multiplication algorithm using Montgomery's ladder with co-Z
> +coordinates. From http://eprint.iacr.org/2011/338.pdf
> +*/
> +
> +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
> +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                    const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
> +  mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
> +
> +  mg_uecc_vli_modSquare_fast(t1, Z, curve);    /* z^2 */
> +  mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
> +  mg_uecc_vli_modMult_fast(t1, t1, Z, curve);  /* z^3 */
> +  mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
>   }
>
> -struct mg_str mg_strdup(const struct mg_str s) {
> -  struct mg_str r = {NULL, 0};
> -  if (s.len > 0 && s.ptr != NULL) {
> -    char *sc = (char *) calloc(1, s.len + 1);
> -    if (sc != NULL) {
> -      memcpy(sc, s.ptr, s.len);
> -      sc[s.len] = '\0';
> -      r.ptr = sc;
> -      r.len = s.len;
> -    }
> +/* P = (x1, y1) => 2P, (x2, y2) => P' */
> +static void XYcZ_initial_double(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                                mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> +                                const mg_uecc_word_t *const initial_Z,
> +                                MG_UECC_Curve curve) {
> +  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> +  wordcount_t num_words = curve->num_words;
> +  if (initial_Z) {
> +    mg_uecc_vli_set(z, initial_Z, num_words);
> +  } else {
> +    mg_uecc_vli_clear(z, num_words);
> +    z[0] = 1;
>     }
> -  return r;
> -}
>
> -int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> -  size_t i = 0;
> -  while (i < str1.len && i < str2.len) {
> -    int c1 = str1.ptr[i];
> -    int c2 = str2.ptr[i];
> -    if (c1 < c2) return -1;
> -    if (c1 > c2) return 1;
> -    i++;
> +  mg_uecc_vli_set(X2, X1, num_words);
> +  mg_uecc_vli_set(Y2, Y1, num_words);
> +
> +  apply_z(X1, Y1, z, curve);
> +  curve->double_jacobian(X1, Y1, z, curve);
> +  apply_z(X2, Y2, z, curve);
> +}
> +
> +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> +   Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
> +   or P => P', Q => P + Q
> +*/
> +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, mg_uecc_word_t *X2,
> +                     mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
> +  /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> +  mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> +  wordcount_t num_words = curve->num_words;
> +
> +  mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
> +  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = (x2 - x1)^2 = A */
> +  mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> +  mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> +  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
> +  mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */
> +
> +  mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */
> +  mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */
> +  mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */
> +  mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve);         /* t2 = y1*(C - B) */
> +  mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */
> +  mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */
> +  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
> +
> +  mg_uecc_vli_set(X2, t5, num_words);
> +}
> +
> +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> +   Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
> +   or P => P - Q, Q => P + Q
> +*/
> +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> +                      mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> +                      MG_UECC_Curve curve) {
> +  /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> +  mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> +  mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
> +  wordcount_t num_words = curve->num_words;
> +
> +  mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
> +  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = (x2 - x1)^2 = A */
> +  mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> +  mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> +  mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */
> +  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
> +
> +  mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */
> +  mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */
> +  mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */
> +  mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */
> +  mg_uecc_vli_modSub(X2, X2, t6, curve->p,
> +                     num_words); /* t3 = D - (B + C) = x3 */
> +
> +  mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */
> +  mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */
> +  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
> +                     num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
> +
> +  mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */
> +  mg_uecc_vli_modSub(t7, t7, t6, curve->p,
> +                     num_words); /* t7 = F - (B + C) = x3' */
> +  mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */
> +  mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */
> +  mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
> +                     num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
> +
> +  mg_uecc_vli_set(X1, t7, num_words);
> +}
> +
> +/* result may overlap point. */
> +static void EccPoint_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
> +                          const mg_uecc_word_t *scalar,
> +                          const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
> +                          MG_UECC_Curve curve) {
> +  /* R0 and R1 */
> +  mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> +  bitcount_t i;
> +  mg_uecc_word_t nb;
> +  wordcount_t num_words = curve->num_words;
> +
> +  mg_uecc_vli_set(Rx[1], point, num_words);
> +  mg_uecc_vli_set(Ry[1], point + num_words, num_words);
> +
> +  XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
> +
> +  for (i = num_bits - 2; i > 0; --i) {
> +    nb = !mg_uecc_vli_testBit(scalar, i);
> +    XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> +    XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> +  }
> +
> +  nb = !mg_uecc_vli_testBit(scalar, 0);
> +  XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> +
> +  /* Find final 1/Z value. */
> +  mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */
> +  mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve);        /* Yb * (X1 - X0) */
> +  mg_uecc_vli_modMult_fast(z, z, point, curve);  /* xP * Yb * (X1 - X0) */
> +  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */
> +  /* yP / (xP * Yb * (X1 - X0)) */
> +  mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
> +  mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
> +                           curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
> +  /* End 1/Z calculation */
> +
> +  XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> +  apply_z(Rx[0], Ry[0], z, curve);
> +
> +  mg_uecc_vli_set(result, Rx[0], num_words);
> +  mg_uecc_vli_set(result + num_words, Ry[0], num_words);
> +}
> +
> +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
> +                                   mg_uecc_word_t *k0, mg_uecc_word_t *k1,
> +                                   MG_UECC_Curve curve) {
> +  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> +  bitcount_t num_n_bits = curve->num_n_bits;
> +  mg_uecc_word_t carry =
> +      mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
> +      (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8) &&
> +       mg_uecc_vli_testBit(k0, num_n_bits));
> +  mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
> +  return carry;
> +}
> +
> +/* Generates a random integer in the range 0 < random < top.
> +   Both random and top have num_words words. */
> +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random,
> +                                                const mg_uecc_word_t *top,
> +                                                wordcount_t num_words) {
> +  mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> +  mg_uecc_word_t tries;
> +  bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
> +
> +  if (!g_rng_function) {
> +    return 0;
> +  }
> +
> +  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> +    if (!g_rng_function((uint8_t *) random,
> +                        (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
> +      return 0;
> +    }
> +    random[num_words - 1] &=
> +        mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits));
> +    if (!mg_uecc_vli_isZero(random, num_words) &&
> +        mg_uecc_vli_cmp(top, random, num_words) == 1) {
> +      return 1;
> +    }
>     }
> -  if (i < str1.len) return 1;
> -  if (i < str2.len) return -1;
>     return 0;
>   }
>
> -const char *mg_strstr(const struct mg_str haystack,
> -                      const struct mg_str needle) {
> -  size_t i;
> -  if (needle.len > haystack.len) return NULL;
> -  for (i = 0; i <= haystack.len - needle.len; i++) {
> -    if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
> -      return haystack.ptr + i;
> +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t *result,
> +                                                  mg_uecc_word_t *private_key,
> +                                                  MG_UECC_Curve curve) {
> +  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> +  mg_uecc_word_t *initial_Z = 0;
> +  mg_uecc_word_t carry;
> +
> +  /* Regularize the bitcount for the private key so that attackers cannot use a
> +     side channel attack to learn the number of leading zeros. */
> +  carry = regularize_k(private_key, tmp1, tmp2, curve);
> +
> +  /* If an RNG function was specified, try to get a random initial Z value to
> +     improve protection against side-channel attacks. */
> +  if (g_rng_function) {
> +    if (!mg_uecc_generate_random_int(p2[carry], curve->p, curve->num_words)) {
> +      return 0;
>       }
> +    initial_Z = p2[carry];
>     }
> -  return NULL;
> +  EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
> +                (bitcount_t) (curve->num_n_bits + 1), curve);
> +
> +  if (EccPoint_isZero(result, curve)) {
> +    return 0;
> +  }
> +  return 1;
>   }
>
> -static bool is_space(int c) {
> -  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
> +#if MG_UECC_WORD_SIZE == 1
> +
> +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
> +                                               const uint8_t *native) {
> +  wordcount_t i;
> +  for (i = 0; i < num_bytes; ++i) {
> +    bytes[i] = native[(num_bytes - 1) - i];
> +  }
>   }
>
> -struct mg_str mg_strstrip(struct mg_str s) {
> -  while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
> -  while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
> -  return s;
> +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
> +                                               const uint8_t *bytes,
> +                                               int num_bytes) {
> +  mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
>   }
>
> -bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
> -  size_t i = 0, j = 0, ni = 0, nj = 0;
> -  if (caps) caps->ptr = NULL, caps->len = 0;
> -  while (i < p.len || j < s.len) {
> -    if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) {
> -      if (caps == NULL) {
> -      } else if (p.ptr[i] == '?') {
> -        caps->ptr = &s.ptr[j], caps->len = 1;     // Finalize `?` cap
> -        caps++, caps->ptr = NULL, caps->len = 0;  // Init next cap
> -      } else if (caps->ptr != NULL && caps->len == 0) {
> -        caps->len = (size_t) (&s.ptr[j] - caps->ptr);  // Finalize current cap
> -        caps++, caps->len = 0, caps->ptr = NULL;       // Init next cap
> -      }
> -      i++, j++;
> -    } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
> -      if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];  // Init cap
> -      ni = i++, nj = j + 1;
> -    } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) {
> -      i = ni, j = nj;
> -      if (caps && caps->ptr == NULL && caps->len == 0) {
> -        caps--, caps->len = 0;  // Restart previous cap
> -      }
> -    } else {
> -      return false;
> -    }
> -  }
> -  if (caps && caps->ptr && caps->len == 0) {
> -    caps->len = (size_t) (&s.ptr[j] - caps->ptr);
> +#else
> +
> +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
> +                                               const mg_uecc_word_t *native) {
> +  int i;
> +  for (i = 0; i < num_bytes; ++i) {
> +    unsigned b = (unsigned) (num_bytes - 1 - i);
> +    bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
> +                          (8 * (b % MG_UECC_WORD_SIZE)));
>     }
> -  return true;
>   }
>
> -bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
> -  return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
> +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native,
> +                                               const uint8_t *bytes,
> +                                               int num_bytes) {
> +  int i;
> +  mg_uecc_vli_clear(native,
> +                    (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
> +                                   MG_UECC_WORD_SIZE));
> +  for (i = 0; i < num_bytes; ++i) {
> +    unsigned b = (unsigned) (num_bytes - 1 - i);
> +    native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
> +                                     << (8 * (b % MG_UECC_WORD_SIZE));
> +  }
>   }
>
> -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff,
> -                     size_t *klen, size_t *voff, size_t *vlen, char delim) {
> -  size_t kvlen, kl;
> -  for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;) kvlen++;
> -  for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
> -  if (koff != NULL) *koff = ofs;
> -  if (klen != NULL) *klen = kl;
> -  if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
> -  if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
> -  ofs += kvlen + 1;
> -  return ofs > n ? n : ofs;
> +#endif /* MG_UECC_WORD_SIZE */
> +
> +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> +                     MG_UECC_Curve curve) {
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> +  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> +#else
> +  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> +#endif
> +  mg_uecc_word_t tries;
> +
> +  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> +    if (!mg_uecc_generate_random_int(_private, curve->n,
> +                                     BITS_TO_WORDS(curve->num_n_bits))) {
> +      return 0;
> +    }
> +
> +    if (EccPoint_compute_public_key(_public, _private, curve)) {
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +      mg_uecc_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits),
> +                                _private);
> +      mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> +      mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes,
> +                                _public + curve->num_words);
> +#endif
> +      return 1;
> +    }
> +  }
> +  return 0;
>   }
>
> -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char sep) {
> -  size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
> -  if (s->ptr == NULL || s->len == 0) return 0;
> -  off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
> -  if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
> -  if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
> -  *s = mg_str_n(s->ptr + off, s->len - off);
> -  return off > 0;
> +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
> +                          uint8_t *secret, MG_UECC_Curve curve) {
> +  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> +  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> +
> +  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t *p2[2] = {_private, tmp};
> +  mg_uecc_word_t *initial_Z = 0;
> +  mg_uecc_word_t carry;
> +  wordcount_t num_words = curve->num_words;
> +  wordcount_t num_bytes = curve->num_bytes;
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) _private, private_key, num_bytes);
> +  bcopy((uint8_t *) _public, public_key, num_bytes * 2);
> +#else
> +  mg_uecc_vli_bytesToNative(_private, private_key,
> +                            BITS_TO_BYTES(curve->num_n_bits));
> +  mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
> +  mg_uecc_vli_bytesToNative(_public + num_words, public_key + num_bytes,
> +                            num_bytes);
> +#endif
> +
> +  /* Regularize the bitcount for the private key so that attackers cannot use a
> +     side channel attack to learn the number of leading zeros. */
> +  carry = regularize_k(_private, _private, tmp, curve);
> +
> +  /* If an RNG function was specified, try to get a random initial Z value to
> +     improve protection against side-channel attacks. */
> +  if (g_rng_function) {
> +    if (!mg_uecc_generate_random_int(p2[carry], curve->p, num_words)) {
> +      return 0;
> +    }
> +    initial_Z = p2[carry];
> +  }
> +
> +  EccPoint_mult(_public, _public, p2[!carry], initial_Z,
> +                (bitcount_t) (curve->num_n_bits + 1), curve);
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
> +#else
> +  mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
> +#endif
> +  return !EccPoint_isZero(_public, curve);
>   }
>
> -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) {
> -  return mg_split(s, k, v, ',');
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
> +                      MG_UECC_Curve curve) {
> +  wordcount_t i;
> +  for (i = 0; i < curve->num_bytes; ++i) {
> +    compressed[i + 1] = public_key[i];
> +  }
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
> +#else
> +  compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
> +#endif
>   }
>
> -char *mg_hex(const void *buf, size_t len, char *to) {
> -  const unsigned char *p = (const unsigned char *) buf;
> -  const char *hex = "0123456789abcdef";
> -  size_t i = 0;
> -  for (; len--; p++) {
> -    to[i++] = hex[p[0] >> 4];
> -    to[i++] = hex[p[0] & 0x0f];
> +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
> +                        MG_UECC_Curve curve) {
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
> +#else
> +  mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
> +#endif
> +  mg_uecc_word_t *y = point + curve->num_words;
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy(public_key, compressed + 1, curve->num_bytes);
> +#else
> +  mg_uecc_vli_bytesToNative(point, compressed + 1, curve->num_bytes);
> +#endif
> +  curve->x_side(y, point, curve);
> +  curve->mod_sqrt(y, curve);
> +
> +  if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
> +    mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
>     }
> -  to[i] = '\0';
> -  return to;
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +  mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
> +  mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y);
> +#endif
>   }
> +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> +
> +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t *point,
> +                                        MG_UECC_Curve curve) {
> +  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> +  wordcount_t num_words = curve->num_words;
> +
> +  /* The point at infinity is invalid. */
> +  if (EccPoint_isZero(point, curve)) {
> +    return 0;
> +  }
>
> -static unsigned char mg_unhex_nimble(unsigned char c) {
> -  return (c >= '0' && c <= '9')   ? (unsigned char) (c - '0')
> -         : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
> -                                  : (unsigned char) (c - 'W');
> +  /* x and y must be smaller than p. */
> +  if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
> +      mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) {
> +    return 0;
> +  }
> +
> +  mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
> +  curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
> +
> +  /* Make sure that y^2 == x^3 + ax + b */
> +  return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
>   }
>
> -unsigned long mg_unhexn(const char *s, size_t len) {
> -  unsigned long i = 0, v = 0;
> -  for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]);
> -  return v;
> +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve) {
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> +#else
> +  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> +#endif
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +  mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> +  mg_uecc_vli_bytesToNative(_public + curve->num_words,
> +                            public_key + curve->num_bytes, curve->num_bytes);
> +#endif
> +  return mg_uecc_valid_point(_public, curve);
>   }
>
> -void mg_unhex(const char *buf, size_t len, unsigned char *to) {
> -  size_t i;
> -  for (i = 0; i < len; i += 2) {
> -    to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
> +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key,
> +                               MG_UECC_Curve curve) {
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> +  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> +#else
> +  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> +#endif
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +  mg_uecc_vli_bytesToNative(_private, private_key,
> +                            BITS_TO_BYTES(curve->num_n_bits));
> +#endif
> +
> +  /* Make sure the private key is in the range [1, n-1]. */
> +  if (mg_uecc_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) {
> +    return 0;
>     }
> -}
>
> -uint64_t mg_tou64(struct mg_str str) {
> -  uint64_t result = 0;
> -  size_t i = 0;
> -  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
> -  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> -    result *= 10;
> -    result += (unsigned) (str.ptr[i] - '0');
> -    i++;
> +  if (mg_uecc_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) !=
> +      1) {
> +    return 0;
>     }
> -  return result;
> +
> +  /* Compute public key. */
> +  if (!EccPoint_compute_public_key(_public, _private, curve)) {
> +    return 0;
> +  }
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +  mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> +  mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes,
> +                            _public + curve->num_words);
> +#endif
> +  return 1;
>   }
>
> -int64_t mg_to64(struct mg_str str) {
> -  int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
> -  size_t i = 0;
> -  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
> -  if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
> -  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> -    if (result > max) return 0;
> -    result *= 10;
> -    result += (str.ptr[i] - '0');
> -    i++;
> +/* -------- ECDSA code -------- */
> +
> +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
> +                     unsigned bits_size, MG_UECC_Curve curve) {
> +  unsigned num_n_bytes = (unsigned) BITS_TO_BYTES(curve->num_n_bits);
> +  unsigned num_n_words = (unsigned) BITS_TO_WORDS(curve->num_n_bits);
> +  int shift;
> +  mg_uecc_word_t carry;
> +  mg_uecc_word_t *ptr;
> +
> +  if (bits_size > num_n_bytes) {
> +    bits_size = num_n_bytes;
> +  }
> +
> +  mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) native, bits, bits_size);
> +#else
> +  mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
> +#endif
> +  if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
> +    return;
> +  }
> +  shift = (int) bits_size * 8 - curve->num_n_bits;
> +  carry = 0;
> +  ptr = native + num_n_words;
> +  while (ptr-- > native) {
> +    mg_uecc_word_t temp = *ptr;
> +    *ptr = (temp >> shift) | carry;
> +    carry = temp << (MG_UECC_WORD_BITS - shift);
> +  }
> +
> +  /* Reduce mod curve_n */
> +  if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t) num_n_words) !=
> +      1) {
> +    mg_uecc_vli_sub(native, native, curve->n, (wordcount_t) num_n_words);
>     }
> -  return result * neg;
>   }
>
> -char *mg_remove_double_dots(char *s) {
> -  char *saved = s, *p = s;
> -  while (*s != '\0') {
> -    *p++ = *s++;
> -    if (s[-1] == '/' || s[-1] == '\\') {
> -      while (s[0] != '\0') {
> -        if (s[0] == '/' || s[0] == '\\') {
> -          s++;
> -        } else if (s[0] == '.' && s[1] == '.' &&
> -                   (s[2] == '/' || s[2] == '\\')) {
> -          s += 2;
> -        } else {
> -          break;
> -        }
> -      }
> +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
> +                                        const uint8_t *message_hash,
> +                                        unsigned hash_size, mg_uecc_word_t *k,
> +                                        uint8_t *signature,
> +                                        MG_UECC_Curve curve) {
> +  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t s[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t *k2[2] = {tmp, s};
> +  mg_uecc_word_t *initial_Z = 0;
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
> +#else
> +  mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
> +#endif
> +  mg_uecc_word_t carry;
> +  wordcount_t num_words = curve->num_words;
> +  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> +  bitcount_t num_n_bits = curve->num_n_bits;
> +
> +  /* Make sure 0 < k < curve_n */
> +  if (mg_uecc_vli_isZero(k, num_words) ||
> +      mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
> +    return 0;
> +  }
> +
> +  carry = regularize_k(k, tmp, s, curve);
> +  /* If an RNG function was specified, try to get a random initial Z value to
> +     improve protection against side-channel attacks. */
> +  if (g_rng_function) {
> +    if (!mg_uecc_generate_random_int(k2[carry], curve->p, num_words)) {
> +      return 0;
>       }
> +    initial_Z = k2[carry];
> +  }
> +  EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
> +                (bitcount_t) (num_n_bits + 1), curve);
> +  if (mg_uecc_vli_isZero(p, num_words)) {
> +    return 0;
>     }
> -  *p = '\0';
> -  return saved;
> -}
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/timer.c"
> +  /* If an RNG function was specified, get a random number
> +     to prevent side channel analysis of k. */
> +  if (!g_rng_function) {
> +    mg_uecc_vli_clear(tmp, num_n_words);
> +    tmp[0] = 1;
> +  } else if (!mg_uecc_generate_random_int(tmp, curve->n, num_n_words)) {
> +    return 0;
> +  }
> +
> +  /* Prevent side channel analysis of mg_uecc_vli_modInv() to determine
> +     bits of k / the private key by premultiplying by a random number */
> +  mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */
> +  mg_uecc_vli_modInv(k, k, curve->n, num_n_words);       /* k = 1 / k' */
> +  mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */
> +
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> +  mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */
>   #endif
>
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits));
> +#else
> +  mg_uecc_vli_bytesToNative(tmp, private_key,
> +                            BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
> +#endif
>
> +  s[num_n_words - 1] = 0;
> +  mg_uecc_vli_set(s, p, num_words);
> +  mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */
>
> -#define MG_TIMER_CALLED 4
> +  bits2int(tmp, message_hash, hash_size, curve);
> +  mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */
> +  mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words);  /* s = (e + r*d) / k */
> +  if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t) curve->num_bytes * 8) {
> +    return 0;
> +  }
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
> +        curve->num_bytes);
> +#else
> +  mg_uecc_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s);
> +#endif
> +  return 1;
> +}
>
> -void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
> -                   unsigned flags, void (*fn)(void *), void *arg) {
> -  t->id = 0, t->period_ms = ms, t->expire = 0;
> -  t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> -  *head = t;
> +#if 0
> +/* For testing - sign with an explicitly specified k value */
> +int mg_uecc_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash,
> +                     unsigned hash_size, const uint8_t *k, uint8_t *signature,
> +                     MG_UECC_Curve curve) {
> +  mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
> +  bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits), curve);
> +  return mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k2,
> +                                   signature, curve);
>   }
> +#endif
>
> -void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> -  while (*head && *head != t) head = &(*head)->next;
> -  if (*head) *head = t->next;
> +int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash,
> +                 unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
> +  mg_uecc_word_t k[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tries;
> +
> +  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> +    if (!mg_uecc_generate_random_int(k, curve->n,
> +                                     BITS_TO_WORDS(curve->num_n_bits))) {
> +      return 0;
> +    }
> +
> +    if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k,
> +                                     signature, curve)) {
> +      return 1;
> +    }
> +  }
> +  return 0;
>   }
>
> -// t: expiration time, prd: period, now: current time. Return true if expired
> -bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> -  if (now + prd < *t) *t = 0;                    // Time wrapped? Reset timer
> -  if (*t == 0) *t = now + prd;                   // Firt poll? Set expiration
> -  if (*t > now) return false;                    // Not expired yet, return
> -  *t = (now - *t) > prd ? now + prd : *t + prd;  // Next expiration time
> -  return true;                                   // Expired, return true
> +/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always
> +   the same size as the hash result size. */
> +static void HMAC_init(const MG_UECC_HashContext *hash_context,
> +                      const uint8_t *K) {
> +  uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> +  unsigned i;
> +  for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x36;
> +  for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
> +
> +  hash_context->init_hash(hash_context);
> +  hash_context->update_hash(hash_context, pad, hash_context->block_size);
>   }
>
> -void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> -  struct mg_timer *t, *tmp;
> -  for (t = *head; t != NULL; t = tmp) {
> -    bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> -                !(t->flags & MG_TIMER_CALLED);  // Handle MG_TIMER_NOW only once
> -    bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> -    tmp = t->next;
> -    if (!once && !expired) continue;
> -    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
> -      t->fn(t->arg);
> +static void HMAC_update(const MG_UECC_HashContext *hash_context,
> +                        const uint8_t *message, unsigned message_size) {
> +  hash_context->update_hash(hash_context, message, message_size);
> +}
> +
> +static void HMAC_finish(const MG_UECC_HashContext *hash_context,
> +                        const uint8_t *K, uint8_t *result) {
> +  uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> +  unsigned i;
> +  for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x5c;
> +  for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
> +
> +  hash_context->finish_hash(hash_context, result);
> +
> +  hash_context->init_hash(hash_context);
> +  hash_context->update_hash(hash_context, pad, hash_context->block_size);
> +  hash_context->update_hash(hash_context, result, hash_context->result_size);
> +  hash_context->finish_hash(hash_context, result);
> +}
> +
> +/* V = HMAC_K(V) */
> +static void update_V(const MG_UECC_HashContext *hash_context, uint8_t *K,
> +                     uint8_t *V) {
> +  HMAC_init(hash_context, K);
> +  HMAC_update(hash_context, V, hash_context->result_size);
> +  HMAC_finish(hash_context, K, V);
> +}
> +
> +/* Deterministic signing, similar to RFC 6979. Differences are:
> +    * We just use H(m) directly rather than bits2octets(H(m))
> +      (it is not reduced modulo curve_n).
> +    * We generate a value for k (aka T) directly rather than converting
> +   endianness.
> +
> +   Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped 0x00 or 0x01) /
> +   <HMAC pad> */
> +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> +                               const uint8_t *message_hash, unsigned hash_size,
> +                               const MG_UECC_HashContext *hash_context,
> +                               uint8_t *signature, MG_UECC_Curve curve) {
> +  uint8_t *K = hash_context->tmp;
> +  uint8_t *V = K + hash_context->result_size;
> +  wordcount_t num_bytes = curve->num_bytes;
> +  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> +  bitcount_t num_n_bits = curve->num_n_bits;
> +  mg_uecc_word_t tries;
> +  unsigned i;
> +  for (i = 0; i < hash_context->result_size; ++i) {
> +    V[i] = 0x01;
> +    K[i] = 0;
> +  }
> +
> +  /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
> +  HMAC_init(hash_context, K);
> +  V[hash_context->result_size] = 0x00;
> +  HMAC_update(hash_context, V, hash_context->result_size + 1);
> +  HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> +  HMAC_update(hash_context, message_hash, hash_size);
> +  HMAC_finish(hash_context, K, K);
> +
> +  update_V(hash_context, K, V);
> +
> +  /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
> +  HMAC_init(hash_context, K);
> +  V[hash_context->result_size] = 0x01;
> +  HMAC_update(hash_context, V, hash_context->result_size + 1);
> +  HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> +  HMAC_update(hash_context, message_hash, hash_size);
> +  HMAC_finish(hash_context, K, K);
> +
> +  update_V(hash_context, K, V);
> +
> +  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> +    mg_uecc_word_t T[MG_UECC_MAX_WORDS];
> +    uint8_t *T_ptr = (uint8_t *) T;
> +    wordcount_t T_bytes = 0;
> +    for (;;) {
> +      update_V(hash_context, K, V);
> +      for (i = 0; i < hash_context->result_size; ++i) {
> +        T_ptr[T_bytes++] = V[i];
> +        if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
> +          goto filled;
> +        }
> +      }
> +    }
> +  filled:
> +    if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 > num_n_bits) {
> +      mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> +      T[num_n_words - 1] &=
> +          mask >>
> +          ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
> +    }
> +
> +    if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, T,
> +                                     signature, curve)) {
> +      return 1;
>       }
> -    t->flags |= MG_TIMER_CALLED;
> +
> +    /* K = HMAC_K(V || 0x00) */
> +    HMAC_init(hash_context, K);
> +    V[hash_context->result_size] = 0x00;
> +    HMAC_update(hash_context, V, hash_context->result_size + 1);
> +    HMAC_finish(hash_context, K, K);
> +
> +    update_V(hash_context, K, V);
>     }
> +  return 0;
>   }
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/tls_dummy.c"
> +static bitcount_t smax(bitcount_t a, bitcount_t b) {
> +  return (a > b ? a : b);
> +}
> +
> +int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash,
> +                   unsigned hash_size, const uint8_t *signature,
> +                   MG_UECC_Curve curve) {
> +  mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
> +  mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
> +  const mg_uecc_word_t *points[4];
> +  const mg_uecc_word_t *point;
> +  bitcount_t num_bits;
> +  bitcount_t i;
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> +#else
> +  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>   #endif
> +  mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
> +  wordcount_t num_words = curve->num_words;
> +  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>
> +  rx[num_n_words - 1] = 0;
> +  r[num_n_words - 1] = 0;
> +  s[num_n_words - 1] = 0;
>
> -#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL && !MG_ENABLE_CUSTOM_TLS
> -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> -  (void) opts;
> -  mg_error(c, "TLS is not enabled");
> -}
> -void mg_tls_handshake(struct mg_connection *c) {
> -  (void) c;
> -}
> -void mg_tls_free(struct mg_connection *c) {
> -  (void) c;
> -}
> -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> -  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> -}
> -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> -  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> -}
> -size_t mg_tls_pending(struct mg_connection *c) {
> -  (void) c;
> -  return 0;
> -}
> +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +  bcopy((uint8_t *) r, signature, curve->num_bytes);
> +  bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes);
> +#else
> +  mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> +  mg_uecc_vli_bytesToNative(_public + num_words, public_key + curve->num_bytes,
> +                            curve->num_bytes);
> +  mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
> +  mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes);
>   #endif
>
> -#ifdef MG_ENABLE_LINES
> -#line 1 "src/tls_mbed.c"
> -#endif
> +  /* r, s must not be 0. */
> +  if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s, num_words)) {
> +    return 0;
> +  }
>
> +  /* r, s must be < n. */
> +  if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
> +      mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
> +    return 0;
> +  }
>
> +  /* Calculate u1 and u2. */
> +  mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
> +  u1[num_n_words - 1] = 0;
> +  bits2int(u1, message_hash, hash_size, curve);
> +  mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */
> +  mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words);  /* u2 = r/s */
> +
> +  /* Calculate sum = G + Q. */
> +  mg_uecc_vli_set(sum, _public, num_words);
> +  mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
> +  mg_uecc_vli_set(tx, curve->G, num_words);
> +  mg_uecc_vli_set(ty, curve->G + num_words, num_words);
> +  mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */
> +  XYcZ_add(tx, ty, sum, sum + num_words, curve);
> +  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
> +  apply_z(sum, sum + num_words, z, curve);
> +
> +  /* Use Shamir's trick to calculate u1*G + u2*Q */
> +  points[0] = 0;
> +  points[1] = curve->G;
> +  points[2] = _public;
> +  points[3] = sum;
> +  num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
> +                  mg_uecc_vli_numBits(u2, num_n_words));
> +  point =
> +      points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
> +             ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
> +  mg_uecc_vli_set(rx, point, num_words);
> +  mg_uecc_vli_set(ry, point + num_words, num_words);
> +  mg_uecc_vli_clear(z, num_words);
> +  z[0] = 1;
> +
> +  for (i = num_bits - 2; i >= 0; --i) {
> +    mg_uecc_word_t index;
> +    curve->double_jacobian(rx, ry, z, curve);
> +
> +    index = (!!mg_uecc_vli_testBit(u1, i)) |
> +            (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
> +    point = points[index];
> +    if (point) {
> +      mg_uecc_vli_set(tx, point, num_words);
> +      mg_uecc_vli_set(ty, point + num_words, num_words);
> +      apply_z(tx, ty, z, curve);
> +      mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */
> +      XYcZ_add(tx, ty, rx, ry, curve);
> +      mg_uecc_vli_modMult_fast(z, z, tz, curve);
> +    }
> +  }
>
> +  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
> +  apply_z(rx, ry, z, curve);
>
> -#if MG_ENABLE_MBEDTLS
> +  /* v = x1 (mod n) */
> +  if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
> +    mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
> +  }
>
> -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
> -#define MGRNG , rng_get, NULL
> -#else
> -#define MGRNG
> -#endif
> +  /* Accept only if v == r. */
> +  return (int) (mg_uecc_vli_equal(rx, r, num_words));
> +}
>
> -void mg_tls_free(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  if (tls != NULL) {
> -    free(tls->cafile);
> -    mbedtls_ssl_free(&tls->ssl);
> -    mbedtls_pk_free(&tls->pk);
> -    mbedtls_x509_crt_free(&tls->ca);
> -    mbedtls_x509_crt_free(&tls->cert);
> -    mbedtls_ssl_config_free(&tls->conf);
> -    free(tls);
> -    c->tls = NULL;
> -  }
> +#if MG_UECC_ENABLE_VLI_API
> +
> +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
> +  return curve->num_words;
>   }
>
> -static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
> -  long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> -  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> -  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> -  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> -  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> -  return (int) n;
> +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
> +  return curve->num_bytes;
>   }
>
> -static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> -  long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> -  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> -  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> -  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> -  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> -  return (int) n;
> +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
> +  return curve->num_bytes * 8;
>   }
>
> -void mg_tls_handshake(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  int rc = mbedtls_ssl_handshake(&tls->ssl);
> -  if (rc == 0) {  // Success
> -    MG_DEBUG(("%lu success", c->id));
> -    c->is_tls_hs = 0;
> -    mg_call(c, MG_EV_TLS_HS, NULL);
> -  } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> -             rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
> -    MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
> -                c->is_tls_hs, rc, -rc));
> -  } else {
> -    mg_error(c, "TLS handshake: -%#x", -rc);  // Error
> -  }
> +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
> +  return BITS_TO_WORDS(curve->num_n_bits);
>   }
>
> -static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> -  mg_random(buf, len);
> -  (void) ctx;
> -  return 0;
> +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
> +  return BITS_TO_BYTES(curve->num_n_bits);
>   }
>
> -static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
> -  n = (int) strlen(s2) - 1;
> -  MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
> -  (void) s;
> +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
> +  return curve->num_n_bits;
>   }
>
> -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
> -static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
> -  (void) p_rng;
> -  mg_random(buf, len);
> -  return 0;
> +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
> +  return curve->p;
>   }
> -#endif
>
> -static struct mg_str mg_loadfile(struct mg_fs *fs, const char *path) {
> -  size_t n = 0;
> -  if (path[0] == '-') return mg_str(path);
> -  char *p = mg_file_read(fs, path, &n);
> -  return mg_str_n(p, n);
> +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
> +  return curve->n;
>   }
>
> -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> -  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> -  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> -  int rc = 0;
> -  c->tls = tls;
> -  if (c->tls == NULL) {
> -    mg_error(c, "TLS OOM");
> -    goto fail;
> -  }
> -  MG_DEBUG(("%lu Setting TLS", c->id));
> -  mbedtls_ssl_init(&tls->ssl);
> -  mbedtls_ssl_config_init(&tls->conf);
> -  mbedtls_x509_crt_init(&tls->ca);
> -  mbedtls_x509_crt_init(&tls->cert);
> -  mbedtls_pk_init(&tls->pk);
> -  mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> -#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> -  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
> +  return curve->G;
> +}
> +
> +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
> +  return curve->b;
> +}
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> +  curve->mod_sqrt(a, curve);
> +}
>   #endif
> -  if ((rc = mbedtls_ssl_config_defaults(
> -           &tls->conf,
> -           c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> -           MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> -    mg_error(c, "tls defaults %#x", -rc);
> -    goto fail;
> -  }
> -  mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
> -  if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
> -    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> -  } else if (opts->ca != NULL && opts->ca[0] != '\0') {
> -#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
> -    tls->cafile = strdup(opts->ca);
> -    rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL);
> -    if (rc != 0) {
> -      mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
> -      goto fail;
> -    }
> +
> +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product,
> +                           MG_UECC_Curve curve) {
> +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> +  curve->mmod_fast(result, product);
>   #else
> -    struct mg_str s = mg_loadfile(fs, opts->ca);
> -    rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len + 1);
> -    if (opts->ca[0] != '-') free((char *) s.ptr);
> -    if (rc != 0) {
> -      mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
> -      goto fail;
> -    }
> -    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> +  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>   #endif
> -    if (opts->srvname.len > 0) {
> -      char *x = mg_mprintf("%.*s", (int) opts->srvname.len, opts->srvname.ptr);
> -      mbedtls_ssl_set_hostname(&tls->ssl, x);
> -      free(x);
> -    }
> -    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
> -  }
> -  if (opts->cert != NULL && opts->cert[0] != '\0') {
> -    struct mg_str s = mg_loadfile(fs, opts->cert);
> -    const char *key = opts->certkey == NULL ? opts->cert : opts->certkey;
> -    rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr, s.len + 1);
> -    if (opts->cert[0] != '-') free((char *) s.ptr);
> -    if (rc != 0) {
> -      mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
> -      goto fail;
> -    }
> -    s = mg_loadfile(fs, key);
> -    rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len + 1, NULL,
> -                              0 MGRNG);
> -    if (key[0] != '-') free((char *) s.ptr);
> -    if (rc != 0) {
> -      mg_error(c, "tls key(%s) %#x", key, -rc);
> -      goto fail;
> -    }
> -    rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
> -    if (rc != 0) {
> -      mg_error(c, "own cert %#x", -rc);
> -      goto fail;
> -    }
> -  }
> -  if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> -    mg_error(c, "setup err %#x", -rc);
> -    goto fail;
> -  }
> -  c->tls = tls;
> -  c->is_tls = 1;
> -  c->is_tls_hs = 1;
> -  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> -  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> -    mg_tls_handshake(c);
> -  }
> -  return;
> -fail:
> -  mg_tls_free(c);
>   }
>
> -size_t mg_tls_pending(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> -}
> +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
> +                        const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
> +  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> +  mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> +  mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
>
> -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> -  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> -    return MG_IO_WAIT;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
> +  EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, curve);
>   }
>
> -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> -  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> -    return MG_IO_WAIT;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
> -}
> -#endif
> +#endif  /* MG_UECC_ENABLE_VLI_API */
> +#endif  // MG_TLS_BUILTIN
> +// End of uecc BSD-2
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "src/tls_openssl.c"
> +#line 1 "src/tls_x25519.c"
>   #endif
> +/**
> + * Adapted from STROBE: https://strobe.sourceforge.io/
> + * Copyright (c) 2015-2016 Cryptography Research, Inc.
> + * Author: Mike Hamburg
> + * License: MIT License
> + */
>
>
>
> -#if MG_ENABLE_OPENSSL
> -static int mg_tls_err(struct mg_tls *tls, int res) {
> -  int err = SSL_get_error(tls->ssl, res);
> -  // We've just fetched the last error from the queue.
> -  // Now we need to clear the error queue. If we do not, then the following
> -  // can happen (actually reported):
> -  //  - A new connection is accept()-ed with cert error (e.g. self-signed cert)
> -  //  - Since all accept()-ed connections share listener's context,
> -  //  - *ALL* SSL accepted connection report read error on the next poll cycle.
> -  //    Thus a single errored connection can close all the rest, unrelated ones.
> -  // Clearing the error keeps the shared SSL_CTX in an OK state.
> +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
>
> -  if (err != 0) ERR_print_errors_fp(stderr);
> -  ERR_clear_error();
> -  if (err == SSL_ERROR_WANT_READ) return 0;
> -  if (err == SSL_ERROR_WANT_WRITE) return 0;
> -  return err;
> -}
> +#define X25519_WBITS 32
>
> -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
> -  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> -  const char *id = "mongoose";
> -  static unsigned char s_initialised = 0;
> -  int rc;
> +typedef uint32_t limb_t;
> +typedef uint64_t dlimb_t;
> +typedef int64_t sdlimb_t;
>
> -  if (tls == NULL) {
> -    mg_error(c, "TLS OOM");
> -    goto fail;
> -  }
> +#define NLIMBS (256 / X25519_WBITS)
> +typedef limb_t mg_fe[NLIMBS];
>
> -  if (!s_initialised) {
> -    SSL_library_init();
> -    s_initialised++;
> -  }
> -  MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
> -            opts->ca == NULL ? "null" : opts->ca,
> -            opts->cert == NULL ? "null" : opts->cert,
> -            opts->certkey == NULL ? "null" : opts->certkey));
> -  tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> -                          : SSL_CTX_new(SSLv23_server_method());
> -  if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> -    mg_error(c, "SSL_new");
> -    goto fail;
> -  }
> -  SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> -                             (unsigned) strlen(id));
> -  // Disable deprecated protocols
> -  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> -  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> -  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> -  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> -#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> -  SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> -#endif
> -#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> -  SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> -#endif
> +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) {
> +  dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
> +  *carry = (limb_t) (tmp >> X25519_WBITS);
> +  return (limb_t) tmp;
> +}
>
> -  if (opts->ca != NULL && opts->ca[0] != '\0') {
> -    SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> -                   NULL);
> -    if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 1) {
> -      mg_error(c, "load('%s') %d err %d", opts->ca, rc, mg_tls_err(tls, rc));
> -      goto fail;
> -    }
> -  }
> -  if (opts->cert != NULL && opts->cert[0] != '\0') {
> -    const char *key = opts->certkey;
> -    if (key == NULL) key = opts->cert;
> -    if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) != 1) {
> -      mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
> -      goto fail;
> -    } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) != 1) {
> -      mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
> -      goto fail;
> -#if OPENSSL_VERSION_NUMBER > 0x10100000L
> -    } else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) !=
> -               1) {
> -      mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
> -      goto fail;
> -#endif
> -    } else {
> -      SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> -#if OPENSSL_VERSION_NUMBER > 0x10002000L
> -      SSL_set_ecdh_auto(tls->ssl, 1);
> -#endif
> -    }
> -  }
> -  if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, opts->ciphers);
> -#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> -  if (opts->srvname.len > 0) {
> -    char *s = mg_mprintf("%.*s", (int) opts->srvname.len, opts->srvname.ptr);
> -    SSL_set1_host(tls->ssl, s);
> -    free(s);
> -  }
> -#endif
> -  c->tls = tls;
> -  c->is_tls = 1;
> -  c->is_tls_hs = 1;
> -  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> -    mg_tls_handshake(c);
> -  }
> -  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
> -  return;
> -fail:
> -  c->is_closing = 1;
> -  free(tls);
> +// These functions are implemented in terms of umaal on ARM
> +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
> +  dlimb_t total = (dlimb_t) *carry + acc + mand;
> +  *carry = (limb_t) (total >> X25519_WBITS);
> +  return (limb_t) total;
> +}
> +
> +static limb_t adc0(limb_t *carry, limb_t acc) {
> +  dlimb_t total = (dlimb_t) *carry + acc;
> +  *carry = (limb_t) (total >> X25519_WBITS);
> +  return (limb_t) total;
> +}
> +
> +// - Precondition: carry is small.
> +// - Invariant: result of propagate is < 2^255 + 1 word
> +// - In particular, always less than 2p.
> +// - Also, output x >= min(x,19)
> +static void propagate(mg_fe x, limb_t over) {
> +  unsigned i;
> +  limb_t carry;
> +  over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
> +  x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
> +
> +  carry = over * 19;
> +  for (i = 0; i < NLIMBS; i++) {
> +    x[i] = adc0(&carry, x[i]);
> +  }
>   }
>
> -void mg_tls_handshake(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  int rc;
> -  SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
> -  rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
> -  if (rc == 1) {
> -    MG_DEBUG(("%lu success", c->id));
> -    c->is_tls_hs = 0;
> -    mg_call(c, MG_EV_TLS_HS, NULL);
> -  } else {
> -    int code = mg_tls_err(tls, rc);
> -    if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
> +static void add(mg_fe out, const mg_fe a, const mg_fe b) {
> +  unsigned i;
> +  limb_t carry = 0;
> +  for (i = 0; i < NLIMBS; i++) {
> +    out[i] = adc(&carry, a[i], b[i]);
>     }
> +  propagate(out, carry);
>   }
>
> -void mg_tls_free(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  if (tls == NULL) return;
> -  SSL_free(tls->ssl);
> -  SSL_CTX_free(tls->ctx);
> -  free(tls);
> -  c->tls = NULL;
> +static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
> +  unsigned i;
> +  sdlimb_t carry = -38;
> +  for (i = 0; i < NLIMBS; i++) {
> +    carry = carry + a[i] - b[i];
> +    out[i] = (limb_t) carry;
> +    carry >>= X25519_WBITS;
> +  }
> +  propagate(out, (limb_t) (1 + carry));
> +}
> +
> +// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of `mg_fe`
> +// to avoid build warnings
> +static void mul(mg_fe out, const mg_fe a, const limb_t *b, unsigned nb) {
> +  limb_t accum[2 * NLIMBS] = {0};
> +  unsigned i, j;
> +
> +  limb_t carry2;
> +  for (i = 0; i < nb; i++) {
> +    limb_t mand = b[i];
> +    carry2 = 0;
> +    for (j = 0; j < NLIMBS; j++) {
> +      limb_t tmp;                        // "a" may be misaligned
> +      memcpy(&tmp, &a[j], sizeof(tmp));  // So make an aligned copy
> +      accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
> +    }
> +    accum[i + j] = carry2;
> +  }
> +
> +  carry2 = 0;
> +  for (j = 0; j < NLIMBS; j++) {
> +    out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
> +  }
> +  propagate(out, carry2);
>   }
>
> -size_t mg_tls_pending(struct mg_connection *c) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> +static void sqr(mg_fe out, const mg_fe a) {
> +  mul(out, a, a, NLIMBS);
> +}
> +static void mul1(mg_fe out, const mg_fe a) {
> +  mul(out, a, out, NLIMBS);
> +}
> +static void sqr1(mg_fe a) {
> +  mul1(a, a);
>   }
>
> -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  int n = SSL_read(tls->ssl, buf, (int) len);
> -  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
> +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
> +                     limb_t doswap) {
> +  unsigned i;
> +  for (i = 0; i < 2 * NLIMBS; i++) {
> +    limb_t xor_ab = (a[i] ^ b[i]) & doswap;
> +    a[i] ^= xor_ab;
> +    b[i] ^= xor_ab;
> +  }
>   }
>
> -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> -  struct mg_tls *tls = (struct mg_tls *) c->tls;
> -  int n = SSL_write(tls->ssl, buf, (int) len);
> -  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> -  if (n <= 0) return MG_IO_ERR;
> -  return n;
> +// Canonicalize a field element x, reducing it to the least residue which is
> +// congruent to it mod 2^255-19
> +// - Precondition: x < 2^255 + 1 word
> +static limb_t canon(mg_fe x) {
> +  // First, add 19.
> +  unsigned i;
> +  limb_t carry0 = 19;
> +  limb_t res;
> +  sdlimb_t carry;
> +  for (i = 0; i < NLIMBS; i++) {
> +    x[i] = adc0(&carry0, x[i]);
> +  }
> +  propagate(x, carry0);
> +
> +  // Here, 19 <= x2 < 2^255
> +  // - This is because we added 19, so before propagate it can't be less
> +  // than 19. After propagate, it still can't be less than 19, because if
> +  // propagate does anything it adds 19.
> +  // - We know that the high bit must be clear, because either the input was ~
> +  // 2^255 + one word + 19 (in which case it propagates to at most 2 words) or
> +  // it was < 2^255. So now, if we subtract 19, we will get back to something in
> +  // [0,2^255-19).
> +  carry = -19;
> +  res = 0;
> +  for (i = 0; i < NLIMBS; i++) {
> +    carry += x[i];
> +    res |= x[i] = (limb_t) carry;
> +    carry >>= X25519_WBITS;
> +  }
> +  return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
> +}
> +
> +static const limb_t a24[1] = {121665};
> +
> +static void ladder_part1(mg_fe xs[5]) {
> +  limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
> +  add(t1, x2, z2);                                 // t1 = A
> +  sub(z2, x2, z2);                                 // z2 = B
> +  add(x2, x3, z3);                                 // x2 = C
> +  sub(z3, x3, z3);                                 // z3 = D
> +  mul1(z3, t1);                                    // z3 = DA
> +  mul1(x2, z2);                                    // x3 = BC
> +  add(x3, z3, x2);                                 // x3 = DA+CB
> +  sub(z3, z3, x2);                                 // z3 = DA-CB
> +  sqr1(t1);                                        // t1 = AA
> +  sqr1(z2);                                        // z2 = BB
> +  sub(x2, t1, z2);                                 // x2 = E = AA-BB
> +  mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0]));  // z2 = E*a24
> +  add(z2, z2, t1);                                 // z2 = E*a24 + AA
> +}
> +
> +static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
> +  limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
> +  sqr1(z3);         // z3 = (DA-CB)^2
> +  mul1(z3, x1);     // z3 = x1 * (DA-CB)^2
> +  sqr1(x3);         // x3 = (DA+CB)^2
> +  mul1(z2, x2);     // z2 = AA*(E*a24+AA)
> +  sub(x2, t1, x2);  // x2 = BB again
> +  mul1(x2, t1);     // x2 = AA*BB
> +}
> +
> +static void x25519_core(mg_fe xs[5], const uint8_t scalar[X25519_BYTES],
> +                        const uint8_t *x1, int clamp) {
> +  int i;
> +  mg_fe x1_limbs;
> +  limb_t swap = 0;
> +  limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
> +  memset(xs, 0, 4 * sizeof(mg_fe));
> +  x2[0] = z3[0] = 1;
> +  for (i = 0; i < NLIMBS; i++) {
> +    x3[i] = x1_limbs[i] =
> +        MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
> +  }
> +
> +  for (i = 255; i >= 0; i--) {
> +    uint8_t bytei = scalar[i / 8];
> +    limb_t doswap;
> +    if (clamp) {
> +      if (i / 8 == 0) {
> +        bytei &= (uint8_t) ~7U;
> +      } else if (i / 8 == X25519_BYTES - 1) {
> +        bytei &= 0x7F;
> +        bytei |= 0x40;
> +      }
> +    }
> +    doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
> +    condswap(x2, x3, swap ^ doswap);
> +    swap = doswap;
> +
> +    ladder_part1(xs);
> +    ladder_part2(xs, (const limb_t *) x1_limbs);
> +  }
> +  condswap(x2, x3, swap);
> +}
> +
> +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
> +                  const uint8_t x1[X25519_BYTES], int clamp) {
> +  int i, ret;
> +  mg_fe xs[5], out_limbs;
> +  limb_t *x2, *z2, *z3, *prev;
> +  static const struct {
> +    uint8_t a, c, n;
> +  } steps[13] = {{2, 1, 1},  {2, 1, 1},  {4, 2, 3},  {2, 4, 6},  {3, 1, 1},
> +                 {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
> +                 {3, 1, 2},  {3, 1, 2},  {3, 1, 1}};
> +  x25519_core(xs, scalar, x1, clamp);
> +
> +  // Precomputed inversion chain
> +  x2 = xs[0];
> +  z2 = xs[1];
> +  z3 = xs[3];
> +
> +  prev = z2;
> +  for (i = 0; i < 13; i++) {
> +    int j;
> +    limb_t *a = xs[steps[i].a];
> +    for (j = steps[i].n; j > 0; j--) {
> +      sqr(a, prev);
> +      prev = a;
> +    }
> +    mul1(a, xs[steps[i].c]);
> +  }
> +
> +  // Here prev = z3
> +  // x2 /= z2
> +  mul(out_limbs, x2, z3, NLIMBS);
> +  ret = (int) canon(out_limbs);
> +  if (!clamp) ret = 0;
> +  for (i = 0; i < NLIMBS; i++) {
> +    uint32_t n = out_limbs[i];
> +    out[i * 4] = (uint8_t) (n & 0xff);
> +    out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
> +    out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
> +    out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
> +  }
> +  return ret;
>   }
> -#endif
>
>   #ifdef MG_ENABLE_LINES
>   #line 1 "src/url.c"
> @@ -5382,7 +14797,7 @@ struct url {
>   int mg_url_is_ssl(const char *url) {
>     return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 ||
>            strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
> -         strncmp(url, "tls:", 4) == 0;
> +         strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
>   }
>
>   static struct url urlparse(const char *url) {
> @@ -5464,6 +14879,14 @@ struct mg_str mg_url_pass(const char *url) {
>   #endif
>
>
> +// Not using memset for zeroing memory, cause it can be dropped by compiler
> +// See https://github.com/cesanta/mongoose/pull/1265
> +void mg_bzero(volatile unsigned char *buf, size_t len) {
> +  if (buf != NULL) {
> +    while (len--) *buf++ = 0;
> +  }
> +}
> +
>   #if MG_ENABLE_CUSTOM_RANDOM
>   #else
>   void mg_random(void *buf, size_t len) {
> @@ -5512,11 +14935,16 @@ uint16_t mg_ntohs(uint16_t net) {
>   }
>
>   uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
> -  int i;
> +  static const uint32_t crclut[16] = {
> +      // table for polynomial 0xEDB88320 (reflected)
> +      0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4,
> +      0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
> +      0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
>     crc = ~crc;
>     while (len--) {
> -    crc ^= *(unsigned char *) buf++;
> -    for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1;
> +    uint8_t b = *(uint8_t *) buf++;
> +    crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
> +    crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
>     }
>     return ~crc;
>   }
> @@ -5539,18 +14967,36 @@ static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
>     return len;
>   }
>
> -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
> -  struct mg_str k, v;
> +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
> +  struct mg_str entry;
>     int allowed = acl.len == 0 ? '+' : '-';  // If any ACL is set, deny by default
> -  while (mg_commalist(&acl, &k, &v)) {
> -    uint32_t net, mask;
> -    if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
> -    if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
> -    if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
> +  uint32_t remote_ip4;
> +  if (remote_ip->is_ip6) {
> +    return -1;  // TODO(): handle IPv6 ACL and addresses
> +  } else {      // IPv4
> +    memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
> +    while (mg_span(acl, &entry, &acl, ',')) {
> +      uint32_t net, mask;
> +      if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
> +      if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
> +      if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
> +    }
>     }
>     return allowed == '+';
>   }
>
> +bool mg_path_is_sane(const struct mg_str path) {
> +  const char *s = path.buf;
> +  size_t n = path.len;
> +  if (path.buf[0] == '.' && path.buf[1] == '.') return false;  // Starts with ..
> +  for (; s[0] != '\0' && n > 0; s++, n--) {
> +    if ((s[0] == '/' || s[0] == '\\') && n >= 2) {   // Subdir?
> +      if (s[1] == '.' && s[2] == '.') return false;  // Starts with ..
> +    }
> +  }
> +  return true;
> +}
> +
>   #if MG_ENABLE_CUSTOM_MILLIS
>   #else
>   uint64_t mg_millis(void) {
> @@ -5558,9 +15004,8 @@ uint64_t mg_millis(void) {
>     return GetTickCount();
>   #elif MG_ARCH == MG_ARCH_RP2040
>     return time_us_64() / 1000;
> -#elif MG_ARCH == MG_ARCH_ESP32
> -  return esp_timer_get_time() / 1000;
> -#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
> +#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
> +    MG_ARCH == MG_ARCH_FREERTOS
>     return xTaskGetTickCount() * portTICK_PERIOD_MS;
>   #elif MG_ARCH == MG_ARCH_AZURERTOS
>     return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
> @@ -5568,6 +15013,12 @@ uint64_t mg_millis(void) {
>     return (uint64_t) Clock_getTicks();
>   #elif MG_ARCH == MG_ARCH_ZEPHYR
>     return (uint64_t) k_uptime_get();
> +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
> +  return (uint64_t) rt_time_get();
> +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> +  return (uint64_t) ((osKernelGetTickCount() * 1000) / osKernelGetTickFreq());
> +#elif MG_ARCH == MG_ARCH_RTTHREAD
> +  return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
>   #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
>     // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on linux
>     // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on linux
> @@ -5595,7 +15046,6 @@ uint64_t mg_millis(void) {
>   }
>   #endif
>
> -
>   #ifdef MG_ENABLE_LINES
>   #line 1 "src/ws.c"
>   #endif
> @@ -5609,6 +15059,7 @@ uint64_t mg_millis(void) {
>
>
>
> +
>   struct ws_msg {
>     uint8_t flags;
>     size_t header_len;
> @@ -5640,10 +15091,10 @@ static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey,
>
>     mg_sha1_ctx sha_ctx;
>     mg_sha1_init(&sha_ctx);
> -  mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len);
> +  mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf, wskey->len);
>     mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
>     mg_sha1_final(sha, &sha_ctx);
> -  mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
> +  mg_base64_encode(sha, sizeof(sha), (char *) b64_sha, sizeof(b64_sha));
>     mg_xprintf(mg_pfn_iobuf, &c->send,
>                "HTTP/1.1 101 Switching Protocols\r\n"
>                "Upgrade: websocket\r\n"
> @@ -5653,7 +15104,7 @@ static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey,
>     if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>     if (wsproto != NULL) {
>       mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
> -              wsproto->ptr);
> +              wsproto->buf);
>     }
>     mg_send(c, "\r\n", 2);
>   }
> @@ -5746,12 +15197,15 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
>       mg_error(c, "not http");  // Some just, not an HTTP request
>     } else if (n > 0) {
>       if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
> -      mg_error(c, "handshake error");
> +      mg_error(c, "ws handshake error");
>       } else {
>         struct mg_http_message hm;
> -      mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> -      c->is_websocket = 1;
> -      mg_call(c, MG_EV_WS_OPEN, &hm);
> +      if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
> +        c->is_websocket = 1;
> +        mg_call(c, MG_EV_WS_OPEN, &hm);
> +      } else {
> +        mg_error(c, "ws handshake error");
> +      }
>       }
>       mg_iobuf_del(&c->recv, 0, (size_t) n);
>     } else {
> @@ -5760,8 +15214,7 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
>     return false;  // Continue event handler
>   }
>
> -static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
> -                     void *fn_data) {
> +static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) {
>     struct ws_msg msg;
>     size_t ofs = (size_t) c->pfn_data;
>
> @@ -5775,7 +15228,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
>         size_t len = msg.header_len + msg.data_len;
>         uint8_t final = msg.flags & 128, op = msg.flags & 15;
>         // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
> -      //                       (int) m.data.len, (int) m.data.len, m.data.ptr));
> +      //                       (int) m.data.len, (int) m.data.len, m.data.buf));
>         switch (op) {
>           case WEBSOCKET_OP_CONTINUE:
>             mg_call(c, MG_EV_WS_CTL, &m);
> @@ -5796,7 +15249,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
>             MG_DEBUG(("%lu WS CLOSE", c->id));
>             mg_call(c, MG_EV_WS_CTL, &m);
>             // Echo the payload of the received CLOSE message back to the sender
> -          mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
> +          mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
>             c->is_draining = 1;
>             break;
>           default:
> @@ -5827,7 +15280,6 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
>         }
>       }
>     }
> -  (void) fn_data;
>     (void) ev_data;
>   }
>
> @@ -5839,7 +15291,7 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
>       char nonce[16], key[30];
>       struct mg_str host = mg_url_host(url);
>       mg_random(nonce, sizeof(nonce));
> -    mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
> +    mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key, sizeof(key));
>       mg_xprintf(mg_pfn_iobuf, &c->send,
>                  "GET %s HTTP/1.1\r\n"
>                  "Upgrade: websocket\r\n"
> @@ -5847,7 +15299,7 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
>                  "Connection: Upgrade\r\n"
>                  "Sec-WebSocket-Version: 13\r\n"
>                  "Sec-WebSocket-Key: %s\r\n",
> -               mg_url_uri(url), (int) host.len, host.ptr, key);
> +               mg_url_uri(url), (int) host.len, host.buf, key);
>       if (fmt != NULL) {
>         va_list ap;
>         va_start(ap, fmt);
> @@ -5896,1591 +15348,2247 @@ size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
>   }
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "mip/driver_stm32.c"
> +#line 1 "src/drivers/cmsis.c"
>   #endif
> +// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
>
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS
>
> -#if MG_ENABLE_MIP && \
> -    (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
> -struct stm32_eth {
> -  volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
> -      MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
> -      MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
> -      MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> -      RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> -      RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> -      RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
> -      PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
> -      DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
> -      DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> -      DMACHRBAR;
> -};
> -#undef ETH
> -#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
>
> -#undef BIT
> -#define BIT(x) ((uint32_t) 1 << (x))
> -#define ETH_PKT_SIZE 1540  // Max frame size
> -#define ETH_DESC_CNT 4     // Descriptors count
> -#define ETH_DS 4           // Descriptor size (words)
>
> -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
> -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
> -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
> -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
> -static struct mip_if *s_ifp;                         // MIP interface
> -enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 };     // PHY constants
>
> -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
> -  ETH->MACMIIAR &= (7 << 2);
> -  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> -  ETH->MACMIIAR |= BIT(0);
> -  while (ETH->MACMIIAR & BIT(0)) (void) 0;
> -  return ETH->MACMIIDR;
> -}
>
> -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> -  ETH->MACMIIDR = val;
> -  ETH->MACMIIAR &= (7 << 2);
> -  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
> -  ETH->MACMIIAR |= BIT(0);
> -  while (ETH->MACMIIAR & BIT(0)) (void) 0;
> -}
> +extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
> +extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
>
> -static uint32_t get_hclk(void) {
> -  struct rcc {
> -    volatile uint32_t CR, PLLCFGR, CFGR;
> -  } *rcc = (struct rcc *) 0x40023800;
> -  uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */;
> +static struct mg_tcpip_if *s_ifp;
>
> -  if (rcc->CFGR & (1 << 2)) {
> -    clk = hse;
> -  } else if (rcc->CFGR & (1 << 3)) {
> -    uint32_t vco, m, n, p;
> -    m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> -    n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> -    p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> -    clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> -    vco = (uint32_t) ((uint64_t) clk * n / m);
> -    clk = vco / p;
> -  } else {
> -    clk = hsi;
> +static void mac_cb(uint32_t);
> +static bool cmsis_init(struct mg_tcpip_if *);
> +static bool cmsis_up(struct mg_tcpip_if *);
> +static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
> +static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
> +
> +struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init, cmsis_tx, NULL,
> +                                                cmsis_up};
> +
> +static bool cmsis_init(struct mg_tcpip_if *ifp) {
> +  ARM_ETH_MAC_ADDR addr;
> +  s_ifp = ifp;
> +
> +  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> +  ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> +  ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
> +  if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
> +  if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) != ARM_DRIVER_OK)
> +    return false;
> +  if (cap.event_rx_frame == 0)  // polled mode driver
> +    mg_tcpip_driver_cmsis.rx = cmsis_rx;
> +  mac->PowerControl(ARM_POWER_FULL);
> +  if (cap.mac_address) {  // driver provides MAC address
> +    mac->GetMacAddress(&addr);
> +    memcpy(ifp->mac, &addr, sizeof(ifp->mac));
> +  } else {  // we provide MAC address
> +    memcpy(&addr, ifp->mac, sizeof(addr));
> +    mac->SetMacAddress(&addr);
>     }
> -  uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> -  if (hpre < 8) return clk;
> +  phy->PowerControl(ARM_POWER_FULL);
> +  phy->SetInterface(cap.media_interface);
> +  phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
> +  return true;
> +}
>
> -  uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
> -  return ((uint32_t) clk) >> ahbptab[hpre - 8];
> +static size_t cmsis_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
> +  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> +  if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
> +    ifp->nerr++;
> +    return 0;
> +  }
> +  ifp->nsent++;
> +  return len;
>   }
>
> -//  Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3,
> -//  it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived
> -//  from the HSI (internal RC), and it can go above specs, the datasheets
> -//  specify a range of frequencies and activate one of a series of dividers to
> -//  keep the MDC clock safely below 2.5MHz. We guess a divider setting based on
> -//  HCLK with a +5% drift. If the user uses a different clock from our
> -//  defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
> -//  (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
> -static int guess_mdc_cr(void) {
> -  uint8_t crs[] = {2, 3, 0, 1, 4, 5};          // ETH->MACMIIAR::CR values
> -  uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
> -  uint32_t hclk = get_hclk();                  // Guess system HCLK
> -  int result = -1;                             // Invalid CR value
> -  if (hclk < 25000000) {
> -    MG_ERROR(("HCLK too low"));
> -  } else {
> -    for (int i = 0; i < 6; i++) {
> -      if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> -        result = crs[i];
> -        break;
> +static bool cmsis_up(struct mg_tcpip_if *ifp) {
> +  ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> +  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> +  bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0;  // link state
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {             // just went up
> +    ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
> +    mac->Control(ARM_ETH_MAC_CONFIGURE,
> +                 (st.speed << ARM_ETH_MAC_SPEED_Pos) |
> +                     (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
> +                     ARM_ETH_MAC_ADDRESS_BROADCAST);
> +    MG_DEBUG(("Link is %uM %s-duplex",
> +              (st.speed == 2) ? 1000
> +              : st.speed      ? 100
> +                              : 10,
> +              st.duplex ? "full" : "half"));
> +    mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
> +    mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
> +  } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) {  // just went down
> +    mac->Control(ARM_ETH_MAC_FLUSH,
> +                 ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
> +    mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
> +    mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
> +  }
> +  return up;
> +}
> +
> +static void mac_cb(uint32_t ev) {
> +  if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
> +  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> +  uint32_t len = mac->GetRxFrameSize();  // CRC already stripped
> +  if (len >= 60 && len <= 1518) {        // proper frame
> +    char *p;
> +    if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) {  // have room
> +      if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) {   // copy succeeds
> +        mg_queue_add(&s_ifp->recv_queue, len);
> +        s_ifp->nrecv++;
>         }
> +      return;
>       }
> -    if (result < 0) MG_ERROR(("HCLK too high"));
> +    s_ifp->ndrop++;
>     }
> -  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> -  return result;
> +  mac->ReadFrame(NULL, 0);  // otherwise, discard
> +}
> +
> +static size_t cmsis_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
> +  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> +  uint32_t len = mac->GetRxFrameSize();  // CRC already stripped
> +  if (len >= 60 && len <= 1518 &&
> +      ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
> +    return len;
> +  if (len > 0) mac->ReadFrame(NULL, 0);  // discard bad frames
> +  (void) ifp;
> +  return 0;
>   }
>
> -static bool mip_driver_stm32_init(struct mip_if *ifp) {
> -  struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data *) ifp->driver_data;
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/imxrt.c"
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT
> +struct imxrt_enet {
> +  volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR, RESERVED2[3],
> +      ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC, RESERVED5[7], RCR,
> +      RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0, TXIC1, TXIC2,
> +      RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR, GALR,
> +      RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL, RSEM,
> +      RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC, RACC,
> +      RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
> +      RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
> +      RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127, RMON_T_P128TO255,
> +      RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048, RMON_T_GTE2048,
> +      RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL, IEEE_T_MCOL,
> +      IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR, IEEE_T_CSERR,
> +      IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3], RMON_R_PACKETS,
> +      RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
> +      RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
> +      RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511, RMON_R_P512TO1023,
> +      RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
> +      IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR, IEEE_R_FDXFC,
> +      IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER, ATCOR, ATINC,
> +      ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1, TCSR2, TCCR2,
> +      TCSR3;
> +};
> +
> +#undef ENET
> +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
> +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
> +#define ETH_DESC_CNT 5     // Descriptors count
> +#else
> +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#endif
> +
> +#define ETH_PKT_SIZE 1536  // Max frame size, 64-bit aligned
> +
> +struct enet_desc {
> +  uint16_t length;   // Data length
> +  uint16_t control;  // Control and status
> +  uint32_t *buffer;  // Data ptr
> +};
> +
> +// TODO(): handle these in a portable compiler-independent CMSIS-friendly way
> +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
> +
> +// Descriptors: in non-cached area (TODO(scaprile)), (37.5.1.22.2 37.5.1.23.2)
> +// Buffers: 64-byte aligned (37.3.14)
> +static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT] MG_64BYTE_ALIGNED;
> +static volatile struct enet_desc s_txdesc[ETH_DESC_CNT] MG_64BYTE_ALIGNED;
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
> +
> +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
> +  ENET->EIR |= MG_BIT(23);  // MII interrupt clear
> +  ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18) | (2 << 16);
> +  while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> +  return ENET->MMFR & 0xffff;
> +}
> +
> +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  ENET->EIR |= MG_BIT(23);  // MII interrupt clear
> +  ENET->MMFR =
> +      (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16) | val;
> +  while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> +}
> +
> +//  MDC clock is generated from IPS Bus clock (ipg_clk); as per 802.3,
> +//  it must not exceed 2.5MHz
> +// The PHY receives the PLL6-generated 50MHz clock
> +static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_imxrt_data *d =
> +      (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
>     s_ifp = ifp;
>
>     // Init RX descriptors
>     for (int i = 0; i < ETH_DESC_CNT; i++) {
> -    s_rxdesc[i][0] = BIT(31);                            // Own
> -    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14);       // 2nd address chained
> -    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
> -    s_rxdesc[i][3] =
> -        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +    s_rxdesc[i].control = MG_BIT(15);              // Own (E)
> +    s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i];  // Point to data buffer
>     }
> +  s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13);  // Wrap last descriptor
>
>     // Init TX descriptors
>     for (int i = 0; i < ETH_DESC_CNT; i++) {
> -    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
> -    s_txdesc[i][3] =
> -        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +    // s_txdesc[i].control = MG_BIT(10);  // Own (TC)
> +    s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
>     }
> -
> -  ETH->DMABMR |= BIT(0);                         // Software reset
> -  while ((ETH->DMABMR & BIT(0)) != 0) (void) 0;  // Wait until done
> -
> -  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
> -  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> -  ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
> -
> -  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> -  // hardware checksum. Therefore, descriptor size is 4, not 8
> -  // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
> -  ETH->MACIMR = BIT(3) | BIT(9);  // Mask timestamp & PMT IT
> -  ETH->MACFCR = BIT(7);           // Disable zero quarta pause
> -  // ETH->MACFFR = BIT(31);                            // Receive all
> -  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15));           // Reset PHY
> -  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12));           // Set autonegotiation
> -  ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc;     // RX descriptors
> -  ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc;     // RX descriptors
> -  ETH->DMAIER = BIT(6) | BIT(16);                      // RIE, NISE
> -  ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14);    // RE, TE, Duplex, Fast
> -  ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
> -
> -  // MAC address filtering
> -  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> -  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> -                 ((uint32_t) ifp->mac[2] << 16) |
> -                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> -  if (ifp->queue.len == 0) ifp->queue.len = 8192;
> +  s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13);  // Wrap last descriptor
> +
> +  ENET->ECR = MG_BIT(0);                     // Software reset, disable
> +  while ((ENET->ECR & MG_BIT(0))) (void) 0;  // Wait until done
> +
> +  // Set MDC clock divider. If user told us the value, use it.
> +  // TODO(): Otherwise, guess (currently assuming max freq)
> +  int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
> +  ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1);  // HOLDTIME 2 clks
> +  struct mg_phy phy = {enet_read_phy, enet_write_phy};
> +  mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC clocks PHY
> +  // Select RMII mode, 100M, keep CRC, set max rx length, disable loop
> +  ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
> +  // ENET->RCR |= MG_BIT(3);     // Receive all
> +  ENET->TCR = MG_BIT(2);  // Full-duplex
> +  ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
> +  ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
> +  ENET->MRBR[0] = ETH_PKT_SIZE;  // Same size for RX/TX buffers
> +  // MAC address filtering (bytes in reversed order)
> +  ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t) ifp->mac[5] << 16U;
> +  ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
> +               ((uint32_t) ifp->mac[1] << 16U) |
> +               ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> +  ENET->ECR = MG_BIT(8) | MG_BIT(1);  // Little-endian CPU, Enable
> +  ENET->EIMR = MG_BIT(25);            // Set interrupt mask
> +  ENET->RDAR = MG_BIT(24);            // Receive Descriptors have changed
> +  ENET->TDAR = MG_BIT(24);            // Transmit Descriptors have changed
> +  // ENET->OPD = 0x10014;
>     return true;
>   }
>
> -static uint32_t s_txno;
> -static size_t mip_driver_stm32_tx(const void *buf, size_t len, struct mip_if *ifp) {
> -  if (len > sizeof(s_txbuf[s_txno])) {
> +// Transmit frame
> +static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
> +                                       struct mg_tcpip_if *ifp) {
> +  static int s_txno;  // Current descriptor index
> +  if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>       MG_ERROR(("Frame too big, %ld", (long) len));
> -    len = 0;  // Frame is too big
> -  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> -    MG_ERROR(("No free descriptors"));
> -    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
> -    len = 0;  // All descriptors are busy, fail
> +    len = (size_t) -1;  // fail
> +  } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
> +    ifp->nerr++;
> +    MG_ERROR(("No descriptors available"));
> +    len = 0;  // retry later
>     } else {
> -    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
> -    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
> -    s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30);  // Chain,FS,LS
> -    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
> +    memcpy(s_txbuf[s_txno], buf, len);         // Copy data
> +    s_txdesc[s_txno].length = (uint16_t) len;  // Set data len
> +    // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
> +    s_txdesc[s_txno].control |=
> +        (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
> +    ENET->TDAR = MG_BIT(24);  // Descriptor ring updated
>       if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>     }
> -  ETH->DMASR = BIT(2) | BIT(5);  // Clear any prior TBUS/TUS
> -  ETH->DMATPDR = 0;              // and resume
> -  return len;
>     (void) ifp;
> +  return len;
>   }
>
> -static bool mip_driver_stm32_up(struct mip_if *ifp) {
> -  uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
> -  (void) ifp;
> -  return bsr & BIT(2) ? 1 : 0;
> +static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_imxrt_data *d =
> +      (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {enet_read_phy, enet_write_phy};
> +  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    // tmp = reg with flags set to the most likely situation: 100M full-duplex
> +    // if(link is slow or half) set flags otherwise
> +    // reg = tmp
> +    uint32_t tcr = ENET->TCR | MG_BIT(2);             // Full-duplex
> +    uint32_t rcr = ENET->RCR & ~MG_BIT(9);            // 100M
> +    if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9);  // 10M
> +    if (full_duplex == false) tcr &= ~MG_BIT(2);      // Half-duplex
> +    ENET->TCR = tcr;  // IRQ handler does not fiddle with these registers
> +    ENET->RCR = rcr;
> +    MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
> +              tcr & MG_BIT(2) ? "full" : "half"));
> +  }
> +  return up;
>   }
>
> -void ETH_IRQHandler(void);
> +void ENET_IRQHandler(void);
>   static uint32_t s_rxno;
> -void ETH_IRQHandler(void) {
> -  qp_mark(QP_IRQTRIGGERED, 0);
> -  if (ETH->DMASR & BIT(6)) {             // Frame received, loop
> -    ETH->DMASR = BIT(16) | BIT(6);       // Clear flag
> -    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
> -      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
> -      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
> -          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
> -        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> -        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> -        //  ETH->DMASR);
> -        mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> -      }
> -      s_rxdesc[s_rxno][0] = BIT(31);
> -      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +void ENET_IRQHandler(void) {
> +  ENET->EIR = MG_BIT(25);  // Ack IRQ
> +  // Frame received, loop
> +  for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
> +    uint32_t r = s_rxdesc[s_rxno].control;
> +    if (r & MG_BIT(15)) break;  // exit when done
> +    // skip partial/errored frames (Table 37-32)
> +    if ((r & MG_BIT(11)) &&
> +        !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) | MG_BIT(0)))) {
> +      size_t len = s_rxdesc[s_rxno].length;
> +      mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>       }
> +    s_rxdesc[s_rxno].control |= MG_BIT(15);
> +    if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>     }
> -  ETH->DMASR = BIT(7);  // Clear possible RBUS while processing
> -  ETH->DMARPDR = 0;     // and resume RX
> +  ENET->RDAR = MG_BIT(24);  // Receive Descriptors have changed
> +  // If b24 == 0, descriptors were exhausted and probably frames were dropped
>   }
>
> -struct mip_driver mip_driver_stm32 = {
> -    mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx, mip_driver_stm32_up};
> +struct mg_tcpip_driver mg_tcpip_driver_imxrt = {mg_tcpip_driver_imxrt_init,
> +                                                mg_tcpip_driver_imxrt_tx, NULL,
> +                                                mg_tcpip_driver_imxrt_up};
> +
>   #endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "mip/driver_tm4c.c"
> +#line 1 "src/drivers/phy.c"
>   #endif
>
>
> -#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
> -struct tm4c_emac {
> -  volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
> -      EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS,
> -      EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
> -      EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
> -      EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> -      EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> -      EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
> -      EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR,
> -      EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> -      EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC,
> -      EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> -      EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> -      RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> -      EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
> -      EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> -      RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
> -      RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> -      EMACEPHYIM, EMACEPHYIMSC;
> +enum {                      // ID1  ID2
> +  MG_PHY_KSZ8x = 0x22,      // 0022 1561 - KSZ8081RNB
> +  MG_PHY_DP83x = 0x2000,    // 2000 a140 - TI DP83825I
> +  MG_PHY_DP83867 = 0xa231,  // 2000 a231 - TI DP83867I
> +  MG_PHY_LAN87x = 0x7,      // 0007 c0fx - LAN8720
> +  MG_PHY_RTL8201 = 0x1C     // 001c c816 - RTL8201
> +};
> +
> +enum {
> +  MG_PHY_REG_BCR = 0,
> +  MG_PHY_REG_BSR = 1,
> +  MG_PHY_REG_ID1 = 2,
> +  MG_PHY_REG_ID2 = 3,
> +  MG_PHY_DP83x_REG_PHYSTS = 16,
> +  MG_PHY_DP83867_REG_PHYSTS = 17,
> +  MG_PHY_DP83x_REG_RCSR = 23,
> +  MG_PHY_DP83x_REG_LEDCR = 24,
> +  MG_PHY_KSZ8x_REG_PC1R = 30,
> +  MG_PHY_KSZ8x_REG_PC2R = 31,
> +  MG_PHY_LAN87x_REG_SCSR = 31,
> +  MG_PHY_RTL8201_REG_RMSR = 16,  // in page 7
> +  MG_PHY_RTL8201_REG_PAGESEL = 31
> +};
> +
> +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
> +  switch (id1) {
> +    case MG_PHY_DP83x:
> +      switch (id2) {
> +        case MG_PHY_DP83867:
> +          return "DP83867";
> +        default:
> +          return "DP83x";
> +      }
> +    case MG_PHY_KSZ8x:
> +      return "KSZ8x";
> +    case MG_PHY_LAN87x:
> +      return "LAN87x";
> +    case MG_PHY_RTL8201:
> +      return "RTL8201";
> +    default:
> +      return "unknown";
> +  }
> +  (void) id2;
> +}
> +
> +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) {
> +  uint16_t id1, id2;
> +  phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15));  // Reset PHY
> +  while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15)) (void) 0;
> +  // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw says otherwise
> +
> +  id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> +  id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> +  MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2)));
> +
> +  if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
> +    phy->write_reg(phy_addr, 0x0d, 0x1f);    // write 0x10d to IO_MUX_CFG (0x0170)
> +    phy->write_reg(phy_addr, 0x0e, 0x170);
> +    phy->write_reg(phy_addr, 0x0d, 0x401f);
> +    phy->write_reg(phy_addr, 0x0e, 0x10d);
> +  }
> +
> +  if (config & MG_PHY_CLOCKS_MAC) {
> +    // Use PHY crystal oscillator (preserve defaults)
> +    // nothing to do
> +  } else {  // MAC clocks PHY, PHY has no xtal
> +    // Enable 50 MHz external ref clock at XI (preserve defaults)
> +    if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
> +      phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) | MG_BIT(0));
> +    } else if (id1 == MG_PHY_KSZ8x) {
> +      phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
> +                     MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
> +    } else if (id1 == MG_PHY_LAN87x) {
> +      // nothing to do
> +    } else if (id1 == MG_PHY_RTL8201) {
> +      // assume PHY has been hardware strapped properly
> +#if 0
> +      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7);  // Select page 7
> +      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
> +      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0);  // Select page 0
> +#endif
> +    }
> +  }
> +
> +  if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
> +    phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
> +                   MG_BIT(9) | MG_BIT(7));  // LED status, active high
> +  }  // Other PHYs do not support this feature
> +}
> +
> +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex,
> +               uint8_t *speed) {
> +  bool up = false;
> +  uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
> +  if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2)))  // some PHYs latch down events
> +    bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);  // read again
> +  up = bsr & MG_BIT(2);
> +  if (up && full_duplex != NULL && speed != NULL) {
> +    uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> +    if (id1 == MG_PHY_DP83x) {
> +      uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> +      if (id2 == MG_PHY_DP83867) {
> +        uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83867_REG_PHYSTS);
> +        *full_duplex = physts & MG_BIT(13);
> +        *speed = (physts & MG_BIT(15))   ? MG_PHY_SPEED_1000M
> +                 : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
> +                                         : MG_PHY_SPEED_10M;
> +      } else {
> +        uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83x_REG_PHYSTS);
> +        *full_duplex = physts & MG_BIT(2);
> +        *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
> +      }
> +    } else if (id1 == MG_PHY_KSZ8x) {
> +      uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
> +      *full_duplex = pc1r & MG_BIT(2);
> +      *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
> +    } else if (id1 == MG_PHY_LAN87x) {
> +      uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
> +      *full_duplex = scsr & MG_BIT(4);
> +      *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
> +    } else if (id1 == MG_PHY_RTL8201) {
> +      uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
> +      *full_duplex = bcr & MG_BIT(8);
> +      *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
> +    }
> +  }
> +  return up;
> +}
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/ra.c"
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA
> +struct ra_etherc {
> +  volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR, RESERVED2, ECSIPR,
> +      RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR, RESERVED6[3], IPGR,
> +      APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR, RESERVED8[20], MAHR,
> +      RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR, RESERVED11, CEFCR,
> +      FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
>   };
> -#undef EMAC
> -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>
> -#undef BIT
> -#define BIT(x) ((uint32_t) 1 << (x))
> -#define ETH_PKT_SIZE 1540  // Max frame size
> +struct ra_edmac {
> +  volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR, RESERVED2, TDLAR,
> +      RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR, RESERVED6, TRSCER,
> +      RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10, RMCR,
> +      RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR, TRIMD,
> +      RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
> +};
> +
> +#undef ETHERC
> +#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
> +#undef EDMAC
> +#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
> +#undef RASYSC
> +#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
> +#undef ICU_IELSR
> +#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
> +
> +#define ETH_PKT_SIZE 1536  // Max frame size, multiple of 32
>   #define ETH_DESC_CNT 4     // Descriptors count
> -#define ETH_DS 4           // Descriptor size (words)
>
> -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
> -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
> -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
> -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
> -static struct mip_if *s_ifp;                         // MIP interface
> -enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 };  // PHY constants
> +// TODO(): handle these in a portable compiler-independent CMSIS-friendly way
> +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
> +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
>
> -static inline void tm4cspin(volatile uint32_t count) {
> +// Descriptors: 16-byte aligned
> +// Buffers: 32-byte aligned (27.3.1)
> +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
> +static volatile uint32_t s_txdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
> +
> +// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or equivalent
> +static inline void raspin(volatile uint32_t count) {
>     while (count--) (void) 0;
>   }
> -
> -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> -  EMAC->EMACMIIADDR &= (0xf << 2);
> -  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> -  EMAC->EMACMIIADDR |= BIT(0);
> -  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> -  return EMAC->EMACMIIDATA;
> +// count to get the 200ns SMC semi-cycle period (2.5MHz) calling raspin():
> +// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
> +static uint32_t s_smispin;
> +
> +// Bit-banged SMI
> +static void smi_preamble(void) {
> +  unsigned int i = 32;
> +  uint32_t pir = MG_BIT(1) | MG_BIT(2);  // write, mdio = 1, mdc = 0
> +  ETHERC->PIR = pir;
> +  while (i--) {
> +    pir &= ~MG_BIT(0);  // mdc = 0
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +    pir |= MG_BIT(0);  // mdc = 1
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +  }
>   }
> -
> -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> -  EMAC->EMACMIIDATA = val;
> -  EMAC->EMACMIIADDR &= (0xf << 2);
> -  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
> -  EMAC->EMACMIIADDR |= BIT(0);
> -  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> +static void smi_wr(uint16_t header, uint16_t data) {
> +  uint32_t word = (header << 16) | data;
> +  smi_preamble();
> +  unsigned int i = 32;
> +  while (i--) {
> +    uint32_t pir = MG_BIT(1) |
> +                   (word & 0x80000000 ? MG_BIT(2) : 0);  // write, mdc = 0, data
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +    pir |= MG_BIT(0);  // mdc = 1
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +    word <<= 1;
> +  }
>   }
> -
> -static uint32_t get_sysclk(void) {
> -  struct sysctl {
> -    volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
> -        PLLFREQ1;
> -  } *sysctl = (struct sysctl *) 0x400FE000;
> -  uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */;
> -  if (sysctl->RSCLKCFG & (1 << 28)) {  // USEPLL
> -    uint32_t fin, vco, mdiv, n, q, psysdiv;
> -    uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> -    if (pllsrc == 0) {
> -      clk = piosc;
> -    } else if (pllsrc == 3) {
> -      clk = mosc;
> -    } else {
> -      MG_ERROR(("Unsupported clock source"));
> -    }
> -    q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> -    n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> -    fin = clk / ((q + 1) * (n + 1));
> -    mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> -           0;  // mint + (mfrac / 1024); MFRAC not supported
> -    psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> -    vco = (uint32_t) ((uint64_t) fin * mdiv);
> -    return vco / (psysdiv + 1);
> +static uint16_t smi_rd(uint16_t header) {
> +  smi_preamble();
> +  unsigned int i = 16;  // 2 LSb as turnaround
> +  uint32_t pir;
> +  while (i--) {
> +    pir = (i > 1 ? MG_BIT(1) : 0) |
> +          (header & 0x8000
> +               ? MG_BIT(2)
> +               : 0);  // mdc = 0, header, set read direction at turnaround
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +    pir |= MG_BIT(0);  // mdc = 1
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
> +    header <<= 1;
>     }
> -  uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> -  if (oscsrc == 0) {
> -    clk = piosc;
> -  } else if (oscsrc == 3) {
> -    clk = mosc;
> -  } else {
> -    MG_ERROR(("Unsupported clock source"));
> +  i = 16;
> +  uint16_t data = 0;
> +  while (i--) {
> +    data <<= 1;
> +    pir = 0;  // read, mdc = 0
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin / 2);  // 1/4 clock period, 300ns max access time
> +    data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0);  // read mdio
> +    raspin(s_smispin / 2);                    // 1/4 clock period
> +    pir |= MG_BIT(0);                         // mdc = 1
> +    ETHERC->PIR = pir;
> +    raspin(s_smispin);
>     }
> -  uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> -  return clk / (osysdiv + 1);
> +  return data;
>   }
>
> -//  Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per
> -//  802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be
> -//  derived from the PIOSC (internal RC), and it can go above  specs, the
> -//  datasheets specify a range of frequencies and activate one of a series of
> -//  dividers to keep the MDC clock safely below 2.5MHz. We guess a divider
> -//  setting based on SYSCLK with a +5% drift. If the user uses a different clock
> -//  from our defaults, needs to set the macros on top Valid for TM4C129x (20.7)
> -//  (4.5% worst case drift)
> -// The PHY receives the main oscillator (MOSC) (20.3.1)
> -static int guess_mdc_cr(void) {
> -  uint8_t crs[] = {2, 3, 0, 1};      // EMAC->MACMIIAR::CR values
> -  uint8_t div[] = {16, 26, 42, 62};  // Respective HCLK dividers
> -  uint32_t sysclk = get_sysclk();    // Guess system SYSCLK
> -  int result = -1;                   // Invalid CR value
> -  if (sysclk < 25000000) {
> -    MG_ERROR(("SYSCLK too low"));
> -  } else {
> -    for (int i = 0; i < 4; i++) {
> -      if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> -        result = crs[i];
> -        break;
> -      }
> -    }
> -    if (result < 0) MG_ERROR(("SYSCLK too high"));
> -  }
> -  MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> -  return result;
> +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
> +  return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) | (reg << 2) | (2 << 0)));
>   }
>
> -static bool mip_driver_tm4c_init(struct mip_if *ifp) {
> -  struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data *) ifp->driver_data;
> +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg << 2) | (2 << 0)), val);
> +}
> +
> +// MDC clock is generated manually; as per 802.3, it must not exceed 2.5MHz
> +static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_ra_data *d =
> +      (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
>     s_ifp = ifp;
>
> +  // Init SMI clock timing. If user told us the clock value, use it.
> +  // TODO(): Otherwise, guess
> +  s_smispin = d->clock / 15000000;
> +
>     // Init RX descriptors
>     for (int i = 0; i < ETH_DESC_CNT; i++) {
> -    s_rxdesc[i][0] = BIT(31);                            // Own
> -    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14);       // 2nd address chained
> -    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
> -    s_rxdesc[i][3] =
> -        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> -    // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> +    s_rxdesc[i][0] = MG_BIT(31);             // RACT
> +    s_rxdesc[i][1] = ETH_PKT_SIZE << 16;     // RBL
> +    s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];  // Point to data buffer
>     }
> +  s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30);  // Wrap last descriptor
>
>     // Init TX descriptors
>     for (int i = 0; i < ETH_DESC_CNT; i++) {
> -    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
> -    s_txdesc[i][3] =
> -        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +    // TACT = 0
> +    s_txdesc[i][2] = (uint32_t) s_txbuf[i];
>     }
> -
> -  EMAC->EMACDMABUSMOD |= BIT(0);                            // Software reset
> -  while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1);  // Wait until done
> -
> -  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
> -  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> -  EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> -
> -  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> -  // hardware checksum. Therefore, descriptor size is 4, not 8
> -  // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
> -  EMAC->EMACIM = BIT(3) | BIT(9);  // Mask timestamp & PMT IT
> -  EMAC->EMACFLOWCTL = BIT(7);      // Disable zero-quanta pause
> -  // EMAC->EMACFRAMEFLTR = BIT(31);   // Receive all
> -  // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> -  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15));  // Reset internal PHY (EPHY)
> -  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12));  // Set autonegotiation
> -  EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
> -  EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors
> -  EMAC->EMACDMAIM = BIT(6) | BIT(16);                    // RIE, NIE
> -  EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14);   // RE, TE, Duplex, Fast
> -  EMAC->EMACDMAOPMODE =
> -      BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
> -  EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> -  EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> -                     ((uint32_t) ifp->mac[2] << 16) |
> -                     ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> -  // NOTE(scaprile) There are 3 additional slots for filtering, disabled by
> -  // default. This also applies to the STM32 driver (at least for F7)
> -
> -  if (ifp->queue.len == 0) ifp->queue.len = 8192;
> +  s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30);  // Wrap last descriptor
> +
> +  EDMAC->EDMR = MG_BIT(0);  // Software reset, wait 64 PCLKA clocks (27.2.1)
> +  uint32_t sckdivcr = RASYSC[8];  // get divisors from SCKDIVCR (8.2.2)
> +  uint32_t ick = 1 << ((sckdivcr >> 24) & 7);   // sys_clock div
> +  uint32_t pcka = 1 << ((sckdivcr >> 12) & 7);  // pclka div
> +  raspin((64U * pcka) / (3U * ick));
> +  EDMAC->EDMR = MG_BIT(6);  // Initialize, little-endian (27.2.1)
> +
> +  MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
> +  struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> +  mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
> +
> +  // Select RMII mode,
> +  ETHERC->ECMR = MG_BIT(2) | MG_BIT(1);  // 100M, Full-duplex, CRC
> +  // ETHERC->ECMR |= MG_BIT(0);             // Receive all
> +  ETHERC->RFLR = 1518;  // Set max rx length
> +
> +  EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
> +  EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
> +  // MAC address filtering (bytes in reversed order)
> +  ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
> +                 ((uint32_t) ifp->mac[1] << 16U) |
> +                 ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> +  ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
> +
> +  EDMAC->TFTR = 0;                        // Store and forward (27.2.10)
> +  EDMAC->FDR = 0x070f;                    // (27.2.11)
> +  EDMAC->RMCR = MG_BIT(0);                // (27.2.12)
> +  ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5);  // TE RE
> +  EDMAC->EESIPR = MG_BIT(18);             // Enable Rx IRQ
> +  EDMAC->EDRRR = MG_BIT(0);               // Receive Descriptors have changed
> +  EDMAC->EDTRR = MG_BIT(0);               // Transmit Descriptors have changed
>     return true;
>   }
>
> -static uint32_t s_txno;
> -static size_t mip_driver_tm4c_tx(const void *buf, size_t len, struct mip_if *ifp) {
> -  if (len > sizeof(s_txbuf[s_txno])) {
> +// Transmit frame
> +static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
> +                                    struct mg_tcpip_if *ifp) {
> +  static int s_txno;  // Current descriptor index
> +  if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>       MG_ERROR(("Frame too big, %ld", (long) len));
> -    len = 0;  // fail
> -  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> +    len = (size_t) -1;  // fail
> +  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> +    ifp->nerr++;
>       MG_ERROR(("No descriptors available"));
> -    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> -    // EMAC->EMACDMARIS);
> -    len = 0;  // fail
> +    len = 0;  // retry later
>     } else {
> -    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
> -    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
> -    s_txdesc[s_txno][0] =
> -        BIT(20) | BIT(28) | BIT(29) | BIT(30);  // Chain,FS,LS,IC
> -    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
> +    memcpy(s_txbuf[s_txno], buf, len);            // Copy data
> +    s_txdesc[s_txno][1] = len << 16;              // Set data len
> +    s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28;  // (27.3.1.1) mark valid
> +    EDMAC->EDTRR = MG_BIT(0);                     // Transmit request
>       if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>     }
> -  EMAC->EMACDMARIS = BIT(2) | BIT(5);  // Clear any prior TU/UNF
> -  EMAC->EMACTXPOLLD = 0;               // and resume
>     return len;
> -  (void) ifp;
>   }
>
> -static bool mip_driver_tm4c_up(struct mip_if *ifp) {
> -  uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> -  (void) ifp;
> -  return (bmsr & BIT(2)) ? 1 : 0;
> +static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_ra_data *d =
> +      (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> +  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    // tmp = reg with flags set to the most likely situation: 100M full-duplex
> +    // if(link is slow or half) set flags otherwise
> +    // reg = tmp
> +    uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1);  // 100M Full-duplex
> +    if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2);     // 10M
> +    if (full_duplex == false) ecmr &= ~MG_BIT(1);          // Half-duplex
> +    ETHERC->ECMR = ecmr;  // IRQ handler does not fiddle with these registers
> +    MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
> +              ecmr & MG_BIT(1) ? "full" : "half"));
> +  }
> +  return up;
>   }
>
> -void EMAC0_IRQHandler(void);
> +void EDMAC_IRQHandler(void);
>   static uint32_t s_rxno;
> -void EMAC0_IRQHandler(void) {
> -  qp_mark(QP_IRQTRIGGERED, 0);
> -  if (EMAC->EMACDMARIS & BIT(6)) {        // Frame received, loop
> -    EMAC->EMACDMARIS = BIT(16) | BIT(6);  // Clear flag
> -    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
> -      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
> -      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
> -          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
> -        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> -        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> -        //  EMAC->EMACDMARIS);
> -        mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> -      }
> -      s_rxdesc[s_rxno][0] = BIT(31);
> -      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +void EDMAC_IRQHandler(void) {
> +  struct mg_tcpip_driver_ra_data *d =
> +      (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
> +  EDMAC->EESR = MG_BIT(18);            // Ack IRQ in EDMAC 1st
> +  ICU_IELSR[d->irqno] &= ~MG_BIT(16);  // Ack IRQ in ICU last
> +  // Frame received, loop
> +  for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
> +    uint32_t r = s_rxdesc[s_rxno][0];
> +    if (r & MG_BIT(31)) break;  // exit when done
> +    // skip partial/errored frames (27.3.1.2)
> +    if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
> +      size_t len = s_rxdesc[s_rxno][1] & 0xffff;
> +      mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);  // CRC already stripped
>       }
> +    s_rxdesc[s_rxno][0] |= MG_BIT(31);
> +    if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>     }
> -  EMAC->EMACDMARIS = BIT(7);  // Clear possible RU while processing
> -  EMAC->EMACRXPOLLD = 0;      // and resume RX
> +  EDMAC->EDRRR = MG_BIT(0);  // Receive Descriptors have changed
> +  // If b0 == 0, descriptors were exhausted and probably frames were dropped,
> +  // (27.2.9 RMFCR counts them)
>   }
>
> -struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init, mip_driver_tm4c_tx,
> -                                     mip_driver_rx, mip_driver_tm4c_up};
> +struct mg_tcpip_driver mg_tcpip_driver_ra = {mg_tcpip_driver_ra_init,
> +                                             mg_tcpip_driver_ra_tx, NULL,
> +                                             mg_tcpip_driver_ra_up};
> +
>   #endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "mip/driver_w5500.c"
> +#line 1 "src/drivers/same54.c"
>   #endif
>
>
> -#if MG_ENABLE_MIP
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54
>
> -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> +#include <sam.h>
>
> -static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t addr, bool wr,
> -                      void *buf, size_t len) {
> -  uint8_t *p = (uint8_t *) buf;
> -  uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> -                   (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> -  s->begin(s->spi);
> -  for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> -  for (size_t i = 0; i < len; i++) {
> -    uint8_t r = s->txn(s->spi, p[i]);
> -    if (!wr) p[i] = r;
> -  }
> -  s->end(s->spi);
> +#define ETH_PKT_SIZE 1536  // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 2           // Descriptor size (words)
> +
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
> +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
> +static uint8_t s_txno;                           // Current TX descriptor
> +static uint8_t s_rxno;                           // Current RX descriptor
> +
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
> +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> +
> +#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
> +#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
> +#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
> +
> +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> +  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
> +                        GMAC_MAN_OP(2) |  // Setting the read operation
> +                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) |  // PHY address
> +                        GMAC_MAN_REGA(reg);  // Setting the register
> +  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
> +  return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk;  // Getting the read value
>   }
>
> -// clang-format off
> -static  void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
> -static  void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> -static  void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
> -static  void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
> -static  uint8_t w5500_r1(struct mip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
> -static  uint16_t w5500_r2(struct mip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> -// clang-format on
> +#if 0
> +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) |   // Setting the write operation
> +                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
> +                        GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val);  // Setting the register
> +  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting until the write op is complete
> +}
> +#endif
>
> -static size_t w5500_rx(void *buf, size_t buflen, struct mip_if *ifp) {
> -  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> -  uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2;     // Read recv len
> -  while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2;  // Until it is stable
> -  // printf("RSR: %d\n", (int) n);
> -  if (n > 0) {
> -    uint16_t ptr = w5500_r2(s, W5500_S0, 0x28);  // Get read pointer
> -    n = w5500_r2(s, W5500_RX0, ptr);             // Read frame length
> -    if (n <= len + 2 && n > 1) {
> -      r = (uint16_t) (n - 2);
> -      w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
> +int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
> +  if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
> +    return d->mdc_cr;
> +  } else {
> +    // get MCLK from GCLK_GENERATOR 0
> +    uint32_t div = 512;
> +    uint32_t mclk;
> +    if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
> +      div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
> +      if (div == 0) div = 1;
>       }
> -    w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n));  // Advance read pointer
> -    w5500_w1(s, W5500_S0, 1, 0x40);                     // Sock0 CR -> RECV
> -    // printf("  RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
> +    switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
> +      case GCLK_GENCTRL_SRC_XOSC0_Val:
> +        mclk = 32000000UL; /* 32MHz */
> +        break;
> +      case GCLK_GENCTRL_SRC_XOSC1_Val:
> +        mclk = 32000000UL; /* 32MHz */
> +        break;
> +      case GCLK_GENCTRL_SRC_OSCULP32K_Val:
> +        mclk = 32000UL;
> +        break;
> +      case GCLK_GENCTRL_SRC_XOSC32K_Val:
> +        mclk = 32000UL;
> +        break;
> +      case GCLK_GENCTRL_SRC_DFLL_Val:
> +        mclk = 48000000UL; /* 48MHz */
> +        break;
> +      case GCLK_GENCTRL_SRC_DPLL0_Val:
> +        mclk = 200000000UL; /* 200MHz */
> +        break;
> +      case GCLK_GENCTRL_SRC_DPLL1_Val:
> +        mclk = 200000000UL; /* 200MHz */
> +        break;
> +      default:
> +        mclk = 200000000UL; /* 200MHz */
> +    }
> +
> +    mclk /= div;
> +    uint8_t crs[] = {0, 1, 2, 3, 4, 5};            // GMAC->NCFGR::CLK values
> +    uint8_t dividers[] = {8, 16, 32, 48, 64, 96};  // Respective CLK dividers
> +    for (int i = 0; i < 6; i++) {
> +      if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
> +        return crs[i];
> +      }
> +    }
> +
> +    return 5;
>     }
> -  return r;
>   }
>
> -static size_t w5500_tx(const void *buf, size_t buflen, struct mip_if *ifp) {
> -  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> -  uint16_t n = 0, len = (uint16_t) buflen;
> -  while (n < len) n = w5500_r2(s, W5500_S0, 0x20);      // Wait for space
> -  uint16_t ptr = w5500_r2(s, W5500_S0, 0x24);           // Get write pointer
> -  w5500_wn(s, W5500_TX0, ptr, (void *) buf, len);       // Write data
> -  w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len));  // Advance write pointer
> -  w5500_w1(s, W5500_S0, 1, 0x20);                       // Sock0 CR -> SEND
> -  for (int i = 0; i < 40; i++) {
> -    uint8_t ir = w5500_r1(s, W5500_S0, 2);  // Read S0 IR
> -    if (ir == 0) continue;
> -    // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
> -    w5500_w1(s, W5500_S0, 2, ir);  // Write S0 IR: clear it!
> -    if (ir & 8) len = 0;           // Timeout. Report error
> -    if (ir & (16 | 8)) break;      // Stop on SEND_OK or timeout
> +static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_same54_data *d =
> +      (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
> +  s_ifp = ifp;
> +
> +  MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
> +  MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
> +  GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d));  // Set MDC divider
> +  GMAC_REGS->GMAC_NCR = 0;                                    // Disable RX & TX
> +  GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk;  // Enable MDC & MDIO
> +
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {   // Init TX descriptors
> +    s_txdesc[i][0] = (uint32_t) s_txbuf[i];  // Point to data buffer
> +    s_txdesc[i][1] = MG_BIT(31);             // OWN bit
>     }
> -  return len;
> +  s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30);  // Last tx descriptor - wrap
> +
> +  GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18)  // DMA recv buf 1536
> +                          | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
> +                          GMAC_DCFGR_TXPBMS(1);  // See #2487
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {       // Init RX descriptors
> +    s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];      // Address of the data buffer
> +    s_rxdesc[i][1] = 0;                          // Clear status
> +  }
> +  s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1);  // Last rx descriptor - wrap
> +
> +  GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc;  // about the descriptor addresses
> +  GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc;  // Let the controller know
> +
> +  GMAC_REGS->SA[0].GMAC_SAB =
> +      MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> +  GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
> +
> +  GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk;  // Disable MII, use RMII
> +  GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk | GMAC_NCFGR_MTIHEN_Msk |
> +                           GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
> +  GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
> +                        GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
> +                        GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> +                        GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
> +  GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
> +                        GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
> +  GMAC_REGS->GMAC_IDR = ~0U;  // Disable interrupts, then enable required
> +  GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
> +                        GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
> +                        GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
> +                        GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
> +  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
> +  NVIC_EnableIRQ(GMAC_IRQn);
> +
> +  return true;
>   }
>
> -static bool w5500_init(struct mip_if *ifp) {
> -  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> -  s->end(s->spi);
> -  w5500_w1(s, W5500_CR, 0, 0x80);     // Reset chip: CR -> 0x80
> -  w5500_w1(s, W5500_CR, 0x2e, 0);     // CR PHYCFGR -> reset
> -  w5500_w1(s, W5500_CR, 0x2e, 0xf8);  // CR PHYCFGR -> set
> -  // w5500_wn(s, W5500_CR, 9, s->mac, 6);      // Set source MAC
> -  w5500_w1(s, W5500_S0, 0x1e, 16);          // Sock0 RX buf size
> -  w5500_w1(s, W5500_S0, 0x1f, 16);          // Sock0 TX buf size
> -  w5500_w1(s, W5500_S0, 0, 4);              // Sock0 MR -> MACRAW
> -  w5500_w1(s, W5500_S0, 1, 1);              // Sock0 CR -> OPEN
> -  return w5500_r1(s, W5500_S0, 3) == 0x42;  // Sock0 SR == MACRAW
> +static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t len,
> +                                        struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // Frame is too big
> +  } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
> +    ifp->nerr++;
> +    MG_ERROR(("No free descriptors"));
> +    len = 0;  // All descriptors are busy, fail
> +  } else {
> +    uint32_t status = len | MG_BIT(15);  // Frame length, last chunk
> +    if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30);  // wrap
> +    memcpy(s_txbuf[s_txno], buf, len);                     // Copy data
> +    s_txdesc[s_txno][1] = status;
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> +  }
> +  __DSB();  // Ensure descriptors have been written
> +  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk;  // Enable transmission
> +  return len;
>   }
>
> -static bool w5500_up(struct mip_if *ifp) {
> -  struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
> -  uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> -  return phycfgr & 1;  // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> +static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
> +  uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
> +  bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
> +
> +  // If PHY is ready, update NCFGR accordingly
> +  if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
> +    uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
> +    bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
> +    bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
> +    GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
> +                             ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
> +                            GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
> +  }
> +
> +  return up;
> +}
> +
> +void GMAC_Handler(void);
> +void GMAC_Handler(void) {
> +  uint32_t isr = GMAC_REGS->GMAC_ISR;
> +  uint32_t rsr = GMAC_REGS->GMAC_RSR;
> +  uint32_t tsr = GMAC_REGS->GMAC_TSR;
> +  if (isr & GMAC_ISR_RCOMP_Msk) {
> +    if (rsr & GMAC_ISR_RCOMP_Msk) {
> +      for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> +        if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
> +        size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> +        s_rxdesc[s_rxno][0] &= ~MG_BIT(0);  // Disown
> +        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +      }
> +    }
> +  }
> +
> +  if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk | GMAC_TSR_TXCOMP_Msk |
> +              GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> +              GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
> +    // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
> +    if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= MG_BIT(31);
> +  }
> +
> +  GMAC_REGS->GMAC_RSR = rsr;
> +  GMAC_REGS->GMAC_TSR = tsr;
>   }
>
> -struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, w5500_up};
> +struct mg_tcpip_driver mg_tcpip_driver_same54 = {
> +    mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
> +    mg_tcpip_driver_same54_up};
>   #endif
>
>   #ifdef MG_ENABLE_LINES
> -#line 1 "mip/mip.c"
> +#line 1 "src/drivers/stm32f.c"
>   #endif
>
>
> -#if MG_ENABLE_MIP
> -
> -#define MIP_ETHEMERAL_PORT 49152
> -#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
> -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> -
> -#ifndef MIP_QSIZE
> -#define MIP_QSIZE (16 * 1024)  // Queue size
> -#endif
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> +    MG_ENABLE_DRIVER_STM32F
> +struct stm32f_eth {
> +  volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
> +      MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
> +      MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
> +      MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> +      RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> +      RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> +      RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
> +      PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
> +      DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
> +      DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> +      DMACHRBAR;
> +};
> +#undef ETH
> +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
>
> -#ifndef MIP_TCP_KEEPALIVE_MS
> -#define MIP_TCP_KEEPALIVE_MS 45000  // TCP keep-alive period, ms
> -#endif
> +#define ETH_PKT_SIZE 1540  // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 4           // Descriptor size (words)
>
> -#define MIP_TCP_ACK_MS 150  // Timeout for ACKing
> +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
> +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
> +static uint8_t s_txno;                               // Current TX descriptor
> +static uint8_t s_rxno;                               // Current RX descriptor
>
> -struct connstate {
> -  uint32_t seq, ack;           // TCP seq/ack counters
> -  uint64_t timer;              // TCP keep-alive / ACK timer
> -  uint8_t mac[6];              // Peer MAC address
> -  uint8_t ttype;               // Timer type. 0: ack, 1: keep-alive
> -#define MIP_TTYPE_KEEPALIVE 0  // Connection is idle for long, send keepalive
> -#define MIP_TTYPE_ACK 1        // Peer sent us data, we have to ack it soon
> -  uint8_t tmiss;               // Number of keep-alive misses
> -  struct mg_iobuf raw;         // For TLS only. Incoming raw data
> -};
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
>
> -#pragma pack(push, 1)
> +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> +  ETH->MACMIIAR &= (7 << 2);
> +  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> +  ETH->MACMIIAR |= MG_BIT(0);
> +  while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> +  return ETH->MACMIIDR & 0xffff;
> +}
>
> -struct lcp {
> -  uint8_t addr, ctrl, proto[2], code, id, len[2];
> -};
> +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  ETH->MACMIIDR = val;
> +  ETH->MACMIIAR &= (7 << 2);
> +  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1);
> +  ETH->MACMIIAR |= MG_BIT(0);
> +  while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> +}
>
> -struct eth {
> -  uint8_t dst[6];  // Destination MAC address
> -  uint8_t src[6];  // Source MAC address
> -  uint16_t type;   // Ethernet type
> -};
> +static uint32_t get_hclk(void) {
> +  struct rcc {
> +    volatile uint32_t CR, PLLCFGR, CFGR;
> +  } *rcc = (struct rcc *) 0x40023800;
> +  uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */;
>
> -struct ip {
> -  uint8_t ver;    // Version
> -  uint8_t tos;    // Unused
> -  uint16_t len;   // Length
> -  uint16_t id;    // Unused
> -  uint16_t frag;  // Fragmentation
> -  uint8_t ttl;    // Time to live
> -  uint8_t proto;  // Upper level protocol
> -  uint16_t csum;  // Checksum
> -  uint32_t src;   // Source IP
> -  uint32_t dst;   // Destination IP
> -};
> +  if (rcc->CFGR & (1 << 2)) {
> +    clk = hse;
> +  } else if (rcc->CFGR & (1 << 3)) {
> +    uint32_t vco, m, n, p;
> +    m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> +    n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> +    p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> +    clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> +    vco = (uint32_t) ((uint64_t) clk * n / m);
> +    clk = vco / p;
> +  } else {
> +    clk = hsi;
> +  }
> +  uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> +  if (hpre < 8) return clk;
>
> -struct ip6 {
> -  uint8_t ver;      // Version
> -  uint8_t opts[3];  // Options
> -  uint16_t len;     // Length
> -  uint8_t proto;    // Upper level protocol
> -  uint8_t ttl;      // Time to live
> -  uint8_t src[16];  // Source IP
> -  uint8_t dst[16];  // Destination IP
> -};
> +  uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
> +  return ((uint32_t) clk) >> ahbptab[hpre - 8];
> +}
>
> -struct icmp {
> -  uint8_t type;
> -  uint8_t code;
> -  uint16_t csum;
> -};
> +//  Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3,
> +//  it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived
> +//  from the HSI (internal RC), and it can go above specs, the datasheets
> +//  specify a range of frequencies and activate one of a series of dividers to
> +//  keep the MDC clock safely below 2.5MHz. We guess a divider setting based on
> +//  HCLK with a +5% drift. If the user uses a different clock from our
> +//  defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
> +//  (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
> +static int guess_mdc_cr(void) {
> +  uint8_t crs[] = {2, 3, 0, 1, 4, 5};          // ETH->MACMIIAR::CR values
> +  uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
> +  uint32_t hclk = get_hclk();                  // Guess system HCLK
> +  int result = -1;                             // Invalid CR value
> +  if (hclk < 25000000) {
> +    MG_ERROR(("HCLK too low"));
> +  } else {
> +    for (int i = 0; i < 6; i++) {
> +      if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> +        result = crs[i];
> +        break;
> +      }
> +    }
> +    if (result < 0) MG_ERROR(("HCLK too high"));
> +  }
> +  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> +  return result;
> +}
>
> -struct arp {
> -  uint16_t fmt;    // Format of hardware address
> -  uint16_t pro;    // Format of protocol address
> -  uint8_t hlen;    // Length of hardware address
> -  uint8_t plen;    // Length of protocol address
> -  uint16_t op;     // Operation
> -  uint8_t sha[6];  // Sender hardware address
> -  uint32_t spa;    // Sender protocol address
> -  uint8_t tha[6];  // Target hardware address
> -  uint32_t tpa;    // Target protocol address
> -};
> +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_stm32f_data *d =
> +      (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> +  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> +  s_ifp = ifp;
>
> -struct tcp {
> -  uint16_t sport;  // Source port
> -  uint16_t dport;  // Destination port
> -  uint32_t seq;    // Sequence number
> -  uint32_t ack;    // Acknowledgement number
> -  uint8_t off;     // Data offset
> -  uint8_t flags;   // TCP flags
> -#define TH_FIN 0x01
> -#define TH_SYN 0x02
> -#define TH_RST 0x04
> -#define TH_PUSH 0x08
> -#define TH_ACK 0x10
> -#define TH_URG 0x20
> -#define TH_ECE 0x40
> -#define TH_CWR 0x80
> -  uint16_t win;   // Window
> -  uint16_t csum;  // Checksum
> -  uint16_t urp;   // Urgent pointer
> -};
> +  // Init RX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_rxdesc[i][0] = MG_BIT(31);                         // Own
> +    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14);    // 2nd address chained
> +    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
> +    s_rxdesc[i][3] =
> +        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +  }
>
> -struct udp {
> -  uint16_t sport;  // Source port
> -  uint16_t dport;  // Destination port
> -  uint16_t len;    // UDP length
> -  uint16_t csum;   // UDP checksum
> -};
> +  // Init TX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
> +    s_txdesc[i][3] =
> +        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +  }
>
> -struct dhcp {
> -  uint8_t op, htype, hlen, hops;
> -  uint32_t xid;
> -  uint16_t secs, flags;
> -  uint32_t ciaddr, yiaddr, siaddr, giaddr;
> -  uint8_t hwaddr[208];
> -  uint32_t magic;
> -  uint8_t options[32];
> -};
> +  ETH->DMABMR |= MG_BIT(0);                         // Software reset
> +  while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0;  // Wait until done
>
> -#pragma pack(pop)
> +  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
> +  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> +  ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
>
> -struct pkt {
> -  struct mg_str raw;  // Raw packet data
> -  struct mg_str pay;  // Payload data
> -  struct eth *eth;
> -  struct llc *llc;
> -  struct arp *arp;
> -  struct ip *ip;
> -  struct ip6 *ip6;
> -  struct icmp *icmp;
> -  struct tcp *tcp;
> -  struct udp *udp;
> -  struct dhcp *dhcp;
> -};
> +  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> +  // hardware checksum. Therefore, descriptor size is 4, not 8
> +  // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) |
> +  // MG_BIT(25);
> +  ETH->MACIMR = MG_BIT(3) | MG_BIT(9);  // Mask timestamp & PMT IT
> +  ETH->MACFCR = MG_BIT(7);              // Disable zero quarta pause
> +  // ETH->MACFFR = MG_BIT(31);                            // Receive all
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
> +  ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
> +  ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc;  // RX descriptors
> +  ETH->DMAIER = MG_BIT(6) | MG_BIT(16);             // RIE, NISE
> +  ETH->MACCR =
> +      MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14);  // RE, TE, Duplex, Fast
> +  ETH->DMAOMR =
> +      MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25);  // SR, ST, TSF, RSF
>
> -static void q_copyin(struct queue *q, const uint8_t *buf, size_t len,
> -                     size_t head) {
> -  size_t left = q->len - head;
> -  memcpy(&q->buf[head], buf, left < len ? left : len);
> -  if (left < len) memcpy(q->buf, &buf[left], len - left);
> +  // MAC address filtering
> +  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> +  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> +                 ((uint32_t) ifp->mac[2] << 16) |
> +                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> +  return true;
>   }
>
> -static void q_copyout(struct queue *q, uint8_t *buf, size_t len, size_t tail) {
> -  size_t left = q->len - tail;
> -  memcpy(buf, &q->buf[tail], left < len ? left : len);
> -  if (left < len) memcpy(&buf[left], q->buf, len - left);
> +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t len,
> +                                        struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // Frame is too big
> +  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> +    ifp->nerr++;
> +    MG_ERROR(("No free descriptors"));
> +    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
> +    len = 0;  // All descriptors are busy, fail
> +  } else {
> +    memcpy(s_txbuf[s_txno], buf, len);                           // Copy data
> +    s_txdesc[s_txno][1] = (uint32_t) len;                        // Set data len
> +    s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29);  // Chain,FS,LS
> +    s_txdesc[s_txno][0] |= MG_BIT(31);  // Set OWN bit - let DMA take over
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> +  }
> +  MG_DSB();                            // ensure descriptors have been written
> +  ETH->DMASR = MG_BIT(2) | MG_BIT(5);  // Clear any prior TBUS/TUS
> +  ETH->DMATPDR = 0;                    // and resume
> +  return len;
>   }
>
> -static bool q_write(struct queue *q, const void *buf, size_t len) {
> -  bool success = false;
> -  size_t left = (q->len - q->head + q->tail - 1) % q->len;
> -  if (len + sizeof(size_t) <= left) {
> -    q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
> -    q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) % q->len);
> -    q->head = (q->head + sizeof(len) + len) % q->len;
> -    success = true;
> +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_stm32f_data *d =
> +      (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> +  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    // tmp = reg with flags set to the most likely situation: 100M full-duplex
> +    // if(link is slow or half) set flags otherwise
> +    // reg = tmp
> +    uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11);  // 100M, Full-duplex
> +    if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14);    // 10M
> +    if (full_duplex == false) maccr &= ~MG_BIT(11);         // Half-duplex
> +    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
> +    MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> +              maccr & MG_BIT(11) ? "full" : "half"));
>     }
> -  return success;
> +  return up;
>   }
>
> -#ifdef MIP_QPROFILE
> -static inline size_t q_space(struct queue *q) {
> -  return q->tail > q->head ? q->tail - q->head : q->tail + (q->len - q->head);
> +#ifdef __riscv
> +__attribute__((interrupt()))  // For RISCV CH32V307, which share the same MAC
> +#endif
> +void ETH_IRQHandler(void);
> +void ETH_IRQHandler(void) {
> +  if (ETH->DMASR & MG_BIT(6)) {           // Frame received, loop
> +    ETH->DMASR = MG_BIT(16) | MG_BIT(6);  // Clear flag
> +    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
> +      if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break;  // exit when done
> +      if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
> +           (MG_BIT(8) | MG_BIT(9))) &&
> +          !(s_rxdesc[s_rxno][0] & MG_BIT(15))) {  // skip partial/errored frames
> +        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> +        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> +        //  ETH->DMASR);
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> +      }
> +      s_rxdesc[s_rxno][0] = MG_BIT(31);
> +      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +    }
> +  }
> +  // Cleanup flags
> +  ETH->DMASR = MG_BIT(16)    // NIS, normal interrupt summary
> +               | MG_BIT(7);  // Clear possible RBUS while processing
> +  ETH->DMARPDR = 0;          // and resume RX
>   }
> +
> +struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
> +    mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
> +    mg_tcpip_driver_stm32f_up};
>   #endif
>
> -static inline size_t q_avail(struct queue *q) {
> -  size_t n = 0;
> -  if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n), q->tail);
> -  return n;
> -}
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/stm32h.c"
> +#endif
>
> -static size_t q_read(struct queue *q, void *buf) {
> -  size_t n = q_avail(q);
> -  if (n > 0) {
> -    q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
> -    q->tail = (q->tail + sizeof(n) + n) % q->len;
> -  }
> -  return n;
> -}
>
> -static struct mg_str mkstr(void *buf, size_t len) {
> -  struct mg_str str = {(char *) buf, len};
> -  return str;
> -}
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> +    MG_ENABLE_DRIVER_STM32H
> +struct stm32h_eth {
> +  volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
> +      RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR,
> +      RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR,
> +      MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], MACLCSR,
> +      MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10,
> +      MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR,
> +      RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR,
> +      MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR,
> +      MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR,
> +      RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
> +      RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
> +      MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
> +      RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
> +      RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
> +      MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR,
> +      MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR,
> +      RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28,
> +      MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
> +      RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, MACPPSIR,
> +      MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, MACLMIR,
> +      RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], MTLTQOMR,
> +      MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, MTLRQDR,
> +      RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], DMACCR,
> +      DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
> +      DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
> +      DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR,
> +      RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2],
> +      DMACMFCR;
> +};
> +#undef ETH
> +#define ETH \
> +  ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 0x8000UL))
>
> -static void mkpay(struct pkt *pkt, void *p) {
> -  pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) p));
> +#define ETH_PKT_SIZE 1540  // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 4           // Descriptor size (words)
> +
> +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
> +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];       // RX ethernet buffers
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];       // TX ethernet buffers
> +static struct mg_tcpip_if *s_ifp;                         // MIP interface
> +
> +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> +  ETH->MACMDIOAR &= (0xF << 8);
> +  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2;
> +  ETH->MACMDIOAR |= MG_BIT(0);
> +  while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
> +  return (uint16_t) ETH->MACMDIODR;
>   }
>
> -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> -  const uint8_t *p = (const uint8_t *) buf;
> -  for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
> -  return sum;
> +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  ETH->MACMDIODR = val;
> +  ETH->MACMDIOAR &= (0xF << 8);
> +  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2;
> +  ETH->MACMDIOAR |= MG_BIT(0);
> +  while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
>   }
>
> -static uint16_t csumfin(uint32_t sum) {
> -  while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> -  return mg_htons(~sum & 0xffff);
> +static uint32_t get_hclk(void) {
> +  struct rcc {
> +    volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR,
> +        D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR,
> +        PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R,
> +        D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR,
> +        RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR,
> +        APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9],
> +        RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR,
> +        APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
> +        AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR,
> +        RESERVED13[4];
> +  } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
> +  uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz */,
> +           csi = 4000000 /* 4MHz */;
> +  unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
> +
> +  if (sel == 1) {
> +    clk = csi;
> +  } else if (sel == 2) {
> +    clk = hse;
> +  } else if (sel == 3) {
> +    uint32_t vco, m, n, p;
> +    unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
> +    m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
> +    n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
> +        ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0);  // round-up in fractional mode
> +    p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
> +    if (src == 1) {
> +      clk = csi;
> +    } else if (src == 2) {
> +      clk = hse;
> +    } else {
> +      clk = hsi;
> +      clk >>= ((rcc->CR & 3) >> 3);
> +    }
> +    vco = (uint32_t) ((uint64_t) clk * n / m);
> +    clk = vco / p;
> +  } else {
> +    clk = hsi;
> +    clk >>= ((rcc->CR & 3) >> 3);
> +  }
> +  const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
> +  uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
> +  if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
> +  MG_DEBUG(("D1 CLK: %u", clk));
> +  uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
> +  if (hpre < 8) return clk;
> +  return ((uint32_t) clk) >> cptab[hpre - 8];
> +}
> +
> +//  Guess CR from AHB1 clock. MDC clock is generated from the ETH peripheral
> +//  clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock can
> +//  be derived from HSI or CSI (internal RC) clocks, and those can go above
> +//  specs, the datasheets specify a range of frequencies and activate one of a
> +//  series of dividers to keep the MDC clock safely below 2.5MHz. We guess a
> +//  divider setting based on HCLK with some drift. If the user uses a different
> +//  clock from our defaults, needs to set the macros on top. Valid for
> +//  STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 7.5 %
> +//  worst case drift @ max temp)
> +static int guess_mdc_cr(void) {
> +  const uint8_t crs[] = {2, 3, 0, 1, 4, 5};  // ETH->MACMDIOAR::CR values
> +  const uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
> +  uint32_t hclk = get_hclk();                        // Guess system HCLK
> +  int result = -1;                                   // Invalid CR value
> +  for (int i = 0; i < 6; i++) {
> +    if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> +      result = crs[i];
> +      break;
> +    }
> +  }
> +  if (result < 0) MG_ERROR(("HCLK too high"));
> +  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> +  return result;
>   }
>
> -static uint16_t ipcsum(const void *buf, size_t len) {
> -  uint32_t sum = csumup(0, buf, len);
> -  return csumfin(sum);
> -}
> +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_stm32h_data *d =
> +      (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> +  s_ifp = ifp;
> +  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> +  uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
>
> -// ARP cache is organised as a doubly linked list. A successful cache lookup
> -// moves an entry to the head of the list. New entries are added by replacing
> -// the last entry in the list with a new IP/MAC.
> -// ARP cache format: | prev | next | Entry0 | Entry1 | .... | EntryN |
> -// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
> -// prev and next are 1-byte offsets in the cache, so cache size is max 256 bytes
> -// ARP entry size is 12 bytes
> -static void arp_cache_init(uint8_t *p, int n, int size) {
> -  for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i - 1) * size);
> -  for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i + 1) * size);
> -  p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
> -  p[1] = p[3 + (n - 1) * size] = 2;
> -}
> +  // Init RX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
> +    s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24);  // OWN, IOC, BUF1V
> +  }
>
> -#if 0
> -static inline void arp_cache_dump(const uint8_t *p) {
> -  MG_INFO(("ARP cache:"));
> -  for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
> -    MG_INFO(("  %I -> %A", 4, &p[j + 2], &p[j + 6]));
> +  // Init TX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
>     }
> -}
> -#endif
>
> -static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
> +  ETH->DMAMR |= MG_BIT(0);                         // Software reset
> +  while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0;  // Wait until done
>
> -static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
> -  uint8_t *p = ifp->arp_cache;
> -  if (ip == 0) return NULL;
> -  // use broadcast MAC for local and global broadcast IP
> -  if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
> -    return (uint8_t *) bcastmac;
> -  for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
> -    if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
> -      p[1] = j, p[0] = p[j];  // Found entry! Point list head to us
> -      // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
> -      return p + j + 6;  // And return MAC address
> -    }
> -  }
> -  return NULL;
> -}
> +  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
> +  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> +  ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
> +
> +  // NOTE(scaprile): We do not use timing facilities so the DMA engine does not
> +  // re-write buffer address
> +  ETH->DMAMR = 0 << 16;        // use interrupt mode 0 (58.8.1) (reset value)
> +  ETH->DMASBMR |= MG_BIT(12);  // AAL NOTE(scaprile): is this actually needed
> +  ETH->MACIER = 0;  // Do not enable additional irq sources (reset value)
> +  ETH->MACTFCR = MG_BIT(7);  // Disable zero-quanta pause
> +  // ETH->MACPFR = MG_BIT(31);  // Receive all
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  mg_phy_init(&phy, phy_addr, phy_conf);
> +  ETH->DMACRDLAR =
> +      (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors start address
> +  ETH->DMACRDRLR = ETH_DESC_CNT - 1;    // ring length
> +  ETH->DMACRDTPR =
> +      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
> +                                       1];  // last valid descriptor address
> +  ETH->DMACTDLAR =
> +      (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors start address
> +  ETH->DMACTDRLR = ETH_DESC_CNT - 1;    // ring length
> +  ETH->DMACTDTPR =
> +      (uint32_t) (uintptr_t) s_txdesc;  // first available descriptor address
> +  ETH->DMACCR = 0;  // DSL = 0 (contiguous descriptor table) (reset value)
> +  ETH->DMACIER = MG_BIT(6) | MG_BIT(15);  // RIE, NIE
> +  ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
> +               MG_BIT(15);     // RE, TE, Duplex, Fast, Reserved
> +  ETH->MTLTQOMR |= MG_BIT(1);  // TSF
> +  ETH->MTLRQOMR |= MG_BIT(5);  // RSF
> +  ETH->DMACTCR |= MG_BIT(0);   // ST
> +  ETH->DMACRCR |= MG_BIT(0);   // SR
>
> -static void arp_cache_add(struct mip_if *ifp, uint32_t ip, uint8_t mac[6]) {
> -  uint8_t *p = ifp->arp_cache;
> -  if (ip == 0 || ip == ~0U) return;             // Bad IP
> -  if (arp_cache_find(ifp, ip) != NULL) return;  // Already exists, do nothing
> -  memcpy(p + p[0] + 2, &ip, sizeof(ip));  // Replace last entry: IP address
> -  memcpy(p + p[0] + 6, mac, 6);           // And MAC address
> -  p[1] = p[0], p[0] = p[p[1]];            // Point list head to us
> -  MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
> +  // MAC address filtering
> +  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> +  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> +                 ((uint32_t) ifp->mac[2] << 16) |
> +                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> +  return true;
>   }
>
> -static size_t ether_output(struct mip_if *ifp, size_t len) {
> -  // size_t min = 64;  // Pad short frames to 64 bytes (minimum Ethernet size)
> -  // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len = min;
> -  // mg_hexdump(ifp->tx.ptr, len);
> -  return ifp->driver->tx(ifp->tx.ptr, len, ifp);
> +static uint32_t s_txno;
> +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len,
> +                                        struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // Frame is too big
> +  } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
> +    ifp->nerr++;
> +    MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
> +              s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
> +    for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", s_txdesc[i][3]));
> +    len = 0;  // All descriptors are busy, fail
> +  } else {
> +    memcpy(s_txbuf[s_txno], buf, len);              // Copy data
> +    s_txdesc[s_txno][2] = (uint32_t) len;           // Set data len
> +    s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29);  // FD, LD
> +    s_txdesc[s_txno][3] |= MG_BIT(31);  // Set OWN bit - let DMA take over
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> +  }
> +  ETH->DMACSR |= MG_BIT(2) | MG_BIT(1);  // Clear any prior TBU, TPS
> +  ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno];  // and resume
> +  return len;
> +  (void) ifp;
>   }
>
> -static void arp_ask(struct mip_if *ifp, uint32_t ip) {
> -  struct eth *eth = (struct eth *) ifp->tx.ptr;
> -  struct arp *arp = (struct arp *) (eth + 1);
> -  memset(eth->dst, 255, sizeof(eth->dst));
> -  memcpy(eth->src, ifp->mac, sizeof(eth->src));
> -  eth->type = mg_htons(0x806);
> -  memset(arp, 0, sizeof(*arp));
> -  arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> -  arp->plen = 4;
> -  arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> -  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> -  ether_output(ifp, PDIFF(eth, arp + 1));
> +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_stm32h_data *d =
> +      (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> +  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    // tmp = reg with flags set to the most likely situation: 100M full-duplex
> +    // if(link is slow or half) set flags otherwise
> +    // reg = tmp
> +    uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13);  // 100M, Full-duplex
> +    if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14);    // 10M
> +    if (full_duplex == false) maccr &= ~MG_BIT(13);         // Half-duplex
> +    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
> +    MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> +              maccr & MG_BIT(13) ? "full" : "half"));
> +  }
> +  return up;
>   }
>
> -static void onstatechange(struct mip_if *ifp) {
> -  if (ifp->state == MIP_STATE_READY) {
> -    MG_INFO(("READY, IP: %I", 4, &ifp->ip));
> -    MG_INFO(("       GW: %I", 4, &ifp->gw));
> -    if (ifp->lease_expire > ifp->now) {
> -      MG_INFO(
> -          ("       Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
> +void ETH_IRQHandler(void);
> +static uint32_t s_rxno;
> +void ETH_IRQHandler(void) {
> +  if (ETH->DMACSR & MG_BIT(6)) {           // Frame received, loop
> +    ETH->DMACSR = MG_BIT(15) | MG_BIT(6);  // Clear flag
> +    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
> +      if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break;  // exit when done
> +      if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
> +           (MG_BIT(28) | MG_BIT(29))) &&
> +          !(s_rxdesc[s_rxno][3] & MG_BIT(15))) {  // skip partial/errored frames
> +        uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
> +        // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3],
> +        // ETH->DMACSR));
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> +      }
> +      s_rxdesc[s_rxno][3] =
> +          MG_BIT(31) | MG_BIT(30) | MG_BIT(24);  // OWN, IOC, BUF1V
> +      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>       }
> -    arp_ask(ifp, ifp->gw);
> -  } else if (ifp->state == MIP_STATE_UP) {
> -    MG_ERROR(("Link up"));
> -  } else if (ifp->state == MIP_STATE_DOWN) {
> -    MG_ERROR(("Link down"));
>     }
> +  ETH->DMACSR =
> +      MG_BIT(7) | MG_BIT(8);  // Clear possible RBU RPS while processing
> +  ETH->DMACRDTPR =
> +      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1];  // and resume RX
>   }
>
> -static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto, uint32_t ip_src,
> -                        uint32_t ip_dst, size_t plen) {
> -  struct eth *eth = (struct eth *) ifp->tx.ptr;
> -  struct ip *ip = (struct ip *) (eth + 1);
> -  uint8_t *mac = arp_cache_find(ifp, ip_dst);  // Dst IP in ARP cache ?
> -  if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
> -    arp_ask(ifp, ip_dst);                             // Same net, lookup
> -  if (!mac) mac = arp_cache_find(ifp, ifp->gw);       // Use gateway MAC
> -  if (!mac) arp_ask(ifp, ifp->gw);                    // Not found? lookup
> -  if (mac) memcpy(eth->dst, mac, sizeof(eth->dst));   // Found? Use it
> -  if (!mac) memset(eth->dst, 255, sizeof(eth->dst));  // No? Use broadcast
> -  memcpy(eth->src, ifp->mac, sizeof(eth->src));       // TODO(cpq): ARP lookup
> -  eth->type = mg_htons(0x800);
> -  memset(ip, 0, sizeof(*ip));
> -  ip->ver = 0x45;   // Version 4, header length 5 words
> -  ip->frag = 0x40;  // Don't fragment
> -  ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> -  ip->ttl = 64;
> -  ip->proto = proto;
> -  ip->src = ip_src;
> -  ip->dst = ip_dst;
> -  ip->csum = ipcsum(ip, sizeof(*ip));
> -  return ip;
> -}
> +struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
> +    mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
> +    mg_tcpip_driver_stm32h_up};
> +#endif
>
> -static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t sport,
> -                   uint32_t ip_dst, uint16_t dport, const void *buf,
> -                   size_t len) {
> -  struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len + sizeof(struct udp));
> -  struct udp *udp = (struct udp *) (ip + 1);
> -  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> -  udp->sport = sport;
> -  udp->dport = dport;
> -  udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> -  udp->csum = 0;
> -  uint32_t cs = csumup(0, udp, sizeof(*udp));
> -  cs = csumup(cs, buf, len);
> -  cs = csumup(cs, &ip->src, sizeof(ip->src));
> -  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> -  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> -  udp->csum = csumfin(cs);
> -  memmove(udp + 1, buf, len);
> -  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> -  ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len);
> -}
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/tm4c.c"
> +#endif
>
> -static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
> -                    uint8_t *opts, size_t optslen) {
> -  struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> -  dhcp.magic = mg_htonl(0x63825363);
> -  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> -  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> -  memcpy(&dhcp.options, opts, optslen);
> -  tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp, sizeof(dhcp));
> -}
>
> -static void tx_dhcp_request(struct mip_if *ifp, uint32_t src, uint32_t dst) {
> -  uint8_t opts[] = {
> -      53, 1, 3,                 // Type: DHCP request
> -      55, 2, 1,   3,            // GW and mask
> -      12, 3, 'm', 'i', 'p',     // Host name: "mip"
> -      54, 4, 0,   0,   0,   0,  // DHCP server ID
> -      50, 4, 0,   0,   0,   0,  // Requested IP
> -      255                       // End of options
> -  };
> -  memcpy(opts + 14, &dst, sizeof(dst));
> -  memcpy(opts + 20, &src, sizeof(src));
> -  tx_dhcp(ifp, src, dst, opts, sizeof(opts));
> -}
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
> +struct tm4c_emac {
> +  volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
> +      EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS,
> +      EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
> +      EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
> +      EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> +      EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> +      EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
> +      EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR,
> +      EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> +      EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC,
> +      EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> +      EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> +      RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> +      EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
> +      EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> +      RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
> +      RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> +      EMACEPHYIM, EMACEPHYIMSC;
> +};
> +#undef EMAC
> +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>
> -static void tx_dhcp_discover(struct mip_if *ifp) {
> -  uint8_t opts[] = {
> -      53, 1, 1,     // Type: DHCP discover
> -      55, 2, 1, 3,  // Parameters: ip, mask
> -      255           // End of options
> -  };
> -  tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
> -  MG_DEBUG(("DHCP discover sent"));
> -}
> +#define ETH_PKT_SIZE 1540  // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 4           // Descriptor size (words)
>
> -static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
> -  if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> -    // ARP request. Make a response, then send
> -    MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4, &pkt->arp->spa, 4,
> -              &pkt->arp->tpa));
> -    struct eth *eth = (struct eth *) ifp->tx.ptr;
> -    struct arp *arp = (struct arp *) (eth + 1);
> -    memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> -    memcpy(eth->src, ifp->mac, sizeof(eth->src));
> -    eth->type = mg_htons(0x806);
> -    *arp = *pkt->arp;
> -    arp->op = mg_htons(2);
> -    memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> -    memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> -    arp->tpa = pkt->arp->spa;
> -    arp->spa = ifp->ip;
> -    MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
> -    ether_output(ifp, PDIFF(eth, arp + 1));
> -  } else if (pkt->arp->op == mg_htons(2)) {
> -    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
> -    // MG_INFO(("ARP RESPONSE"));
> -    arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
> -  }
> +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
> +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
> +static struct mg_tcpip_if *s_ifp;                    // MIP interface
> +enum {
> +  EPHY_ADDR = 0,
> +  EPHYBMCR = 0,
> +  EPHYBMSR = 1,
> +  EPHYSTS = 16
> +};  // PHY constants
> +
> +static inline void tm4cspin(volatile uint32_t count) {
> +  while (count--) (void) 0;
>   }
>
> -static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
> -  // MG_DEBUG(("ICMP %d", (int) len));
> -  if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
> -    size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
> -    size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> -    if (plen > space) plen = space;
> -    struct ip *ip =
> -        tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
> -    struct icmp *icmp = (struct icmp *) (ip + 1);
> -    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
> -    memcpy(icmp + 1, pkt->pay.ptr, plen);  // Copy RX payload to TX
> -    icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> -    ether_output(ifp, hlen + plen);
> -  }
> +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> +  EMAC->EMACMIIADDR &= (0xf << 2);
> +  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> +  EMAC->EMACMIIADDR |= MG_BIT(0);
> +  while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
> +  return EMAC->EMACMIIDATA;
>   }
>
> -static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
> -  uint32_t ip = 0, gw = 0, mask = 0;
> -  uint8_t *p = pkt->dhcp->options,
> -          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> -  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> -  while (p + 1 < end && p[0] != 255) {  // Parse options
> -    if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) {  // Mask
> -      memcpy(&mask, p + 2, sizeof(mask));
> -    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) {  // GW
> -      memcpy(&gw, p + 2, sizeof(gw));
> -      ip = pkt->dhcp->yiaddr;
> -    } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) {  // Lease
> -      uint32_t lease = 0;
> -      memcpy(&lease, p + 2, sizeof(lease));
> -      ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
> -    }
> -    p += p[1] + 2;
> -  }
> -  if (ip && mask && gw && ifp->ip == 0) {
> -    arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *) pkt->raw.ptr)->src);
> -    ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> -    ifp->state = MIP_STATE_READY;
> -    onstatechange(ifp);
> -    tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
> -  }
> +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> +  EMAC->EMACMIIDATA = val;
> +  EMAC->EMACMIIADDR &= (0xf << 2);
> +  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1);
> +  EMAC->EMACMIIADDR |= MG_BIT(0);
> +  while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
>   }
>
> -// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> -static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
> -  uint8_t op = 0, *p = pkt->dhcp->options,
> -          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> -  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> -  // struct dhcp *req = pkt->dhcp;
> -  struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> -  res.yiaddr = ifp->ip;
> -  ((uint8_t *) (&res.yiaddr))[3]++;                // Offer our IP + 1
> -  while (p + 1 < end && p[0] != 255) {             // Parse options
> -    if (p[0] == 53 && p[1] == 1 && p + 2 < end) {  // Message type
> -      op = p[2];
> +static uint32_t get_sysclk(void) {
> +  struct sysctl {
> +    volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
> +        PLLFREQ1;
> +  } *sysctl = (struct sysctl *) 0x400FE000;
> +  uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */;
> +  if (sysctl->RSCLKCFG & (1 << 28)) {  // USEPLL
> +    uint32_t fin, vco, mdiv, n, q, psysdiv;
> +    uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> +    if (pllsrc == 0) {
> +      clk = piosc;
> +    } else if (pllsrc == 3) {
> +      clk = mosc;
> +    } else {
> +      MG_ERROR(("Unsupported clock source"));
>       }
> -    p += p[1] + 2;
> -  }
> -  if (op == 1 || op == 3) {         // DHCP Discover or DHCP Request
> -    uint8_t msg = op == 1 ? 2 : 5;  // Message type: DHCP OFFER or DHCP ACK
> -    uint8_t opts[] = {
> -        53, 1, msg,                 // Message type
> -        1,  4, 0,   0,   0,   0,    // Subnet mask
> -        54, 4, 0,   0,   0,   0,    // Server ID
> -        12, 3, 'm', 'i', 'p',       // Host name: "mip"
> -        51, 4, 255, 255, 255, 255,  // Lease time
> -        255                         // End of options
> -    };
> -    memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> -    memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> -    memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> -    memcpy(&res.options, opts, sizeof(opts));
> -    res.magic = pkt->dhcp->magic;
> -    res.xid = pkt->dhcp->xid;
> -    arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
> -    tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr, mg_htons(68),
> -           &res, sizeof(res));
> +    q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> +    n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> +    fin = clk / ((q + 1) * (n + 1));
> +    mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> +           0;  // mint + (mfrac / 1024); MFRAC not supported
> +    psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> +    vco = (uint32_t) ((uint64_t) fin * mdiv);
> +    return vco / (psysdiv + 1);
>     }
> -}
> -
> -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
> -                                     bool lsn) {
> -  struct mg_connection *c = NULL;
> -  for (c = mgr->conns; c != NULL; c = c->next) {
> -    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
> -    if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> -        lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> -      break;
> +  uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> +  if (oscsrc == 0) {
> +    clk = piosc;
> +  } else if (oscsrc == 3) {
> +    clk = mosc;
> +  } else {
> +    MG_ERROR(("Unsupported clock source"));
>     }
> -  return c;
> +  uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> +  return clk / (osysdiv + 1);
>   }
>
> -static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
> -  struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> -  if (c == NULL) {
> -    // No UDP listener on this port. Should send ICMP, but keep silent.
> -  } else if (c != NULL) {
> -    c->rem.port = pkt->udp->sport;
> -    c->rem.ip = pkt->ip->src;
> -    if (c->recv.len >= MG_MAX_RECV_SIZE) {
> -      mg_error(c, "max_recv_buf_size reached");
> -    } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> -               !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> -      mg_error(c, "oom");
> -    } else {
> -      memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
> -      c->recv.len += pkt->pay.len;
> -      mg_call(c, MG_EV_READ, &pkt->pay.len);
> +//  Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per
> +//  802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be
> +//  derived from the PIOSC (internal RC), and it can go above  specs, the
> +//  datasheets specify a range of frequencies and activate one of a series of
> +//  dividers to keep the MDC clock safely below 2.5MHz. We guess a divider
> +//  setting based on SYSCLK with a +5% drift. If the user uses a different clock
> +//  from our defaults, needs to set the macros on top Valid for TM4C129x (20.7)
> +//  (4.5% worst case drift)
> +// The PHY receives the main oscillator (MOSC) (20.3.1)
> +static int guess_mdc_cr(void) {
> +  uint8_t crs[] = {2, 3, 0, 1};      // EMAC->MACMIIAR::CR values
> +  uint8_t div[] = {16, 26, 42, 62};  // Respective HCLK dividers
> +  uint32_t sysclk = get_sysclk();    // Guess system SYSCLK
> +  int result = -1;                   // Invalid CR value
> +  if (sysclk < 25000000) {
> +    MG_ERROR(("SYSCLK too low"));
> +  } else {
> +    for (int i = 0; i < 4; i++) {
> +      if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> +        result = crs[i];
> +        break;
> +      }
>       }
> +    if (result < 0) MG_ERROR(("SYSCLK too high"));
>     }
> +  MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> +  return result;
>   }
>
> -static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip, uint8_t flags,
> -                     uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
> -                     const void *buf, size_t len) {
> -  struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
> -  struct tcp *tcp = (struct tcp *) (ip + 1);
> -  memset(tcp, 0, sizeof(*tcp));
> -  if (buf != NULL && len) memmove(tcp + 1, buf, len);
> -  tcp->sport = sport;
> -  tcp->dport = dport;
> -  tcp->seq = seq;
> -  tcp->ack = ack;
> -  tcp->flags = flags;
> -  tcp->win = mg_htons(8192);
> -  tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> -  uint32_t cs = 0;
> -  uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> -  uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
> -  cs = csumup(cs, tcp, n);
> -  cs = csumup(cs, &ip->src, sizeof(ip->src));
> -  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> -  cs = csumup(cs, pseudo, sizeof(pseudo));
> -  tcp->csum = csumfin(cs);
> -  return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
> -}
> +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_tm4c_data *d =
> +      (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
> +  s_ifp = ifp;
>
> -static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt, uint8_t flags,
> -                         uint32_t seq, const void *buf, size_t len) {
> -  uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> -  return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport, pkt->tcp->sport, seq,
> -                mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
> -}
> +  // Init RX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_rxdesc[i][0] = MG_BIT(31);                            // Own
> +    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14);       // 2nd address chained
> +    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
> +    s_rxdesc[i][3] =
> +        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +    // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> +  }
>
> -static void settmout(struct mg_connection *c, uint8_t type) {
> -  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS : MIP_TCP_KEEPALIVE_MS;
> -  s->timer = ifp->now + n;
> -  s->ttype = type;
> -  MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> -}
> +  // Init TX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
> +    s_txdesc[i][3] =
> +        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
> +  }
>
> -static struct mg_connection *accept_conn(struct mg_connection *lsn,
> -                                         struct pkt *pkt) {
> -  struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
> -  settmout(c, MIP_TTYPE_KEEPALIVE);
> -  c->rem.ip = pkt->ip->src;
> -  c->rem.port = pkt->tcp->sport;
> -  MG_DEBUG(
> -      ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip, mg_ntohs(c->rem.port)));
> -  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> -  c->is_accepted = 1;
> -  c->is_hexdumping = lsn->is_hexdumping;
> -  c->pfn = lsn->pfn;
> -  c->loc = lsn->loc;
> -  c->pfn_data = lsn->pfn_data;
> -  c->fn = lsn->fn;
> -  c->fn_data = lsn->fn_data;
> -  mg_call(c, MG_EV_OPEN, NULL);
> -  mg_call(c, MG_EV_ACCEPT, NULL);
> -  return c;
> +  EMAC->EMACDMABUSMOD |= MG_BIT(0);                            // Software reset
> +  while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1);  // Wait until done
> +
> +  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
> +  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> +  EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> +
> +  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> +  // hardware checksum. Therefore, descriptor size is 4, not 8
> +  // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | MG_BIT(25);
> +  EMAC->EMACIM = MG_BIT(3) | MG_BIT(9);  // Mask timestamp & PMT IT
> +  EMAC->EMACFLOWCTL = MG_BIT(7);      // Disable zero-quanta pause
> +  // EMAC->EMACFRAMEFLTR = MG_BIT(31);   // Receive all
> +  // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> +  emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15));  // Reset internal PHY (EPHY)
> +  emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12));  // Set autonegotiation
> +  EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
> +  EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors
> +  EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16);                    // RIE, NIE
> +  EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14);   // RE, TE, Duplex, Fast
> +  EMAC->EMACDMAOPMODE =
> +      MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25);  // SR, ST, TSF, RSF
> +  EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> +  EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> +                     ((uint32_t) ifp->mac[2] << 16) |
> +                     ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> +  // NOTE(scaprile) There are 3 additional slots for filtering, disabled by
> +  // default. This also applies to the STM32 driver (at least for F7)
> +  return true;
>   }
>
> -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> -  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
> -  if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len - max_headers_len;
> -  if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
> -             mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
> -    s->seq += (uint32_t) len;
> -    if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> +static uint32_t s_txno;
> +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
> +                                      struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // fail
> +  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> +    ifp->nerr++;
> +    MG_ERROR(("No descriptors available"));
> +    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> +    // EMAC->EMACDMARIS);
> +    len = 0;  // fail
>     } else {
> -    return MG_IO_ERR;
> +    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
> +    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
> +    s_txdesc[s_txno][0] =
> +        MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30);  // Chain,FS,LS,IC
> +    s_txdesc[s_txno][0] |= MG_BIT(31);  // Set OWN bit - let DMA take over
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>     }
> -  return (long) len;
> +  EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5);  // Clear any prior TU/UNF
> +  EMAC->EMACTXPOLLD = 0;               // and resume
> +  return len;
> +  (void) ifp;
>   }
>
> -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  if (s->raw.len == 0) return MG_IO_WAIT;
> -  if (len > s->raw.len) len = s->raw.len;
> -  memcpy(buf, s->raw.buf, len);
> -  mg_iobuf_del(&s->raw, 0, len);
> -  MG_DEBUG(("%lu", len));
> -  return (long) len;
> +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
> +  uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> +  bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
> +    // tmp = reg with flags set to the most likely situation: 100M full-duplex
> +    // if(link is slow or half) set flags otherwise
> +    // reg = tmp
> +    uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11);  // 100M, Full-duplex
> +    if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14);                 // 10M
> +    if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11);          // Half-duplex
> +    EMAC->EMACCFG = emaccfg;  // IRQ handler does not fiddle with this register
> +    MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10,
> +              emaccfg & MG_BIT(11) ? "full" : "half"));
> +  }
> +  return up;
>   }
>
> -static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
> -  uint32_t seq = mg_ntohl(pkt->tcp->seq);
> -  s->raw.align = c->recv.align;
> -  if (pkt->tcp->flags & TH_FIN) {
> -    s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq = mg_htonl(pkt->tcp->ack);
> -    c->is_closing = 1;
> -  } else if (pkt->pay.len == 0) {
> -    // TODO(cpq): handle this peer's ACK
> -  } else if (seq != s->ack) {
> -    // TODO(cpq): peer sent us SEQ which we don't expect. Retransmit rather
> -    // than close this connection
> -    mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
> -  } else if (io->size - io->len < pkt->pay.len &&
> -             !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> -    mg_error(c, "oom");
> -  } else {
> -    // Copy TCP payload into the IO buffer. If the connection is plain text, we
> -    // copy to c->recv. If the connection is TLS, this data is encrypted,
> -    // therefore we copy that encrypted data to the s->raw iobuffer instead,
> -    // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
> -    // call back mg_io_recv() which grabs raw data from s->raw
> -    memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
> -    io->len += pkt->pay.len;
> -
> -    MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> -    // Advance ACK counter
> -    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> -#if 0
> -    // Send ACK immediately
> -    MG_DEBUG(("  imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> -    tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK, c->loc.port,
> -           c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> -#else
> -    // if not already running, setup a timer to send an ACK later
> -    if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> -#endif
> -
> -    if (c->is_tls) {
> -      // TLS connection. Make room for decrypted data in c->recv
> -      io = &c->recv;
> -      if (io->size - io->len < pkt->pay.len &&
> -          !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> -        mg_error(c, "oom");
> -      } else {
> -        // Decrypt data directly into c->recv
> -        long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> -        if (n == MG_IO_ERR) {
> -          mg_error(c, "TLS recv error");
> -        } else if (n > 0) {
> -          // Decrypted successfully - trigger MG_EV_READ
> -          io->len += (size_t) n;
> -          mg_call(c, MG_EV_READ, &n);
> -        }
> +void EMAC0_IRQHandler(void);
> +static uint32_t s_rxno;
> +void EMAC0_IRQHandler(void) {
> +  if (EMAC->EMACDMARIS & MG_BIT(6)) {        // Frame received, loop
> +    EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6);  // Clear flag
> +    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
> +      if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break;  // exit when done
> +      if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | MG_BIT(9))) &&
> +          !(s_rxdesc[s_rxno][0] & MG_BIT(15))) {  // skip partial/errored frames
> +        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> +        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> +        //  EMAC->EMACDMARIS);
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>         }
> -    } else {
> -      // Plain text connection, data is already in c->recv, trigger MG_EV_READ
> -      mg_call(c, MG_EV_READ, &pkt->pay.len);
> +      s_rxdesc[s_rxno][0] = MG_BIT(31);
> +      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>       }
>     }
> +  EMAC->EMACDMARIS = MG_BIT(7);  // Clear possible RU while processing
> +  EMAC->EMACRXPOLLD = 0;      // and resume RX
>   }
>
> -static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
> -  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> -  struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
> -#if 0
> -  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
> +struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init,
> +                                               mg_tcpip_driver_tm4c_tx, NULL,
> +                                               mg_tcpip_driver_tm4c_up};
>   #endif
> -  if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN | TH_ACK)) {
> -    s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
> -    tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> -    c->is_connecting = 0;             // Client connected
> -    settmout(c, MIP_TTYPE_KEEPALIVE);
> -    mg_call(c, MG_EV_CONNECT, NULL);  // Let user know
> -  } else if (c != NULL && c->is_connecting) {
> -    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> -  } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> -    mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
> -  } else if (c != NULL) {
> -#if 0
> -    MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
> -              4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> -              4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> -    mg_hexdump(pkt->pay.buf, pkt->pay.len);
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/w5500.c"
>   #endif
> -    s->tmiss = 0;                      // Reset missed keep-alive counter
> -    if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
> -      settmout(c, MIP_TTYPE_KEEPALIVE);  // unless a former ACK timeout is pending
> -    read_conn(c, pkt);                 // Override timer with ACK timeout if needed
> -  } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> -    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> -  } else if (pkt->tcp->flags & TH_RST) {
> -    if (c->is_accepted) mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
> -    // ignore RST if not connected
> -  } else if (pkt->tcp->flags & TH_SYN) {
> -    // Use peer's source port as ISN, in order to recognise the handshake
> -    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> -    tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> -  } else if (pkt->tcp->flags & TH_FIN) {
> -    tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> -  } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
> -    accept_conn(c, pkt);
> -  } else if (!c->is_accepted ) {  // no peer
> -    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> -  } else {
> -    // MG_DEBUG(("dropped silently.."));
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500
> +
> +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> +
> +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr,
> +                      bool wr, void *buf, size_t len) {
> +  size_t i;
> +  uint8_t *p = (uint8_t *) buf;
> +  uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> +                   (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> +  s->begin(s->spi);
> +  for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> +  for (i = 0; i < len; i++) {
> +    uint8_t r = s->txn(s->spi, p[i]);
> +    if (!wr) p[i] = r;
>     }
> +  s->end(s->spi);
>   }
>
> -static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
> -  //  MG_DEBUG(("IP %d", (int) pkt->pay.len));
> -  if (pkt->ip->proto == 1) {
> -    pkt->icmp = (struct icmp *) (pkt->ip + 1);
> -    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> -    mkpay(pkt, pkt->icmp + 1);
> -    rx_icmp(ifp, pkt);
> -  } else if (pkt->ip->proto == 17) {
> -    pkt->udp = (struct udp *) (pkt->ip + 1);
> -    if (pkt->pay.len < sizeof(*pkt->udp)) return;
> -    mkpay(pkt, pkt->udp + 1);
> -    if (pkt->udp->dport == mg_htons(68)) {
> -      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> -      mkpay(pkt, pkt->dhcp + 1);
> -      rx_dhcp_client(ifp, pkt);
> -    } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) {
> -      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> -      mkpay(pkt, pkt->dhcp + 1);
> -      rx_dhcp_server(ifp, pkt);
> -    } else {
> -      rx_udp(ifp, pkt);
> +// clang-format off
> +static  void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
> +static  void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> +static  void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
> +static  void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
> +static  uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
> +static  uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> +// clang-format on
> +
> +static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> +  uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2;     // Read recv len
> +  while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2;  // Until it is stable
> +  // printf("RSR: %d\n", (int) n);
> +  if (n > 0) {
> +    uint16_t ptr = w5500_r2(s, W5500_S0, 0x28);  // Get read pointer
> +    n = w5500_r2(s, W5500_RX0, ptr);             // Read frame length
> +    if (n <= len + 2 && n > 1) {
> +      r = (uint16_t) (n - 2);
> +      w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
>       }
> -  } else if (pkt->ip->proto == 6) {
> -    pkt->tcp = (struct tcp *) (pkt->ip + 1);
> -    if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> -    mkpay(pkt, pkt->tcp + 1);
> -    uint16_t iplen = mg_ntohs(pkt->ip->len);
> -    uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
> -    if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> -    rx_tcp(ifp, pkt);
> +    w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n));  // Advance read pointer
> +    w5500_w1(s, W5500_S0, 1, 0x40);                     // Sock0 CR -> RECV
> +    // printf("  RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
>     }
> +  return r;
>   }
>
> -static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
> -  // MG_DEBUG(("IP %d", (int) len));
> -  if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> -    pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> -    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> -    mkpay(pkt, pkt->icmp + 1);
> -    rx_icmp(ifp, pkt);
> -  } else if (pkt->ip6->proto == 17) {
> -    pkt->udp = (struct udp *) (pkt->ip6 + 1);
> -    if (pkt->pay.len < sizeof(*pkt->udp)) return;
> -    // MG_DEBUG(("  UDP %u %u -> %u", len, mg_htons(udp->sport),
> -    // mg_htons(udp->dport)));
> -    mkpay(pkt, pkt->udp + 1);
> +static size_t w5500_tx(const void *buf, size_t buflen,
> +                       struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> +  uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
> +  while (n < len) n = w5500_r2(s, W5500_S0, 0x20);      // Wait for space
> +  ptr = w5500_r2(s, W5500_S0, 0x24);                    // Get write pointer
> +  w5500_wn(s, W5500_TX0, ptr, (void *) buf, len);       // Write data
> +  w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len));  // Advance write pointer
> +  w5500_w1(s, W5500_S0, 1, 0x20);                       // Sock0 CR -> SEND
> +  for (i = 0; i < 40; i++) {
> +    uint8_t ir = w5500_r1(s, W5500_S0, 2);  // Read S0 IR
> +    if (ir == 0) continue;
> +    // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
> +    w5500_w1(s, W5500_S0, 2, ir);  // Write S0 IR: clear it!
> +    if (ir & 8) len = 0;           // Timeout. Report error
> +    if (ir & (16 | 8)) break;      // Stop on SEND_OK or timeout
>     }
> +  return len;
>   }
>
> -static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
> -  const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> -  struct pkt pkt;
> -  memset(&pkt, 0, sizeof(pkt));
> -  pkt.raw.ptr = (char *) buf;
> -  pkt.raw.len = len;
> -  pkt.eth = (struct eth *) buf;
> -  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
> -  if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> -      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
> -    // Not for us. Drop silently
> -  } else if (pkt.eth->type == mg_htons(0x806)) {
> -    pkt.arp = (struct arp *) (pkt.eth + 1);
> -    if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;  // Truncated
> -    rx_arp(ifp, &pkt);
> -  } else if (pkt.eth->type == mg_htons(0x86dd)) {
> -    pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> -    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;  // Truncated
> -    if ((pkt.ip6->ver >> 4) != 0x6) return;                         // Not IP
> -    mkpay(&pkt, pkt.ip6 + 1);
> -    rx_ip6(ifp, &pkt);
> -  } else if (pkt.eth->type == mg_htons(0x800)) {
> -    pkt.ip = (struct ip *) (pkt.eth + 1);
> -    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
> -    // Truncate frame to what IP header tells us
> -    if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
> -      pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> -    }
> -    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
> -    if ((pkt.ip->ver >> 4) != 4) return;                           // Not IP
> -    mkpay(&pkt, pkt.ip + 1);
> -    rx_ip(ifp, &pkt);
> -  } else {
> -    MG_DEBUG(("  Unknown eth type %x", mg_htons(pkt.eth->type)));
> +static bool w5500_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> +  s->end(s->spi);
> +  w5500_w1(s, W5500_CR, 0, 0x80);     // Reset chip: CR -> 0x80
> +  w5500_w1(s, W5500_CR, 0x2e, 0);     // CR PHYCFGR -> reset
> +  w5500_w1(s, W5500_CR, 0x2e, 0xf8);  // CR PHYCFGR -> set
> +  // w5500_wn(s, W5500_CR, 9, s->mac, 6);      // Set source MAC
> +  w5500_w1(s, W5500_S0, 0x1e, 16);          // Sock0 RX buf size
> +  w5500_w1(s, W5500_S0, 0x1f, 16);          // Sock0 TX buf size
> +  w5500_w1(s, W5500_S0, 0, 4);              // Sock0 MR -> MACRAW
> +  w5500_w1(s, W5500_S0, 1, 1);              // Sock0 CR -> OPEN
> +  return w5500_r1(s, W5500_S0, 3) == 0x42;  // Sock0 SR == MACRAW
> +}
> +
> +static bool w5500_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data;
> +  uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> +  return phycfgr & 1;  // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> +}
> +
> +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx,
> +                                                w5500_up};
> +#endif
> +
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/xmc.c"
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC
> +
> +struct ETH_GLOBAL_TypeDef {
> +  volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH,
> +  HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION,
> +  DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2],
> +  INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW,
> +  MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW,
> +  MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
> +  MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK,
> +  MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
> +  RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
> +  RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30],
> +  RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
> +  SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
> +  SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
> +  TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
> +  SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
> +  PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
> +  RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
> +  TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
> +  INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
> +  RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
> +  RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
> +  CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
> +  CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
> +};
> +
> +#undef ETH0
> +#define ETH0  ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
> +
> +#define ETH_PKT_SIZE 1536 // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 4           // Descriptor size (words)
> +
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
> +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
> +static uint8_t s_txno;                           // Current TX descriptor
> +static uint8_t s_rxno;                           // Current RX descriptor
> +
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
> +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> +
> +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> +  ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> +                        ((uint32_t)addr << 11) |
> +                        ((uint32_t)reg << 6) | 1;
> +  while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> +  return (uint16_t)(ETH0->GMII_DATA & 0xffff);
> +}
> +
> +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  ETH0->GMII_DATA  = val;
> +  ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> +                        ((uint32_t)addr << 11) |
> +                        ((uint32_t)reg << 6) | 3;
> +  while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> +}
> +
> +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data *d) {
> +  if (d->mdc_cr == -1) {
> +    // assume ETH clock is 60MHz by default
> +    // then according to 13.2.8.1, we need to set value 3
> +    return 3;
>     }
> +
> +  return d->mdc_cr;
>   }
>
> -static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
> -  if (ifp == NULL || ifp->driver == NULL) return;
> -  bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, uptime_ms);
> -  ifp->now = uptime_ms;
> +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_xmc_data *d =
> +      (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> +  s_ifp = ifp;
>
> -  // Handle physical interface up/down status
> -  if (expired_1000ms && ifp->driver->up) {
> -    bool up = ifp->driver->up(ifp);
> -    bool current = ifp->state != MIP_STATE_DOWN;
> -    if (up != current) {
> -      ifp->state = up == false               ? MIP_STATE_DOWN
> -                   : ifp->enable_dhcp_client ? MIP_STATE_UP
> -                                             : MIP_STATE_READY;
> -      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> -      onstatechange(ifp);
> +  // reset MAC
> +  ETH0->BUS_MODE |= 1;
> +  while (ETH0->BUS_MODE & 1) (void) 0;
> +
> +  // set clock rate
> +  ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
> +
> +  // init phy
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> +
> +  // configure MAC: DO, DM, FES, TC
> +  ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14) | MG_BIT(24);
> +
> +  // set the MAC address
> +  ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
> +  ETH0->MAC_ADDRESS0_LOW =
> +        MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> +
> +  // Configure the receive filter
> +  ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
> +  // Disable flow control
> +  ETH0->FLOW_CONTROL = 0;
> +  // Enable store and forward mode
> +  ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
> +
> +  // Configure DMA bus mode (AAL, USP, RPBL, PBL)
> +  ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) |  (32 << 8);
> +
> +  // init RX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
> +    s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
> +    s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
> +    if (i == ETH_DESC_CNT - 1) {
> +      s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
> +    } else {
> +      s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
>       }
>     }
> -  if (ifp->state == MIP_STATE_DOWN) return;
> -  // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
> +  ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0];
>
> -  if (ifp->ip == 0 && expired_1000ms) {
> -    tx_dhcp_discover(ifp);  // If IP not configured, send DHCP
> -  } else if (ifp->enable_dhcp_client == false && expired_1000ms && ifp->gw &&
> -             arp_cache_find(ifp, ifp->gw) == NULL) {
> -    arp_ask(ifp, ifp->gw);  // If GW's MAC address in not in ARP cache
> +  // init TX descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
> +    s_txdesc[i][2] = (uint32_t) s_txbuf[i];
> +    if (i == ETH_DESC_CNT - 1) {
> +      s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
> +    } else {
> +      s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
> +    }
>     }
> +  ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_txdesc[0][0];
>
> -  // Read data from the network
> -  size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len, ifp);
> -  mip_rx(ifp, (void *) ifp->rx.ptr, len);
> -  qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
> +  // Clear interrupts
> +  ETH0->STATUS = 0xFFFFFFFF;
>
> -  // Process timeouts
> -  for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c = c->next) {
> -    if (c->is_udp || c->is_listening) continue;
> -    if (c->is_connecting || c->is_resolving) continue;
> -    struct connstate *s = (struct connstate *) (c + 1);
> -    if (uptime_ms > s->timer) {
> -      if (s->ttype == MIP_TTYPE_ACK) {
> -        MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
> -        tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> -               mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> -      } else {
> -        if (s->tmiss++ > 2) {
> -          mg_error(c, "keepalive");
> -        } else {
> -          MG_DEBUG(("%lu keepalive", c->id));
> -          tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> -                 mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
> -        }
> -      }
> -      settmout(c, MIP_TTYPE_KEEPALIVE);
> -    }
> -  }
> -#ifdef MIP_QPROFILE
> -  qp_log();
> -#endif
> +  // Disable MAC interrupts
> +  ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
> +  ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> +  ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> +  ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
> +
> +  //Enable interrupts (NIE, RIE, TIE)
> +  ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
> +
> +  // Enable MAC transmission and reception (TE, RE)
> +  ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
> +  // Enable DMA transmission and reception (ST, SR)
> +  ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
> +  return true;
>   }
>
> -// This function executes in interrupt context, thus it should copy data
> -// somewhere fast. Note that newlib's malloc is not thread safe, thus use
> -// our lock-free queue with preallocated buffer to copy data and return asap
> -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
> -  if (q_write(&ifp->queue, buf, len)) {
> -    qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
> +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
> +                                        struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // Frame is too big
> +  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> +    ifp->nerr++;
> +    MG_ERROR(("No free descriptors"));
> +    len = 0;  // All descriptors are busy, fail
>     } else {
> -    ifp->dropped++;
> -    qp_mark(QP_FRAMEDROPPED, ifp->dropped);
> -    MG_ERROR(("dropped %d", (int) len));
> +    memcpy(s_txbuf[s_txno], buf, len);
> +    s_txdesc[s_txno][1] = len;
> +    // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
> +    s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) | MG_BIT(20);
> +    s_txdesc[s_txno][0] |= MG_BIT(31);  // OWN bit: handle control to DMA
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>     }
> -}
>
> -size_t mip_qread(void *buf, struct mip_if *ifp) {
> -  size_t len = q_read(&ifp->queue, buf);
> -  qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
> +  // Resume processing
> +  ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
> +  ETH0->TRANSMIT_POLL_DEMAND = 0;
>     return len;
>   }
>
> -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
> -  return mip_qread((void *) ifp->rx.ptr, ifp);
> -  (void) len, (void) buf;
> -}
> +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_xmc_data *d =
> +      (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 100,
> +              full_duplex ? "full" : "half"));
> +  }
> +  return up;
> +}
> +
> +void ETH0_IRQHandler(void);
> +void ETH0_IRQHandler(void) {
> +  uint32_t irq_status = ETH0->STATUS;
> +
> +  // check if a frame was received
> +  if (irq_status & MG_BIT(6)) {
> +    for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> +      if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
> +        size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> +        s_rxdesc[s_rxno][0] = MG_BIT(31);   // OWN bit: handle control to DMA
> +        // Resume processing
> +        ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
> +        ETH0->RECEIVE_POLL_DEMAND = 0;
> +        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +      }
> +    }
> +    ETH0->STATUS = MG_BIT(6);
> +  }
>
> -void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
> -  if (ifp->driver->init && !ifp->driver->init(ifp)) {
> -    MG_ERROR(("driver init failed"));
> -  } else {
> -    size_t maxpktsize = 1540;
> -    ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len = maxpktsize;
> -    ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len = maxpktsize;
> -    if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1, ifp->queue.len);
> -    ifp->timer_1000ms = mg_millis();
> -    arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
> -    mgr->priv = ifp;
> -    ifp->mgr = mgr;
> -    mgr->extraconnsize = sizeof(struct connstate);
> -    if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> -#ifdef MIP_QPROFILE
> -    qp_init();
> -#endif
> +  // clear Successful transmission interrupt
> +  if (irq_status & 1) {
> +    ETH0->STATUS = 1;
>     }
> -}
>
> -void mip_free(struct mip_if *ifp) {
> -  free((char *) ifp->rx.ptr);
> -  free((char *) ifp->tx.ptr);
> +  // clear normal interrupt
> +  if (irq_status & MG_BIT(16)) {
> +    ETH0->STATUS = MG_BIT(16);
> +  }
>   }
>
> -int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d, bool udp) {
> -  (void) m, (void) fn, (void) d, (void) udp;
> -  MG_ERROR(("Not implemented"));
> -  return -1;
> -}
> +struct mg_tcpip_driver mg_tcpip_driver_xmc = {
> +    mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
> +    mg_tcpip_driver_xmc_up};
> +#endif
>
> -#if 0
> -static uint16_t mkeport(void) {
> -  uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
> -  mg_random(&a, sizeof(a));
> -  c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
> -  return c;
> -}
> +#ifdef MG_ENABLE_LINES
> +#line 1 "src/drivers/xmc7.c"
>   #endif
>
> -void mg_connect_resolved(struct mg_connection *c) {
> -  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> -  c->is_resolving = 0;
> -  if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport = MIP_ETHEMERAL_PORT;
> -  c->loc.ip = ifp->ip;
> -  c->loc.port = mg_htons(ifp->eport++);
> -  MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip, mg_ntohs(c->loc.port),
> -            4, &c->rem.ip, mg_ntohs(c->rem.port)));
> -  mg_call(c, MG_EV_RESOLVE, NULL);
> -  if (c->is_udp) {
> -    mg_call(c, MG_EV_CONNECT, NULL);
> -  } else {
> -    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> -    tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, 0);
> -    c->is_connecting = 1;
> -  }
> -}
>
> -bool mg_open_listener(struct mg_connection *c, const char *url) {
> -  c->loc.port = mg_htons(mg_url_port(url));
> -  return true;
> -}
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7
> +
> +struct ETH_Type {
> +  volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
> +      NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
> +      TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
> +      INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT, PAUSE_TIME,
> +      TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU, JUMBO_MAX_LENGTH,
> +      EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
> +      INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM, HASH_TOP,
> +      SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
> +      SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
> +      SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
> +      STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
> +      MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
> +      TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
> +      TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
> +      DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
> +      FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
> +      FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
> +      FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
> +      SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
> +      LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
> +      OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
> +      PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65, FRAMES_RXED_128,
> +      FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024, FRAMES_RXED_1519,
> +      UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
> +      RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS, RX_RESOURCE_ERRORS,
> +      RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
> +      AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC, TSU_TIMER_MSB_SEC,
> +      TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC, TSU_TIMER_SEC,
> +      TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
> +      TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
> +      TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
> +      PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
> +      PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS, RESERVED6[8],
> +      TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3, RESERVED7,
> +      RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
> +      DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4, DESIGNCFG_DEBUG5,
> +      DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8, DESIGNCFG_DEBUG9,
> +      DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
> +      RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
> +      INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS, RESERVED11,
> +      TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
> +      TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
> +      RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
> +      DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3, RESERVED16[3],
> +      DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A, CBS_IDLESLOPE_Q_B,
> +      UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL, UPPER_RX_Q_BASE_ADDR,
> +      RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
> +      RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
> +};
>
> -static void write_conn(struct mg_connection *c) {
> -  long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> -                       : mg_io_send(c, c->send.buf, c->send.len);
> -  if (len > 0) {
> -    mg_iobuf_del(&c->send, 0, (size_t) len);
> -    mg_call(c, MG_EV_WRITE, &len);
> -  }
> +#define ETH0 ((struct ETH_Type *) 0x40490000)
> +
> +#define ETH_PKT_SIZE 1536  // Max frame size
> +#define ETH_DESC_CNT 4     // Descriptors count
> +#define ETH_DS 2           // Descriptor size (words)
> +
> +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
> +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
> +static uint8_t s_txno;                           // Current TX descriptor
> +static uint8_t s_rxno;                           // Current RX descriptor
> +
> +static struct mg_tcpip_if *s_ifp;  // MIP interface
> +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> +
> +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> +  // WRITE1, READ OPERATION, PHY, REG, WRITE10
> +  ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf) << 24) |
> +                         ((reg & 0x1f) << 18) | MG_BIT(17);
> +  while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
> +  return ETH0->PHY_MANAGEMENT & 0xffff;
>   }
>
> -static void close_conn(struct mg_connection *c) {
> -  struct connstate *s = (struct connstate *) (c + 1);
> -  mg_iobuf_free(&s->raw);  // For TLS connections, release raw data
> -  if (c->is_udp == false && c->is_listening == false) {   // For TCP conns,
> -    struct mip_if *ifp = (struct mip_if *) c->mgr->priv;  // send TCP FIN
> -    tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
> -           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> -  }
> -  mg_close_conn(c);
> +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> +  ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf) << 24) |
> +                         ((reg & 0x1f) << 18) | MG_BIT(17) | val;
> +  while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
>   }
>
> -static bool can_write(struct mg_connection *c) {
> -  return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
> -         c->is_tls_hs == 0;
> +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) {
> +  // see ETH0 -> NETWORK_CONFIG register
> +  (void) d;
> +  return 3;
>   }
>
> -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> -  struct mg_connection *c, *tmp;
> -  uint64_t now = mg_millis();
> -  mip_poll((struct mip_if *) mgr->priv, now);
> -  mg_timer_poll(&mgr->timers, now);
> -  for (c = mgr->conns; c != NULL; c = tmp) {
> -    tmp = c->next;
> -    mg_call(c, MG_EV_POLL, &now);
> -    MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> -                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> -                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> -    if (c->is_tls_hs) mg_tls_handshake(c);
> -    if (can_write(c)) write_conn(c);
> -    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> -    if (c->is_closing) close_conn(c);
> +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_xmc7_data *d =
> +      (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> +  s_ifp = ifp;
> +
> +  // enable controller, set RGMII mode
> +  ETH0->CTL = MG_BIT(31) | 2;
> +
> +  uint32_t cr = get_clock_rate(d);
> +  // set NSP change, ignore RX FCS, data bus width, clock rate
> +  // frame length 1536, full duplex, speed
> +  ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
> +                         ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
> +                         MG_BIT(1) | MG_BIT(0);
> +
> +  // config DMA settings: Force TX burst, Discard on Error, set RX buffer size
> +  // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
> +  ETH0->DMA_CONFIG =
> +      MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8) | 4;
> +
> +  // initialize descriptors
> +  for (int i = 0; i < ETH_DESC_CNT; i++) {
> +    s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
> +    if (i == ETH_DESC_CNT - 1) {
> +      s_rxdesc[i][0] |= MG_BIT(1);  // mark last descriptor
> +    }
> +
> +    s_txdesc[i][0] = (uint32_t) s_txbuf[i];
> +    s_txdesc[i][1] = MG_BIT(31);  // OWN descriptor
> +    if (i == ETH_DESC_CNT - 1) {
> +      s_txdesc[i][1] |= MG_BIT(30);  // mark last descriptor
> +    }
>     }
> -  (void) ms;
> +  ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
> +  ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
> +
> +  // disable other queues
> +  ETH0->TRANSMIT_Q2_PTR = 1;
> +  ETH0->TRANSMIT_Q1_PTR = 1;
> +  ETH0->RECEIVE_Q2_PTR = 1;
> +  ETH0->RECEIVE_Q1_PTR = 1;
> +
> +  // enable interrupts (TX and RX complete)
> +  ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
> +
> +  // set MAC address
> +  ETH0->SPEC_ADD1_BOTTOM =
> +      ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 | ifp->mac[0];
> +  ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
> +
> +  // enable MDIO, TX, RX
> +  ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
> +
> +  // start transmission
> +  ETH0->NETWORK_CONTROL |= MG_BIT(9);
> +
> +  // init phy
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> +
> +  (void) d;
> +  return true;
>   }
>
> -bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> -  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> -  bool res = false;
> -  if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
> -    mg_error(c, "net down");
> -  } else if (c->is_udp) {
> -    tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len);
> -    res = true;
> +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
> +                                      struct mg_tcpip_if *ifp) {
> +  if (len > sizeof(s_txbuf[s_txno])) {
> +    MG_ERROR(("Frame too big, %ld", (long) len));
> +    len = 0;  // Frame is too big
> +  } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
> +    ifp->nerr++;
> +    MG_ERROR(("No free descriptors"));
> +    len = 0;  // All descriptors are busy, fail
>     } else {
> -    res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> -  }
> -  return res;
> -}
> +    memcpy(s_txbuf[s_txno], buf, len);
> +    s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30) : 0) |
> +                          MG_BIT(15) | len;  // Last buffer and length
>
> -#ifdef MIP_QPROFILE
> +    ETH0->NETWORK_CONTROL |= MG_BIT(9);  // enable transmission
> +    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> +  }
>
> -#pragma pack(push, 1)
> -struct qpentry {
> -  uint32_t timestamp;
> -  uint16_t type;
> -  uint16_t len;
> -};
> -#pragma pack(pop)
> +  MG_DSB();
> +  ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
> +  ETH0->NETWORK_CONTROL |= MG_BIT(9);  // enable transmission
>
> -static struct queue qp;
> +  return len;
> +}
>
> -// This is called from IRQ and main contexts; two producers, single consumer
> -// TODO(scaprile): avoid concurrency issues (2 queues ?)
> -void qp_mark(unsigned int type, int len) {
> -  static bool ovf = false;
> -  static uint16_t irq_ctr = 0, drop_ctr = 0;
> -  struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
> -                      .type = (uint16_t) type,
> -                      .len = (uint16_t) len};
> -  if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
> -  if (ovf) {
> -    e.type = (uint16_t) QP_QUEUEOVF;
> -    e.len = drop_ctr;
> +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
> +  struct mg_tcpip_driver_xmc7_data *d =
> +      (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> +  uint8_t speed = MG_PHY_SPEED_10M;
> +  bool up = false, full_duplex = false;
> +  struct mg_phy phy = {eth_read_phy, eth_write_phy};
> +  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> +  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
> +    if (speed == MG_PHY_SPEED_1000M) {
> +		  ETH0->NETWORK_CONFIG |= MG_BIT(10);
> +	  }
> +    MG_DEBUG(("Link is %uM %s-duplex",
> +              speed == MG_PHY_SPEED_10M ? 10 :
> +              (speed == MG_PHY_SPEED_100M ? 100 : 1000),
> +              full_duplex ? "full" : "half"));
>     }
> -  ovf = !q_write(&qp, &e, sizeof(e));
> +  (void) d;
> +  return up;
>   }
>
> -void qp_log(void) {
> -  struct qpentry e;
> -  const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP", "OVFL"};
> -  for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
> -    MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
> +void ETH_IRQHandler(void) {
> +  uint32_t irq_status = ETH0->INT_STATUS;
> +  if (irq_status & MG_BIT(1)) {
> +    for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> +      if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
> +        size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> +        //MG_INFO(("Receive complete: %ld bytes", len));
> +        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> +        s_rxdesc[s_rxno][0] &= ~MG_BIT(0);  // OWN bit: handle control to DMA
> +        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> +      }
> +    }
>     }
> -}
>
> -void qp_init(void) {
> -  qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
> -  qp.buf = calloc(1, qp.len);  // THERE IS NO FREE
> +  ETH0->INT_STATUS = irq_status;
>   }
> -#endif  // MIP_QPROFILE
>
> -#endif  // MG_ENABLE_MIP
> +struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init,
> +                                               mg_tcpip_driver_xmc7_tx, NULL,
> +                                               mg_tcpip_driver_xmc7_up};
> +#endif
> diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
> index 350aad9c..3a439a6e 100644
> --- a/mongoose/mongoose.h
> +++ b/mongoose/mongoose.h
> @@ -1,5 +1,5 @@
>   // Copyright (c) 2004-2013 Sergey Lyubka
> -// Copyright (c) 2013-2022 Cesanta Software Limited
> +// Copyright (c) 2013-2024 Cesanta Software Limited
>   // All rights reserved
>   //
>   // This software is dual-licensed: you can redistribute it and/or modify
> @@ -15,58 +15,48 @@
>   // Alternatively, you can license this software under a commercial
>   // license, as set out in https://www.mongoose.ws/licensing/
>   //
> -// SPDX-License-Identifier: GPL-2.0-only
> +// SPDX-License-Identifier: GPL-2.0-only or commercial
>
>   #ifndef MONGOOSE_H
>   #define MONGOOSE_H
>
> -#define MG_VERSION "7.8"
> +#define MG_VERSION "7.14"
>
>   #ifdef __cplusplus
>   extern "C" {
>   #endif
>
>
> -#define MG_ARCH_CUSTOM 0     // User creates its own mongoose_custom.h
> -#define MG_ARCH_UNIX 1       // Linux, BSD, Mac, ...
> -#define MG_ARCH_WIN32 2      // Windows
> -#define MG_ARCH_ESP32 3      // ESP32
> -#define MG_ARCH_ESP8266 4    // ESP8266
> -#define MG_ARCH_FREERTOS 5   // FreeRTOS
> -#define MG_ARCH_AZURERTOS 6  // MS Azure RTOS
> -#define MG_ARCH_ZEPHYR 7     // Zephyr RTOS
> -#define MG_ARCH_NEWLIB 8     // Bare metal ARM
> -#define MG_ARCH_RTX 9        // Keil MDK RTX
> -#define MG_ARCH_TIRTOS 10    // Texas Semi TI-RTOS
> -#define MG_ARCH_RP2040 11    // Raspberry Pi RP2040
> +#define MG_ARCH_CUSTOM 0        // User creates its own mongoose_config.h
> +#define MG_ARCH_UNIX 1          // Linux, BSD, Mac, ...
> +#define MG_ARCH_WIN32 2         // Windows
> +#define MG_ARCH_ESP32 3         // ESP32
> +#define MG_ARCH_ESP8266 4       // ESP8266
> +#define MG_ARCH_FREERTOS 5      // FreeRTOS
> +#define MG_ARCH_AZURERTOS 6     // MS Azure RTOS
> +#define MG_ARCH_ZEPHYR 7        // Zephyr RTOS
> +#define MG_ARCH_NEWLIB 8        // Bare metal ARM
> +#define MG_ARCH_CMSIS_RTOS1 9   // CMSIS-RTOS API v1 (Keil RTX)
> +#define MG_ARCH_TIRTOS 10       // Texas Semi TI-RTOS
> +#define MG_ARCH_RP2040 11       // Raspberry Pi RP2040
> +#define MG_ARCH_ARMCC 12        // Keil MDK-Core with Configuration Wizard
> +#define MG_ARCH_CMSIS_RTOS2 13  // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS)
> +#define MG_ARCH_RTTHREAD 14     // RT-Thread RTOS
>
>   #if !defined(MG_ARCH)
>   #if defined(__unix__) || defined(__APPLE__)
>   #define MG_ARCH MG_ARCH_UNIX
>   #elif defined(_WIN32)
>   #define MG_ARCH MG_ARCH_WIN32
> -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
> -#define MG_ARCH MG_ARCH_ESP8266
> -#elif defined(__ZEPHYR__)
> -#define MG_ARCH MG_ARCH_ZEPHYR
> -#elif defined(ESP_PLATFORM)
> -#define MG_ARCH MG_ARCH_ESP32
> -#elif defined(FREERTOS_IP_H)
> -#define MG_ARCH MG_ARCH_FREERTOS
> -#define MG_ENABLE_FREERTOS_TCP 1
> -#elif defined(AZURE_RTOS_THREADX)
> -#define MG_ARCH MG_ARCH_AZURERTOS
> -#elif defined(PICO_TARGET_NAME)
> -#define MG_ARCH MG_ARCH_RP2040
>   #endif
>   #endif  // !defined(MG_ARCH)
>
>   #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
> -#include "mongoose_custom.h"  // keep this include
> +#include "mongoose_config.h"  // keep this include
>   #endif
>
>   #if !defined(MG_ARCH)
> -#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..."
> +#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in your compiler"
>   #endif
>
>   // http://esr.ibiblio.org/?p=5095
> @@ -130,7 +120,8 @@ extern "C" {
>   #include <sys/types.h>
>   #include <time.h>
>
> -#include <esp_timer.h>
> +#include <esp_ota_ops.h>  // Use angle brackets to avoid
> +#include <esp_timer.h>    // amalgamation ditching them
>
>   #define MG_PATH_MAX 128
>
> @@ -166,15 +157,24 @@ extern "C" {
>   #if MG_ARCH == MG_ARCH_FREERTOS
>
>   #include <ctype.h>
> -// #include <errno.h> // Cannot include errno - might conflict with lwip!
> +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
> +#include <errno.h>
> +#endif
>   #include <stdarg.h>
>   #include <stdbool.h>
>   #include <stddef.h>
>   #include <stdint.h>
>   #include <stdio.h>
> -#include <stdlib.h> // rand(), strtol(), atoi()
> +#include <stdlib.h>  // rand(), strtol(), atoi()
>   #include <string.h>
> +#if defined(__ARMCC_VERSION)
> +#define mode_t size_t
> +#include <alloca.h>
> +#include <time.h>
> +#elif defined(__CCRH__)
> +#else
>   #include <sys/stat.h>
> +#endif
>
>   #include <FreeRTOS.h>
>   #include <task.h>
> @@ -186,7 +186,7 @@ extern "C" {
>   #define calloc(a, b) mg_calloc(a, b)
>   #define free(a) vPortFree(a)
>   #define malloc(a) pvPortMalloc(a)
> -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> +#define strdup(s) mg_mprintf("%s", s)
>
>   // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
>   static inline void *mg_calloc(size_t cnt, size_t size) {
> @@ -240,9 +240,34 @@ static inline int mg_mkdir(const char *path, mode_t mode) {
>   #include <pico/stdlib.h>
>   int mkdir(const char *, mode_t);
>   #endif
> -
> -
> -#if MG_ARCH == MG_ARCH_RTX
> +
>
> +
>
> +#if MG_ARCH == MG_ARCH_RTTHREAD
>
> +
>
> +#include <rtthread.h>
>
> +#include <ctype.h>
>
> +#include <errno.h>
>
> +#include <fcntl.h>
>
> +#include <sys/socket.h>
>
> +#include <sys/select.h>
>
> +#include <stdarg.h>
>
> +#include <stdbool.h>
>
> +#include <stdint.h>
>
> +#include <stdio.h>
>
> +#include <stdlib.h>
>
> +#include <string.h>
>
> +#include <sys/types.h>
>
> +#include <time.h>
>
> +
>
> +#ifndef MG_IO_SIZE
>
> +#define MG_IO_SIZE 1460
>
> +#endif
>
> +
>
> +#endif // MG_ARCH == MG_ARCH_RTTHREAD
>
> +
> +
> +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
> +    MG_ARCH == MG_ARCH_CMSIS_RTOS2
>
>   #include <ctype.h>
>   #include <errno.h>
> @@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
>   #include <stdint.h>
>   #include <stdio.h>
>   #include <stdlib.h>
> +#include <alloca.h>
>   #include <string.h>
>   #include <time.h>
> +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
> +#include "cmsis_os.h"  // keep this include
> +// https://developer.arm.com/documentation/ka003821/latest
> +extern uint32_t rt_time_get(void);
> +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> +#include "cmsis_os2.h"  // keep this include
> +#endif
> +
> +#define strdup(s) mg_mprintf("%s", s)
> +
> +#if defined(__ARMCC_VERSION)
> +#define mode_t size_t
> +#define mkdir(a, b) mg_mkdir(a, b)
> +static inline int mg_mkdir(const char *path, mode_t mode) {
> +  (void) path, (void) mode;
> +  return -1;
> +}
> +#endif
>
> -#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP)
> +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) &&     \
> +    !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \
> +    (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
>   #define MG_ENABLE_RL 1
> +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> +#endif
>   #endif
>
>   #endif
> @@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
>   #define MG_INVALID_SOCKET INVALID_SOCKET
>   #define MG_SOCKET_TYPE SOCKET
>   typedef unsigned long nfds_t;
> -#define MG_SOCKET_ERRNO WSAGetLastError()
>   #if defined(_MSC_VER)
>   #pragma comment(lib, "ws2_32.lib")
>   #ifndef alloca
> @@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
>   #endif
>   #endif
>   #define poll(a, b, c) WSAPoll((a), (b), (c))
> -#ifndef SO_EXCLUSIVEADDRUSE
> -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
> -#endif
>   #define closesocket(x) closesocket(x)
>
>   typedef int socklen_t;
> @@ -423,16 +468,24 @@ typedef int socklen_t;
>   #define MG_PATH_MAX FILENAME_MAX
>   #endif
>
> -#ifndef EINPROGRESS
> -#define EINPROGRESS WSAEINPROGRESS
> -#endif
> -#ifndef EWOULDBLOCK
> -#define EWOULDBLOCK WSAEWOULDBLOCK
> +#ifndef SO_EXCLUSIVEADDRUSE
> +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
>   #endif
>
> +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0)
> +
> +#define MG_SOCK_PENDING(errcode)                                            \
> +  (((errcode) < 0) &&                                                       \
> +   (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \
> +    WSAGetLastError() == WSAEWOULDBLOCK))
> +
> +#define MG_SOCK_RESET(errcode) \
> +  (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
> +
>   #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
> -#define sleep(x) Sleep(x)
> +#define sleep(x) Sleep((x) *1000)
>   #define mkdir(a, b) _mkdir(a)
> +#define timegm(x) _mkgmtime(x)
>
>   #ifndef S_ISDIR
>   #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
> @@ -442,6 +495,10 @@ typedef int socklen_t;
>   #define MG_ENABLE_DIRLIST 1
>   #endif
>
> +#ifndef SIGPIPE
> +#define SIGPIPE 0
> +#endif
> +
>   #endif
>
>
> @@ -451,8 +508,9 @@ typedef int socklen_t;
>
>   #include <ctype.h>
>   #include <errno.h>
> -#include <fcntl.h>
>   #include <zephyr/net/socket.h>
> +#include <zephyr/posix/fcntl.h>
> +#include <zephyr/posix/sys/select.h>
>   #include <stdarg.h>
>   #include <stdbool.h>
>   #include <stdint.h>
> @@ -464,11 +522,18 @@ typedef int socklen_t;
>
>   #define MG_PUTCHAR(x) printk("%c", x)
>   #ifndef strdup
> -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
>   #endif
>   #define strerror(x) zsock_gai_strerror(x)
> +
> +#ifndef FD_CLOEXEC
>   #define FD_CLOEXEC 0
> +#endif
> +
> +#ifndef F_SETFD
>   #define F_SETFD 0
> +#endif
> +
>   #define MG_ENABLE_SSI 0
>
>   int rand(void);
> @@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
>
>   #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
>
> -#include <ctype.h>
> -#include <errno.h>
>   #include <limits.h>
> -#include <stdarg.h>
> -#include <stdbool.h>
> -#include <stddef.h>
> -#include <stdio.h>
> -#include <stdlib.h>
> -#include <string.h>
> -#include <sys/stat.h>
> -#include <time.h>
> -
> -#include <FreeRTOS.h>
>   #include <list.h>
> -#include <task.h>
>
>   #include <FreeRTOS_IP.h>
>   #include <FreeRTOS_Sockets.h>
> +#include <FreeRTOS_errno_TCP.h>  // contents to be moved and file removed, some day
>
>   #define MG_SOCKET_TYPE Socket_t
>   #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
> @@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
>   #define SO_ERROR 0
>   #define SOL_SOCKET 0
>   #define SO_REUSEADDR 0
> +
> +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> +
> +#define MG_SOCK_PENDING(errcode)                 \
> +  ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
> +   (errcode) == -pdFREERTOS_ERRNO_EISCONN ||     \
> +   (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
> +   (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
> +
> +#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
> +
> +// actually only if optional timeout is enabled
> +#define MG_SOCK_INTR(fd) (fd == NULL)
> +
>   #define sockaddr_in freertos_sockaddr
>   #define sockaddr freertos_sockaddr
>   #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
> @@ -543,8 +610,17 @@ static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
>
>
>   #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
> -#if defined(__GNUC__)
> +
> +#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
>   #include <sys/stat.h>
> +#endif
> +
> +struct timeval;
> +
> +#include <lwip/sockets.h>
> +
> +#if !LWIP_TIMEVAL_PRIVATE
> +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both
>   #include <sys/time.h>
>   #else
>   struct timeval {
> @@ -552,8 +628,7 @@ struct timeval {
>     long tv_usec;
>   };
>   #endif
> -
> -#include <lwip/sockets.h>
> +#endif
>
>   #if LWIP_SOCKET != 1
>   // Sockets support disabled in LWIP by default
> @@ -565,16 +640,25 @@ struct timeval {
>   #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
>   #include <rl_net.h>
>
> -#define MG_ENABLE_CUSTOM_MILLIS 1
>   #define closesocket(x) closesocket(x)
> -#define mkdir(a, b) (-1)
> -#define EWOULDBLOCK BSD_EWOULDBLOCK
> -#define EAGAIN BSD_EWOULDBLOCK
> -#define EINPROGRESS BSD_EWOULDBLOCK
> -#define EINTR BSD_EWOULDBLOCK
> -#define ECONNRESET BSD_ECONNRESET
> -#define EPIPE BSD_ECONNRESET
> +
>   #define TCP_NODELAY SO_KEEPALIVE
> +
> +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> +
> +#define MG_SOCK_PENDING(errcode)                                \
> +  ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
> +   (errcode) == BSD_EINPROGRESS)
> +
> +#define MG_SOCK_RESET(errcode) \
> +  ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
> +
> +// In blocking mode, which is enabled by default, accept() waits for a
> +// connection request. In non blocking mode, you must call accept()
> +// again if the error code BSD_EWOULDBLOCK is returned.
> +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
> +
> +#define socklen_t int
>   #endif
>
>
> @@ -582,8 +666,12 @@ struct timeval {
>   #define MG_ENABLE_LOG 1
>   #endif
>
> -#ifndef MG_ENABLE_MIP
> -#define MG_ENABLE_MIP 0  // Mongoose built-in network stack
> +#ifndef MG_ENABLE_CUSTOM_LOG
> +#define MG_ENABLE_CUSTOM_LOG 0  // Let user define their own MG_LOG
> +#endif
> +
> +#ifndef MG_ENABLE_TCPIP
> +#define MG_ENABLE_TCPIP 0  // Mongoose built-in network stack
>   #endif
>
>   #ifndef MG_ENABLE_LWIP
> @@ -599,7 +687,7 @@ struct timeval {
>   #endif
>
>   #ifndef MG_ENABLE_SOCKET
> -#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
> +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
>   #endif
>
>   #ifndef MG_ENABLE_POLL
> @@ -614,18 +702,6 @@ struct timeval {
>   #define MG_ENABLE_FATFS 0
>   #endif
>
> -#ifndef MG_ENABLE_MBEDTLS
> -#define MG_ENABLE_MBEDTLS 0
> -#endif
> -
> -#ifndef MG_ENABLE_OPENSSL
> -#define MG_ENABLE_OPENSSL 0
> -#endif
> -
> -#ifndef MG_ENABLE_CUSTOM_TLS
> -#define MG_ENABLE_CUSTOM_TLS 0
> -#endif
> -
>   #ifndef MG_ENABLE_SSI
>   #define MG_ENABLE_SSI 0
>   #endif
> @@ -634,6 +710,10 @@ struct timeval {
>   #define MG_ENABLE_IPV6 0
>   #endif
>
> +#ifndef MG_IPV6_V6ONLY
> +#define MG_IPV6_V6ONLY 0  // IPv6 socket binds only to V6, not V4 address
> +#endif
> +
>   #ifndef MG_ENABLE_MD5
>   #define MG_ENABLE_MD5 1
>   #endif
> @@ -659,12 +739,16 @@ struct timeval {
>   #define MG_ENABLE_PACKED_FS 0
>   #endif
>
> +#ifndef MG_ENABLE_ASSERT
> +#define MG_ENABLE_ASSERT 0
> +#endif
> +
>   #ifndef MG_IO_SIZE
>   #define MG_IO_SIZE 2048  // Granularity of the send/recv IO buffer growth
>   #endif
>
>   #ifndef MG_MAX_RECV_SIZE
> -#define MG_MAX_RECV_SIZE (3 * 1024 * 1024)  // Maximum recv IO buffer size
> +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL)  // Maximum recv IO buffer size
>   #endif
>
>   #ifndef MG_DATA_SIZE
> @@ -688,18 +772,18 @@ struct timeval {
>   #endif
>
>   #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
>   #endif
>
>   #ifndef MG_DIRSEP
>   #define MG_DIRSEP '/'
>   #endif
>
> -#ifndef MG_ENABLE_FILE
> +#ifndef MG_ENABLE_POSIX_FS
>   #if defined(FOPEN_MAX)
> -#define MG_ENABLE_FILE 1
> +#define MG_ENABLE_POSIX_FS 1
>   #else
> -#define MG_ENABLE_FILE 0
> +#define MG_ENABLE_POSIX_FS 0
>   #endif
>   #endif
>
> @@ -732,60 +816,112 @@ struct timeval {
>   #define MG_EPOLL_MOD(c, wr)
>   #endif
>
> +#ifndef MG_ENABLE_PROFILE
> +#define MG_ENABLE_PROFILE 0
> +#endif
>
> +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT    // mg_mgr_init() will also initialize
> +#define MG_ENABLE_TCPIP_DRIVER_INIT 1  // enabled built-in driver for
> +#endif                                 // Mongoose built-in network stack
>
> +#ifndef MG_TCPIP_IP                      // e.g. MG_IPV4(192, 168, 0, 223)
> +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
> +#endif
>
> -struct mg_str {
> -  const char *ptr;  // Pointer to string data
> -  size_t len;       // String len
> -};
> +#ifndef MG_TCPIP_MASK
> +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
> +#endif
> +
> +#ifndef MG_TCPIP_GW
> +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
> +#endif
> +
> +#ifndef MG_SET_MAC_ADDRESS
> +#define MG_SET_MAC_ADDRESS(mac)
> +#endif
>
> -#define MG_NULL_STR \
> -  { NULL, 0 }
> +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
> +#endif
>
> -#define MG_C_STR(a) \
> -  { (a), sizeof(a) - 1 }
> +
> +
> +
> +// Describes an arbitrary chunk of memory
> +struct mg_str {
> +  char *buf;   // String data
> +  size_t len;  // String length
> +};
>
>   // Using macro to avoid shadowing C++ struct constructor, see #1298
>   #define mg_str(s) mg_str_s(s)
>
>   struct mg_str mg_str(const char *s);
>   struct mg_str mg_str_n(const char *s, size_t n);
> -int mg_lower(const char *s);
> -int mg_ncasecmp(const char *s1, const char *s2, size_t len);
>   int mg_casecmp(const char *s1, const char *s2);
> -int mg_vcmp(const struct mg_str *s1, const char *s2);
> -int mg_vcasecmp(const struct mg_str *str1, const char *str2);
>   int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
> -struct mg_str mg_strstrip(struct mg_str s);
> -struct mg_str mg_strdup(const struct mg_str s);
> -const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
> +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2);
>   bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps);
> -bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n);
> -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
> -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char delim);
> -char *mg_hex(const void *buf, size_t len, char *dst);
> -void mg_unhex(const char *buf, size_t len, unsigned char *to);
> -unsigned long mg_unhexn(const char *s, size_t len);
> -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
> -int64_t mg_to64(struct mg_str str);
> -uint64_t mg_tou64(struct mg_str str);
> -char *mg_remove_double_dots(char *s);
> +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char delim);
> +
> +bool mg_str_to_num(struct mg_str, int base, void *val, size_t val_len);
> +
>
>
>
> +// Single producer, single consumer non-blocking queue
> +
> +struct mg_queue {
> +  char *buf;
> +  size_t size;
> +  volatile size_t tail;
> +  volatile size_t head;
> +};
> +
> +void mg_queue_init(struct mg_queue *, char *, size_t);        // Init queue
> +size_t mg_queue_book(struct mg_queue *, char **buf, size_t);  // Reserve space
> +void mg_queue_add(struct mg_queue *, size_t);                 // Add new message
> +size_t mg_queue_next(struct mg_queue *, char **);  // Get oldest message
> +void mg_queue_del(struct mg_queue *, size_t);      // Delete oldest message
>
>
> -typedef void (*mg_pfn_t)(char, void *);                  // Custom putchar
> +
> +
> +typedef void (*mg_pfn_t)(char, void *);                  // Output function
>   typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *);  // %M printer
> -void mg_pfn_iobuf(char ch, void *param);                 // iobuf printer
>
>   size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *);
>   size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...);
> +
> +
> +
> +
> +
> +
> +// Convenience wrappers around mg_xprintf
>   size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
>   size_t mg_snprintf(char *, size_t, const char *fmt, ...);
>   char *mg_vmprintf(const char *fmt, va_list *ap);
>   char *mg_mprintf(const char *fmt, ...);
> +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *);
> +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
> +
> +// %M print helper functions
> +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap);
> +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap);
> +
> +// Various output functions
> +void mg_pfn_iobuf(char ch, void *param);  // param: struct mg_iobuf *
> +void mg_pfn_stdout(char c, void *param);  // param: ignored
> +
> +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", MG_ESC("hi"))
> +#define MG_ESC(str) mg_print_esc, 0, (str)
>
>
>
> @@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
>
>
>   enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
> +extern int mg_log_level;  // Current log level, one of MG_LL_*
> +
>   void mg_log(const char *fmt, ...);
> -bool mg_log_prefix(int ll, const char *file, int line, const char *fname);
> -void mg_log_set(int log_level);
> +void mg_log_prefix(int ll, const char *file, int line, const char *fname);
> +// bool mg_log2(int ll, const char *file, int line, const char *fmt, ...);
>   void mg_hexdump(const void *buf, size_t len);
>   void mg_log_set_fn(mg_pfn_t fn, void *param);
>
> +#define mg_log_set(level_) mg_log_level = (level_)
> +
>   #if MG_ENABLE_LOG
> -#define MG_LOG(level, args)                                                \
> -  do {                                                                     \
> -    if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \
> +#define MG_LOG(level, args)                                 \
> +  do {                                                      \
> +    if ((level) <= mg_log_level) {                          \
> +      mg_log_prefix((level), __FILE__, __LINE__, __func__); \
> +      mg_log args;                                          \
> +    }                                                       \
>     } while (0)
>   #else
>   #define MG_LOG(level, args) \
> @@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
>   // stat(), write(), read() calls.
>   struct mg_fs {
>     int (*st)(const char *path, size_t *size, time_t *mtime);  // stat file
> -  void (*ls)(const char *path, void (*fn)(const char *, void *), void *);
> +  void (*ls)(const char *path, void (*fn)(const char *, void *),
> +             void *);  // List directory entries: call fn(file_name, fn_data)
> +                       // for each directory entry
>     void *(*op)(const char *path, int flags);             // Open file
>     void (*cl)(void *fd);                                 // Close file
>     size_t (*rd)(void *fd, void *buf, size_t len);        // Read file
> @@ -877,28 +1022,84 @@ struct mg_fd {
>
>   struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags);
>   void mg_fs_close(struct mg_fd *fd);
> -char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size);
> +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len);
> +struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
>   bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t);
>   bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...);
>
> +// Packed API
> +const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
> +const char *mg_unlist(size_t no);             // Get no'th packed filename
> +struct mg_str mg_unpacked(const char *path);  // Packed file as mg_str
> +
>
>
>
>
>
> +
> +#if MG_ENABLE_ASSERT
> +#include <assert.h>
> +#elif !defined(assert)
> +#define assert(x)
> +#endif
> +
> +void mg_bzero(volatile unsigned char *buf, size_t len);
>   void mg_random(void *buf, size_t len);
>   char *mg_random_str(char *buf, size_t len);
>   uint16_t mg_ntohs(uint16_t net);
>   uint32_t mg_ntohl(uint32_t net);
>   uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
> -uint64_t mg_millis(void);
> +uint64_t mg_millis(void);  // Return milliseconds since boot
> +uint64_t mg_now(void);     // Return milliseconds since Epoch
> +bool mg_path_is_sane(const struct mg_str path);
>
>   #define mg_htons(x) mg_ntohs(x)
>   #define mg_htonl(x) mg_ntohl(x)
>
> -#define MG_U32(a, b, c, d)                                      \
> -  (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
> -   ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
> +#define MG_U32(a, b, c, d)                                           \
> +  (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \
> +   ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
> +
> +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
> +
> +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
> +#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
> +#define MG_IPADDR_PARTS(ADDR) \
> +  MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
> +
> +#define MG_REG(x) ((volatile uint32_t *) (x))[0]
> +#define MG_BIT(x) (((uint32_t) 1U) << (x))
> +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK)
> +
> +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a)))
> +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
> +
> +#if defined(__GNUC__)
> +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
> +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
> +#elif defined(__CCRH__)
> +#define MG_RH850_DISABLE_IRQ() __DI()
> +#define MG_RH850_ENABLE_IRQ() __EI()
> +#else
> +#define MG_ARM_DISABLE_IRQ()
> +#define MG_ARM_ENABLE_IRQ()
> +#endif
> +
> +#if defined(__CC_ARM)
> +#define MG_DSB() __dsb(0xf)
> +#elif defined(__ARMCC_VERSION)
> +#define MG_DSB() __builtin_arm_dsb(0xf)
> +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
> +#define MG_DSB() asm("DSB 0xf")
> +#elif defined(__ICCARM__)
> +#define MG_DSB() __iar_builtin_DSB()
> +#else
> +#define MG_DSB()
> +#endif
> +
> +struct mg_addr;
> +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
>
>   // Linked list management macros
>   #define LIST_ADD_HEAD(type_, head_, elem_) \
> @@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
>   size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t);
>   size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
>
> -int mg_base64_update(unsigned char p, char *to, int len);
> -int mg_base64_final(char *to, int len);
> -int mg_base64_encode(const unsigned char *p, int n, char *to);
> -int mg_base64_decode(const char *src, int n, char *dst);
> +
> +size_t mg_base64_update(unsigned char input_byte, char *buf, size_t len);
> +size_t mg_base64_final(char *buf, size_t len);
> +size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, size_t);
> +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
>
>
>
> @@ -976,35 +1178,793 @@ typedef struct {
>   void mg_sha1_init(mg_sha1_ctx *);
>   void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len);
>   void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
> +// https://github.com/B-Con/crypto-algorithms
> +// Author:     Brad Conte (brad AT bradconte.com)
> +// Disclaimer: This code is presented "as is" without any guarantees.
> +// Details:    Defines the API for the corresponding SHA1 implementation.
> +// Copyright:  public domain
> +
> +
> +
> +
> +
> +typedef struct {
> +  uint32_t state[8];
> +  uint64_t bits;
> +  uint32_t len;
> +  unsigned char buffer[64];
> +} mg_sha256_ctx;
> +
> +void mg_sha256_init(mg_sha256_ctx *);
> +void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len);
> +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
> +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data,
> +                    size_t datasz);
> +#ifndef TLS_X15519_H
> +#define TLS_X15519_H
> +
> +
> +
> +#define X25519_BYTES 32
> +extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
> +
> +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
> +                  const uint8_t x1[X25519_BYTES], int clamp);
> +
> +
> +#endif /* TLS_X15519_H */
> +/******************************************************************************
> + *
> + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
> + *
> + * This is a simple and straightforward implementation of AES-GCM authenticated
> + * encryption. The focus of this work was correctness & accuracy. It is written
> + * in straight 'C' without any particular focus upon optimization or speed. It
> + * should be endian (memory byte order) neutral since the few places that care
> + * are handled explicitly.
> + *
> + * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
> + *
> + * It is intended for general purpose use, but was written in support of GRC's
> + * reference implementation of the SQRL (Secure Quick Reliable Login) client.
> + *
> + * See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> + *         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \
> + *         gcm/gcm-revised-spec.pdf
> + *
> + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
> + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
> + *
> + *******************************************************************************/
> +#ifndef TLS_AES128_H
> +#define TLS_AES128_H
> +
> +typedef unsigned char uchar;  // add some convienent shorter types
> +typedef unsigned int uint;
> +
> +/******************************************************************************
> + *  AES_CONTEXT : cipher context / holds inter-call data
> + ******************************************************************************/
> +typedef struct {
> +  int mode;          // 1 for Encryption, 0 for Decryption
> +  int rounds;        // keysize-based rounds count
> +  uint32_t *rk;      // pointer to current round key
> +  uint32_t buf[68];  // key expansion buffer
> +} aes_context;
> +
> +
> +#define GCM_AUTH_FAILURE 0x55555555  // authentication failure
> +
> +/******************************************************************************
> + *  GCM_CONTEXT : MUST be called once before ANY use of this library
> + ******************************************************************************/
> +int mg_gcm_initialize(void);
> +
> +//
> +//  aes-gcm.h
> +//  MKo
> +//
> +//  Created by Markus Kosmal on 20/11/14.
> +//
> +//
> +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned char *input,
> +                       size_t input_length, const unsigned char *key,
> +                       const size_t key_len, const unsigned char *iv,
> +                       const size_t iv_len, unsigned char *aead,
> +                       size_t aead_len, unsigned char *tag,
> +                       const size_t tag_len);
> +
> +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
> +                       size_t input_length, const unsigned char *key,
> +                       const size_t key_len, const unsigned char *iv,
> +                       const size_t iv_len);
> +
> +#endif /* TLS_AES128_H */
> +
> +// End of aes128 PD
> +
> +
> +
> +#define MG_UECC_SUPPORTS_secp256r1 1
> +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */
> +
> +#ifndef _UECC_H_
> +#define _UECC_H_
> +
> +/* Platform selection options.
> +If MG_UECC_PLATFORM is not defined, the code will try to guess it based on
> +compiler macros. Possible values for MG_UECC_PLATFORM are defined below: */
> +#define mg_uecc_arch_other 0
> +#define mg_uecc_x86 1
> +#define mg_uecc_x86_64 2
> +#define mg_uecc_arm 3
> +#define mg_uecc_arm_thumb 4
> +#define mg_uecc_arm_thumb2 5
> +#define mg_uecc_arm64 6
> +#define mg_uecc_avr 7
> +
> +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate for your platform
> +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly defined then it will
> +be automatically set based on your platform. */
> +
> +/* Optimization level; trade speed for code size.
> +   Larger values produce code that is faster but larger.
> +   Currently supported values are 0 - 4; 0 is unusably slow for most
> +   applications. Optimization level 4 currently only has an effect ARM platforms
> +   where more than one curve is enabled. */
> +#ifndef MG_UECC_OPTIMIZATION_LEVEL
> +#define MG_UECC_OPTIMIZATION_LEVEL 2
> +#endif
> +
> +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a
> +specific function to be used for (scalar) squaring instead of the generic
> +multiplication function. This can make things faster somewhat faster, but
> +increases the code size. */
> +#ifndef MG_UECC_SQUARE_FUNC
> +#define MG_UECC_SQUARE_FUNC 0
> +#endif
> +
> +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will
> +switch to native little-endian format for *all* arrays passed in and out of the
> +public API. This includes public and private keys, shared secrets, signatures
> +and message hashes. Using this switch reduces the amount of call stack memory
> +used by uECC, since less intermediate translations are required. Note that this
> +will *only* work on native little-endian processors and it will treat the
> +uint8_t arrays passed into the public API as word arrays, therefore requiring
> +the provided byte arrays to be word aligned on architectures that do not support
> +unaligned accesses. IMPORTANT: Keys and signatures generated with
> +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures
> +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same
> +endianness. */
> +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
> +#endif
> +
> +/* Curve support selection. Set to 0 to remove that curve. */
> +#ifndef MG_UECC_SUPPORTS_secp160r1
> +#define MG_UECC_SUPPORTS_secp160r1 0
> +#endif
> +#ifndef MG_UECC_SUPPORTS_secp192r1
> +#define MG_UECC_SUPPORTS_secp192r1 0
> +#endif
> +#ifndef MG_UECC_SUPPORTS_secp224r1
> +#define MG_UECC_SUPPORTS_secp224r1 0
> +#endif
> +#ifndef MG_UECC_SUPPORTS_secp256r1
> +#define MG_UECC_SUPPORTS_secp256r1 1
> +#endif
> +#ifndef MG_UECC_SUPPORTS_secp256k1
> +#define MG_UECC_SUPPORTS_secp256k1 0
> +#endif
> +
> +/* Specifies whether compressed point format is supported.
> +   Set to 0 to disable point compression/decompression functions. */
> +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
> +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
> +#endif
> +
> +struct MG_UECC_Curve_t;
> +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#if MG_UECC_SUPPORTS_secp160r1
> +MG_UECC_Curve mg_uecc_secp160r1(void);
> +#endif
> +#if MG_UECC_SUPPORTS_secp192r1
> +MG_UECC_Curve mg_uecc_secp192r1(void);
> +#endif
> +#if MG_UECC_SUPPORTS_secp224r1
> +MG_UECC_Curve mg_uecc_secp224r1(void);
> +#endif
> +#if MG_UECC_SUPPORTS_secp256r1
> +MG_UECC_Curve mg_uecc_secp256r1(void);
> +#endif
> +#if MG_UECC_SUPPORTS_secp256k1
> +MG_UECC_Curve mg_uecc_secp256k1(void);
> +#endif
> +
> +/* MG_UECC_RNG_Function type
> +The RNG function should fill 'size' random bytes into 'dest'. It should return 1
> +if 'dest' was filled with random data, or 0 if the random data could not be
> +generated. The filled-in values should be either truly random, or from a
> +cryptographically-secure PRNG.
> +
> +A correctly functioning RNG function must be set (using mg_uecc_set_rng())
> +before calling mg_uecc_make_key() or mg_uecc_sign().
> +
> +Setting a correctly functioning RNG function improves the resistance to
> +side-channel attacks for mg_uecc_shared_secret() and
> +mg_uecc_sign_deterministic().
> +
> +A correct RNG function is set by default when building for Windows, Linux, or OS
> +X. If you are building on another POSIX-compliant system that supports
> +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined
> +RNG. For embedded platforms there is no predefined RNG function; you must
> +provide your own.
> +*/
> +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
> +
> +/* mg_uecc_set_rng() function.
> +Set the function that will be used to generate random bytes. The RNG function
> +should return 1 if the random data was generated, or 0 if the random data could
> +not be generated.
> +
> +On platforms where there is no predefined RNG function (eg embedded platforms),
> +this must be called before mg_uecc_make_key() or mg_uecc_sign() are used.
> +
> +Inputs:
> +    rng_function - The function that will be used to generate random bytes.
> +*/
> +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
> +
> +/* mg_uecc_get_rng() function.
> +
> +Returns the function that will be used to generate random bytes.
> +*/
> +MG_UECC_RNG_Function mg_uecc_get_rng(void);
> +
> +/* mg_uecc_curve_private_key_size() function.
> +
> +Returns the size of a private key for the curve in bytes.
> +*/
> +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
> +
> +/* mg_uecc_curve_public_key_size() function.
> +
> +Returns the size of a public key for the curve in bytes.
> +*/
> +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
> +
> +/* mg_uecc_make_key() function.
> +Create a public/private key pair.
> +
> +Outputs:
> +    public_key  - Will be filled in with the public key. Must be at least 2 *
> +the curve size (in bytes) long. For example, if the curve is secp256r1,
> +public_key must be 64 bytes long. private_key - Will be filled in with the
> +private key. Must be as long as the curve order; this is typically the same as
> +the curve size, except for secp160r1. For example, if the curve is secp256r1,
> +private_key must be 32 bytes long.
> +
> +                  For secp160r1, private_key must be 21 bytes long! Note that
> +the first byte will almost always be 0 (there is about a 1 in 2^80 chance of it
> +being non-zero).
> +
> +Returns 1 if the key pair was generated successfully, 0 if an error occurred.
> +*/
> +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> +                     MG_UECC_Curve curve);
> +
> +/* mg_uecc_shared_secret() function.
> +Compute a shared secret given your secret key and someone else's public key. If
> +the public key is not from a trusted source and has not been previously
> +verified, you should verify it first using mg_uecc_valid_public_key(). Note: It
> +is recommended that you hash the result of mg_uecc_shared_secret() before using
> +it for symmetric encryption or HMAC.
> +
> +Inputs:
> +    public_key  - The public key of the remote party.
> +    private_key - Your private key.
> +
> +Outputs:
> +    secret - Will be filled in with the shared secret value. Must be the same
> +size as the curve size; for example, if the curve is secp256r1, secret must be
> +32 bytes long.
> +
> +Returns 1 if the shared secret was generated successfully, 0 if an error
> +occurred.
> +*/
> +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
> +                          uint8_t *secret, MG_UECC_Curve curve);
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +/* mg_uecc_compress() function.
> +Compress a public key.
> +
> +Inputs:
> +    public_key - The public key to compress.
> +
> +Outputs:
> +    compressed - Will be filled in with the compressed public key. Must be at
> +least (curve size + 1) bytes long; for example, if the curve is secp256r1,
> +                 compressed must be 33 bytes long.
> +*/
> +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
> +                      MG_UECC_Curve curve);
> +
> +/* mg_uecc_decompress() function.
> +Decompress a compressed public key.
> +
> +Inputs:
> +    compressed - The compressed public key.
> +
> +Outputs:
> +    public_key - Will be filled in with the decompressed public key.
> +*/
> +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
> +                        MG_UECC_Curve curve);
> +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> +
> +/* mg_uecc_valid_public_key() function.
> +Check to see if a public key is valid.
> +
> +Note that you are not required to check for a valid public key before using any
> +other uECC functions. However, you may wish to avoid spending CPU time computing
> +a shared secret or verifying a signature using an invalid public key.
> +
> +Inputs:
> +    public_key - The public key to check.
> +
> +Returns 1 if the public key is valid, 0 if it is invalid.
> +*/
> +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve);
> +
> +/* mg_uecc_compute_public_key() function.
> +Compute the corresponding public key for a private key.
> +
> +Inputs:
> +    private_key - The private key to compute the public key for
> +
> +Outputs:
> +    public_key - Will be filled in with the corresponding public key
> +
> +Returns 1 if the key was computed successfully, 0 if an error occurred.
> +*/
> +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key,
> +                               MG_UECC_Curve curve);
> +
> +/* mg_uecc_sign() function.
> +Generate an ECDSA signature for a given hash value.
> +
> +Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and
> +pass it in to this function along with your private key.
> +
> +Inputs:
> +    private_key  - Your private key.
> +    message_hash - The hash of the message to sign.
> +    hash_size    - The size of message_hash in bytes.
> +
> +Outputs:
> +    signature - Will be filled in with the signature value. Must be at least 2 *
> +curve size long. For example, if the curve is secp256r1, signature must be 64
> +bytes long.
> +
> +Returns 1 if the signature generated successfully, 0 if an error occurred.
> +*/
> +int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash,
> +                 unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
> +
> +/* MG_UECC_HashContext structure.
> +This is used to pass in an arbitrary hash function to
> +mg_uecc_sign_deterministic(). The structure will be used for multiple hash
> +computations; each time a new hash is computed, init_hash() will be called,
> +followed by one or more calls to update_hash(), and finally a call to
> +finish_hash() to produce the resulting hash.
> +
> +The intention is that you will create a structure that includes
> +MG_UECC_HashContext followed by any hash-specific data. For example:
> +
> +typedef struct SHA256_HashContext {
> +    MG_UECC_HashContext uECC;
> +    SHA256_CTX ctx;
> +} SHA256_HashContext;
> +
> +void init_SHA256(MG_UECC_HashContext *base) {
> +    SHA256_HashContext *context = (SHA256_HashContext *)base;
> +    SHA256_Init(&context->ctx);
> +}
> +
> +void update_SHA256(MG_UECC_HashContext *base,
> +                   const uint8_t *message,
> +                   unsigned message_size) {
> +    SHA256_HashContext *context = (SHA256_HashContext *)base;
> +    SHA256_Update(&context->ctx, message, message_size);
> +}
> +
> +void finish_SHA256(MG_UECC_HashContext *base, uint8_t *hash_result) {
> +    SHA256_HashContext *context = (SHA256_HashContext *)base;
> +    SHA256_Final(hash_result, &context->ctx);
> +}
> +
> +... when signing ...
> +{
> +    uint8_t tmp[32 + 32 + 64];
> +    SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64,
> +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash, &ctx.uECC, signature);
> +}
> +*/
> +typedef struct MG_UECC_HashContext {
> +  void (*init_hash)(const struct MG_UECC_HashContext *context);
> +  void (*update_hash)(const struct MG_UECC_HashContext *context,
> +                      const uint8_t *message, unsigned message_size);
> +  void (*finish_hash)(const struct MG_UECC_HashContext *context,
> +                      uint8_t *hash_result);
> +  unsigned
> +      block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */
> +  unsigned
> +      result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */
> +  uint8_t *tmp;    /* Must point to a buffer of at least (2 * result_size +
> +                      block_size) bytes. */
> +} MG_UECC_HashContext;
> +
> +/* mg_uecc_sign_deterministic() function.
> +Generate an ECDSA signature for a given hash value, using a deterministic
> +algorithm (see RFC 6979). You do not need to set the RNG using mg_uecc_set_rng()
> +before calling this function; however, if the RNG is defined it will improve
> +resistance to side-channel attacks.
> +
> +Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and
> +pass it to this function along with your private key and a hash context. Note
> +that the message_hash does not need to be computed with the same hash function
> +used by hash_context.
> +
> +Inputs:
> +    private_key  - Your private key.
> +    message_hash - The hash of the message to sign.
> +    hash_size    - The size of message_hash in bytes.
> +    hash_context - A hash context to use.
> +
> +Outputs:
> +    signature - Will be filled in with the signature value.
> +
> +Returns 1 if the signature generated successfully, 0 if an error occurred.
> +*/
> +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> +                               const uint8_t *message_hash, unsigned hash_size,
> +                               const MG_UECC_HashContext *hash_context,
> +                               uint8_t *signature, MG_UECC_Curve curve);
> +
> +/* mg_uecc_verify() function.
> +Verify an ECDSA signature.
> +
> +Usage: Compute the hash of the signed data using the same hash as the signer and
> +pass it to this function along with the signer's public key and the signature
> +values (r and s).
> +
> +Inputs:
> +    public_key   - The signer's public key.
> +    message_hash - The hash of the signed data.
> +    hash_size    - The size of message_hash in bytes.
> +    signature    - The signature value.
> +
> +Returns 1 if the signature is valid, 0 if it is invalid.
> +*/
> +int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash,
> +                   unsigned hash_size, const uint8_t *signature,
> +                   MG_UECC_Curve curve);
> +
> +#ifdef __cplusplus
> +} /* end of extern "C" */
> +#endif
> +
> +#endif /* _UECC_H_ */
> +
> +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
> +
> +#ifndef _UECC_VLI_H_
> +#define _UECC_VLI_H_
> +
> +//
> +//
> +
> +/* Functions for raw large-integer manipulation. These are only available
> +   if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
> +#ifndef MG_UECC_ENABLE_VLI_API
> +#define MG_UECC_ENABLE_VLI_API 0
> +#endif
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#if MG_UECC_ENABLE_VLI_API
> +
> +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
> +
> +/* Constant-time comparison to zero - secure way to compare long integers */
> +/* Returns 1 if vli == 0, 0 otherwise. */
> +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
> +                                  wordcount_t num_words);
> +
> +/* Returns nonzero if bit 'bit' of vli is set. */
> +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, bitcount_t bit);
> +
> +/* Counts the number of bits required to represent vli. */
> +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
> +                               const wordcount_t max_words);
> +
> +/* Sets dest = src. */
> +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t *src,
> +                     wordcount_t num_words);
> +
> +/* Constant-time comparison function - secure way to compare long integers */
> +/* Returns one if left == right, zero otherwise */
> +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
> +                                 const mg_uecc_word_t *right,
> +                                 wordcount_t num_words);
> +
> +/* Constant-time comparison function - secure way to compare long integers */
> +/* Returns sign of left - right, in constant time. */
> +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
> +                            const mg_uecc_word_t *right, wordcount_t num_words);
> +
> +/* Computes vli = vli >> 1. */
> +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t num_words);
> +
> +/* Computes result = left + right, returning carry. Can modify in place. */
> +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
> +                               const mg_uecc_word_t *left,
> +                               const mg_uecc_word_t *right,
> +                               wordcount_t num_words);
> +
> +/* Computes result = left - right, returning borrow. Can modify in place. */
> +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> +                               const mg_uecc_word_t *left,
> +                               const mg_uecc_word_t *right,
> +                               wordcount_t num_words);
> +
> +/* Computes result = left * right. Result must be 2 * num_words long. */
> +void mg_uecc_vli_mult(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                      const mg_uecc_word_t *right, wordcount_t num_words);
> +
> +/* Computes result = left^2. Result must be 2 * num_words long. */
> +void mg_uecc_vli_square(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                        wordcount_t num_words);
> +
> +/* Computes result = (left + right) % mod.
> +   Assumes that left < mod and right < mod, and that result does not overlap
> +   mod. */
> +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                        const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> +                        wordcount_t num_words);
> +
> +/* Computes result = (left - right) % mod.
> +   Assumes that left < mod and right < mod, and that result does not overlap
> +   mod. */
> +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                        const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> +                        wordcount_t num_words);
> +
> +/* Computes result = product % mod, where product is 2N words long.
> +   Currently only designed to work for mod == curve->p or curve_n. */
> +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t *product,
> +                      const mg_uecc_word_t *mod, wordcount_t num_words);
> +
> +/* Calculates result = product (mod curve->p), where product is up to
> +   2 * curve->num_words long. */
> +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product,
> +                           MG_UECC_Curve curve);
> +
> +/* Computes result = (left * right) % mod.
> +   Currently only designed to work for mod == curve->p or curve_n. */
> +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                         const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> +                         wordcount_t num_words);
> +
> +/* Computes result = (left * right) % curve->p. */
> +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
> +                              const mg_uecc_word_t *left,
> +                              const mg_uecc_word_t *right, MG_UECC_Curve curve);
> +
> +/* Computes result = left^2 % mod.
> +   Currently only designed to work for mod == curve->p or curve_n. */
> +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const mg_uecc_word_t *left,
> +                           const mg_uecc_word_t *mod, wordcount_t num_words);
> +
> +/* Computes result = left^2 % curve->p. */
> +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> +                                const mg_uecc_word_t *left,
> +                                MG_UECC_Curve curve);
> +
> +/* Computes result = (1 / input) % mod.*/
> +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const mg_uecc_word_t *input,
> +                        const mg_uecc_word_t *mod, wordcount_t num_words);
> +
> +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> +/* Calculates a = sqrt(a) (mod curve->p) */
> +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
> +#endif
> +
> +/* Converts an integer in uECC native format to big-endian bytes. */
> +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
> +                               const mg_uecc_word_t *native);
> +/* Converts big-endian bytes to an integer in uECC native format. */
> +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const uint8_t *bytes,
> +                               int num_bytes);
> +
> +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
> +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
> +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
> +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
> +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
> +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
> +
> +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
> +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
> +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
> +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
> +
> +int mg_uecc_valid_point(const mg_uecc_word_t *point, MG_UECC_Curve curve);
> +
> +/* Multiplies a point by a scalar. Points are represented by the X coordinate
> +   followed by the Y coordinate in the same array, both coordinates are
> +   curve->num_words long. Note that scalar must be curve->num_n_words long (NOT
> +   curve->num_words). */
> +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
> +                        const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
> +
> +/* Generates a random integer in the range 0 < random < top.
> +   Both random and top have num_words words. */
> +int mg_uecc_generate_random_int(mg_uecc_word_t *random,
> +                                const mg_uecc_word_t *top,
> +                                wordcount_t num_words);
> +
> +#endif /* MG_UECC_ENABLE_VLI_API */
> +
> +#ifdef __cplusplus
> +} /* end of extern "C" */
> +#endif
> +
> +#endif /* _UECC_VLI_H_ */
> +
> +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
> +
> +#ifndef _UECC_TYPES_H_
> +#define _UECC_TYPES_H_
> +
> +#ifndef MG_UECC_PLATFORM
> +#if defined(__AVR__) && __AVR__
> +#define MG_UECC_PLATFORM mg_uecc_avr
> +#elif defined(__thumb2__) || \
> +    defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
> +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
> +#elif defined(__thumb__)
> +#define MG_UECC_PLATFORM mg_uecc_arm_thumb
> +#elif defined(__arm__) || defined(_M_ARM)
> +#define MG_UECC_PLATFORM mg_uecc_arm
> +#elif defined(__aarch64__)
> +#define MG_UECC_PLATFORM mg_uecc_arm64
> +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
> +    defined(__I86__)
> +#define MG_UECC_PLATFORM mg_uecc_x86
> +#elif defined(__amd64__) || defined(_M_X64)
> +#define MG_UECC_PLATFORM mg_uecc_x86_64
> +#else
> +#define MG_UECC_PLATFORM mg_uecc_arch_other
> +#endif
> +#endif
> +
> +#ifndef MG_UECC_ARM_USE_UMAAL
> +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
> +#define MG_UECC_ARM_USE_UMAAL 1
> +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \
> +    (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
> +#define MG_UECC_ARM_USE_UMAAL 1
> +#else
> +#define MG_UECC_ARM_USE_UMAAL 0
> +#endif
> +#endif
> +
> +#ifndef MG_UECC_WORD_SIZE
> +#if MG_UECC_PLATFORM == mg_uecc_avr
> +#define MG_UECC_WORD_SIZE 1
> +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64)
> +#define MG_UECC_WORD_SIZE 8
> +#else
> +#define MG_UECC_WORD_SIZE 4
> +#endif
> +#endif
> +
> +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
> +    (MG_UECC_WORD_SIZE != 8)
> +#error "Unsupported value for MG_UECC_WORD_SIZE"
> +#endif
> +
> +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
> +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
> +#undef MG_UECC_WORD_SIZE
> +#define MG_UECC_WORD_SIZE 1
> +#endif
> +
> +#if ((MG_UECC_PLATFORM == mg_uecc_arm ||         \
> +      MG_UECC_PLATFORM == mg_uecc_arm_thumb ||   \
> +      MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
> +     (MG_UECC_WORD_SIZE != 4))
> +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
> +#undef MG_UECC_WORD_SIZE
> +#define MG_UECC_WORD_SIZE 4
> +#endif
> +
> +typedef int8_t wordcount_t;
> +typedef int16_t bitcount_t;
> +typedef int8_t cmpresult_t;
> +
> +#if (MG_UECC_WORD_SIZE == 1)
> +
> +typedef uint8_t mg_uecc_word_t;
> +typedef uint16_t mg_uecc_dword_t;
> +
> +#define HIGH_BIT_SET 0x80
> +#define MG_UECC_WORD_BITS 8
> +#define MG_UECC_WORD_BITS_SHIFT 3
> +#define MG_UECC_WORD_BITS_MASK 0x07
> +
> +#elif (MG_UECC_WORD_SIZE == 4)
> +
> +typedef uint32_t mg_uecc_word_t;
> +typedef uint64_t mg_uecc_dword_t;
> +
> +#define HIGH_BIT_SET 0x80000000
> +#define MG_UECC_WORD_BITS 32
> +#define MG_UECC_WORD_BITS_SHIFT 5
> +#define MG_UECC_WORD_BITS_MASK 0x01F
> +
> +#elif (MG_UECC_WORD_SIZE == 8)
> +
> +typedef uint64_t mg_uecc_word_t;
> +
> +#define HIGH_BIT_SET 0x8000000000000000U
> +#define MG_UECC_WORD_BITS 64
> +#define MG_UECC_WORD_BITS_SHIFT 6
> +#define MG_UECC_WORD_BITS_MASK 0x03F
> +
> +#endif /* MG_UECC_WORD_SIZE */
> +
> +#endif /* _UECC_TYPES_H_ */
> +// End of uecc BSD-2
>
>
>   struct mg_connection;
>   typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
> -                                   void *ev_data, void *fn_data);
> +                                   void *ev_data);
>   void mg_call(struct mg_connection *c, int ev, void *ev_data);
>   void mg_error(struct mg_connection *c, const char *fmt, ...);
>
>   enum {
> -  MG_EV_ERROR,       // Error                        char *error_message
> -  MG_EV_OPEN,        // Connection created           NULL
> -  MG_EV_POLL,        // mg_mgr_poll iteration        uint64_t *uptime_millis
> -  MG_EV_RESOLVE,     // Host name is resolved        NULL
> -  MG_EV_CONNECT,     // Connection established       NULL
> -  MG_EV_ACCEPT,      // Connection accepted          NULL
> -  MG_EV_TLS_HS,      // TLS handshake succeeded      NULL
> -  MG_EV_READ,        // Data received from socket    long *bytes_read
> -  MG_EV_WRITE,       // Data written to socket       long *bytes_written
> -  MG_EV_CLOSE,       // Connection closed            NULL
> -  MG_EV_HTTP_MSG,    // HTTP request/response        struct mg_http_message *
> -  MG_EV_HTTP_CHUNK,  // HTTP chunk (partial msg)     struct mg_http_message *
> -  MG_EV_WS_OPEN,     // Websocket handshake done     struct mg_http_message *
> -  MG_EV_WS_MSG,      // Websocket msg, text or bin   struct mg_ws_message *
> -  MG_EV_WS_CTL,      // Websocket control msg        struct mg_ws_message *
> -  MG_EV_MQTT_CMD,    // MQTT low-level command       struct mg_mqtt_message *
> -  MG_EV_MQTT_MSG,    // MQTT PUBLISH received        struct mg_mqtt_message *
> -  MG_EV_MQTT_OPEN,   // MQTT CONNACK received        int *connack_status_code
> -  MG_EV_SNTP_TIME,   // SNTP time received           uint64_t *epoch_millis
> -  MG_EV_USER         // Starting ID for user events
> +  MG_EV_ERROR,      // Error                        char *error_message
> +  MG_EV_OPEN,       // Connection created           NULL
> +  MG_EV_POLL,       // mg_mgr_poll iteration        uint64_t *uptime_millis
> +  MG_EV_RESOLVE,    // Host name is resolved        NULL
> +  MG_EV_CONNECT,    // Connection established       NULL
> +  MG_EV_ACCEPT,     // Connection accepted          NULL
> +  MG_EV_TLS_HS,     // TLS handshake succeeded      NULL
> +  MG_EV_READ,       // Data received from socket    long *bytes_read
> +  MG_EV_WRITE,      // Data written to socket       long *bytes_written
> +  MG_EV_CLOSE,      // Connection closed            NULL
> +  MG_EV_HTTP_HDRS,  // HTTP headers                 struct mg_http_message *
> +  MG_EV_HTTP_MSG,   // Full HTTP request/response   struct mg_http_message *
> +  MG_EV_WS_OPEN,    // Websocket handshake done     struct mg_http_message *
> +  MG_EV_WS_MSG,     // Websocket msg, text or bin   struct mg_ws_message *
> +  MG_EV_WS_CTL,     // Websocket control msg        struct mg_ws_message *
> +  MG_EV_MQTT_CMD,   // MQTT low-level command       struct mg_mqtt_message *
> +  MG_EV_MQTT_MSG,   // MQTT PUBLISH received        struct mg_mqtt_message *
> +  MG_EV_MQTT_OPEN,  // MQTT CONNACK received        int *connack_status_code
> +  MG_EV_SNTP_TIME,  // SNTP time received           uint64_t *epoch_millis
> +  MG_EV_WAKEUP,     // mg_wakeup() data received    struct mg_str *data
> +  MG_EV_USER        // Starting ID for user events
>   };
>
>
> @@ -1021,10 +1981,10 @@ struct mg_dns {
>   };
>
>   struct mg_addr {
> -  uint16_t port;    // TCP or UDP port in network byte order
> -  uint32_t ip;      // IP address in network byte order
> -  uint8_t ip6[16];  // IPv6 address
> -  bool is_ip6;      // True when address is IPv6 address
> +  uint8_t ip[16];    // Holds IPv4 or IPv6 address, in network byte order
> +  uint16_t port;     // TCP or UDP port in network byte order
> +  uint8_t scope_id;  // IPv6 scope ID
> +  bool is_ip6;       // True when address is IPv6 address
>   };
>
>   struct mg_mgr {
> @@ -1036,12 +1996,14 @@ struct mg_mgr {
>     unsigned long nextid;         // Next connection ID
>     unsigned long timerid;        // Next timer ID
>     void *userdata;               // Arbitrary user data pointer
> +  void *tls_ctx;                // TLS context shared by all TLS sessions
>     uint16_t mqtt_id;             // MQTT IDs for pub/sub
>     void *active_dns_requests;    // DNS requests in progress
>     struct mg_timer *timers;      // Active timers
>     int epoll_fd;                 // Used when MG_EPOLL_ENABLE=1
>     void *priv;                   // Used by the MIP stack
>     size_t extraconnsize;         // Used by the MIP stack
> +  MG_SOCKET_TYPE pipe;          // Socketpair end for mg_wakeup()
>   #if MG_ENABLE_FREERTOS_TCP
>     SocketSet_t ss;  // NOTE(lsm): referenced from socket struct
>   #endif
> @@ -1056,6 +2018,8 @@ struct mg_connection {
>     unsigned long id;            // Auto-incrementing unique connection ID
>     struct mg_iobuf recv;        // Incoming data
>     struct mg_iobuf send;        // Outgoing data
> +  struct mg_iobuf prof;        // Profile data enabled by MG_ENABLE_PROFILE
> +  struct mg_iobuf rtls;        // TLS only. Incoming encrypted data
>     mg_event_handler_t fn;       // User-specified event handler function
>     void *fn_data;               // User-specified function parameter
>     mg_event_handler_t pfn;      // Protocol-specific handler function
> @@ -1066,6 +2030,7 @@ struct mg_connection {
>     unsigned is_client : 1;      // Outbound (client) connection
>     unsigned is_accepted : 1;    // Accepted (server) connection
>     unsigned is_resolving : 1;   // Non-blocking DNS resolution is in progress
> +  unsigned is_arplooking : 1;  // Non-blocking ARP resolution is in progress
>     unsigned is_connecting : 1;  // Non-blocking connect is in progress
>     unsigned is_tls : 1;         // TLS-enabled connection
>     unsigned is_tls_hs : 1;      // TLS handshake is in progress
> @@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *, const void *, size_t);
>   size_t mg_printf(struct mg_connection *, const char *fmt, ...);
>   size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap);
>   bool mg_aton(struct mg_str str, struct mg_addr *addr);
> -int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp);
>
>   // These functions are used to integrate with custom network stacks
>   struct mg_connection *mg_alloc_conn(struct mg_mgr *);
>   void mg_close_conn(struct mg_connection *c);
>   bool mg_open_listener(struct mg_connection *c, const char *url);
> +
> +// Utility functions
> +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len);
> +bool mg_wakeup_init(struct mg_mgr *);
>   struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
>                                 unsigned flags, void (*fn)(void *), void *arg);
>
> -// Low-level IO primives used by TLS layer
> -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> -long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
> -long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
> -
>
>
>
> @@ -1127,7 +2090,6 @@ struct mg_http_message {
>     struct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers
>     struct mg_str body;                                  // Body
>     struct mg_str head;                                  // Request + headers
> -  struct mg_str chunk;    // Chunk for chunked encoding,  or partial body
>     struct mg_str message;  // Request + headers + body
>   };
>
> @@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t);
>   int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form);
>   size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len);
>   void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
> -bool mg_http_match_uri(const struct mg_http_message *, const char *glob);
>   long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> -                    struct mg_fs *fs, const char *path, size_t max_size);
> +                    struct mg_fs *fs, const char *dir, size_t max_size);
>   void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
>   struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
>   size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
> @@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>                          const char *fullpath);
>
>
> +#define MG_TLS_NONE 0     // No TLS support
> +#define MG_TLS_MBED 1     // mbedTLS
> +#define MG_TLS_OPENSSL 2  // OpenSSL
> +#define MG_TLS_WOLFSSL 5  // WolfSSL (based on OpenSSL)
> +#define MG_TLS_BUILTIN 3  // Built-in
> +#define MG_TLS_CUSTOM 4   // Custom implementation
> +
> +#ifndef MG_TLS
> +#define MG_TLS MG_TLS_NONE
> +#endif
> +
>
>
>
>
>   struct mg_tls_opts {
> -  const char *ca;         // CA certificate file. For both listeners and clients
> -  const char *crl;        // Certificate Revocation List. For clients
> -  const char *cert;       // Certificate
> -  const char *certkey;    // Certificate key
> -  const char *ciphers;    // Cipher list
> -  struct mg_str srvname;  // If not empty, enables server name verification
> -  struct mg_fs *fs;       // FS API for reading certificate files
> +  struct mg_str ca;       // PEM or DER
> +  struct mg_str cert;     // PEM or DER
> +  struct mg_str key;      // PEM or DER
> +  struct mg_str name;     // If not empty, enable host name verification
> +  int skip_verification;  // Skip certificate and host name verification
>   };
>
> -void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *);
> +void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts);
>   void mg_tls_free(struct mg_connection *);
>   long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
>   long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
>   size_t mg_tls_pending(struct mg_connection *);
>   void mg_tls_handshake(struct mg_connection *);
>
> +// Private
> +void mg_tls_ctx_init(struct mg_mgr *);
> +void mg_tls_ctx_free(struct mg_mgr *);
> +
> +// Low-level IO primives used by TLS layer
> +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> +long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
> +long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
>
>
>
>
>
>
> -#if MG_ENABLE_MBEDTLS
> +
> +#if MG_TLS == MG_TLS_MBED
>   #include <mbedtls/debug.h>
>   #include <mbedtls/net_sockets.h>
>   #include <mbedtls/ssl.h>
> +#include <mbedtls/ssl_ticket.h>
> +
> +struct mg_tls_ctx {
> +  int dummy;
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +  mbedtls_ssl_ticket_context tickets;
> +#endif
> +};
>
>   struct mg_tls {
> -  char *cafile;             // CA certificate path
>     mbedtls_x509_crt ca;      // Parsed CA certificate
>     mbedtls_x509_crt cert;    // Parsed certificate
> +  mbedtls_pk_context pk;    // Private key context
>     mbedtls_ssl_context ssl;  // SSL/TLS context
>     mbedtls_ssl_config conf;  // SSL-TLS config
> -  mbedtls_pk_context pk;    // Private key context
> +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> +  mbedtls_ssl_ticket_context ticket;  // Session tickets context
> +#endif
>   };
>   #endif
>
>
> -#if MG_ENABLE_OPENSSL
> +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>
> -#ifdef CONFIG_SSL_IMPL_WOLFSSL
> -#include <wolfssl/openssl/err.h>
> -#include <wolfssl/openssl/ssl.h>
> -#else
>   #include <openssl/err.h>
>   #include <openssl/ssl.h>
> -#endif
>
>   struct mg_tls {
> +  BIO_METHOD *bm;
>     SSL_CTX *ctx;
>     SSL *ssl;
>   };
> @@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char *buf, size_t len);
>   #define MQTT_CMD_DISCONNECT 14
>   #define MQTT_CMD_AUTH 15
>
> +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
> +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
> +#define MQTT_PROP_CONTENT_TYPE 0x03
> +#define MQTT_PROP_RESPONSE_TOPIC 0x08
> +#define MQTT_PROP_CORRELATION_DATA 0x09
> +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
> +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
> +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
> +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
> +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
> +#define MQTT_PROP_AUTHENTICATION_DATA 0x16
> +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
> +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
> +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
> +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
> +#define MQTT_PROP_SERVER_REFERENCE 0x1C
> +#define MQTT_PROP_REASON_STRING 0x1F
> +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
> +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
> +#define MQTT_PROP_TOPIC_ALIAS 0x23
> +#define MQTT_PROP_MAXIMUM_QOS 0x24
> +#define MQTT_PROP_RETAIN_AVAILABLE 0x25
> +#define MQTT_PROP_USER_PROPERTY 0x26
> +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
> +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
> +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
> +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
> +
> +enum {
> +  MQTT_PROP_TYPE_BYTE,
> +  MQTT_PROP_TYPE_STRING,
> +  MQTT_PROP_TYPE_STRING_PAIR,
> +  MQTT_PROP_TYPE_BINARY_DATA,
> +  MQTT_PROP_TYPE_VARIABLE_INT,
> +  MQTT_PROP_TYPE_INT,
> +  MQTT_PROP_TYPE_SHORT
> +};
> +
>   enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
>
> +struct mg_mqtt_prop {
> +  uint8_t id;         // Enumerated at MQTT5 Reference
> +  uint32_t iv;        // Integer value for 8-, 16-, 32-bit integers types
> +  struct mg_str key;  // Non-NULL only for user property type
> +  struct mg_str val;  // Non-NULL only for UTF-8 types and user properties
> +};
> +
>   struct mg_mqtt_opts {
> -  struct mg_str user;          // Username, can be empty
> -  struct mg_str pass;          // Password, can be empty
> -  struct mg_str client_id;     // Client ID
> -  struct mg_str will_topic;    // Will topic
> -  struct mg_str will_message;  // Will message
> -  uint8_t will_qos;            // Will message quality of service
> -  uint8_t version;             // Can be 4 (3.1.1), or 5. If 0, assume 4.
> -  uint16_t keepalive;          // Keep-alive timer in seconds
> -  bool will_retain;            // Retain last will
> -  bool clean;                  // Use clean session, 0 or 1
> +  struct mg_str user;               // Username, can be empty
> +  struct mg_str pass;               // Password, can be empty
> +  struct mg_str client_id;          // Client ID
> +  struct mg_str topic;              // message/subscription topic
> +  struct mg_str message;            // message content
> +  uint8_t qos;                      // message quality of service
> +  uint8_t version;                  // Can be 4 (3.1.1), or 5. If 0, assume 4
> +  uint16_t keepalive;               // Keep-alive timer in seconds
> +  uint16_t retransmit_id;           // For PUBLISH, init to 0
> +  bool retain;                      // Retain flag
> +  bool clean;                       // Clean session flag
> +  struct mg_mqtt_prop *props;       // MQTT5 props array
> +  size_t num_props;                 // number of props
> +  struct mg_mqtt_prop *will_props;  // Valid only for CONNECT packet (MQTT5)
> +  size_t num_will_props;            // Number of will props
>   };
>
>   struct mg_mqtt_message {
> -  struct mg_str topic;  // Parsed topic
> -  struct mg_str data;   // Parsed message
> -  struct mg_str dgram;  // Whole MQTT datagram, including headers
> -  uint16_t id;  // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
> -  uint8_t cmd;  // MQTT command, one of MQTT_CMD_*
> -  uint8_t qos;  // Quality of service
> -  uint8_t ack;  // Connack return code. 0 - success
> +  struct mg_str topic;  // Parsed topic for PUBLISH
> +  struct mg_str data;   // Parsed message for PUBLISH
> +  struct mg_str dgram;  // Whole MQTT packet, including headers
> +  uint16_t id;          // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
> +  uint8_t cmd;          // MQTT command, one of MQTT_CMD_*
> +  uint8_t qos;          // Quality of service
> +  uint8_t ack;          // CONNACK return code, 0 = success
> +  size_t props_start;   // Offset to the start of the properties (MQTT5)
> +  size_t props_size;    // Length of the properties
>   };
>
>   struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
> @@ -1327,15 +2364,16 @@ struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
>   struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
>                                        mg_event_handler_t fn, void *fn_data);
>   void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts);
> -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> -                 struct mg_str data, int qos, bool retain);
> -void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos);
> +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts);
> +void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts);
>   int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *);
>   void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags,
>                            uint32_t len);
>   void mg_mqtt_ping(struct mg_connection *);
>   void mg_mqtt_pong(struct mg_connection *);
> -void mg_mqtt_disconnect(struct mg_connection *);
> +void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *);
> +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *,
> +                         size_t ofs);
>
>
>
> @@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
>   enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 };
>   int mg_json_get(struct mg_str json, const char *path, int *toklen);
>
> +struct mg_str mg_json_get_tok(struct mg_str json, const char *path);
>   bool mg_json_get_num(struct mg_str json, const char *path, double *v);
>   bool mg_json_get_bool(struct mg_str json, const char *path, bool *v);
>   long mg_json_get_long(struct mg_str json, const char *path, long dflt);
> @@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json, const char *path);
>   char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
>   char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
>
> +bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
> +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
> +                    struct mg_str *val);
> +
>
>
>
> @@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
>   void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
>   void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
>   void mg_rpc_list(struct mg_rpc_req *r);
> +// Copyright (c) 2023 Cesanta Software Limited
> +// All rights reserved
>
>
>
>
>
> -struct mip_if;  // MIP network interface
> +#define MG_OTA_NONE 0      // No OTA support
> +#define MG_OTA_FLASH 1     // OTA via an internal flash
> +#define MG_OTA_ESP32 2     // ESP32 OTA implementation
> +#define MG_OTA_CUSTOM 100  // Custom implementation
>
> -struct mip_driver {
> -  bool (*init)(struct mip_if *);                         // Initialise driver
> -  size_t (*tx)(const void *, size_t, struct mip_if *);   // Transmit frame
> -  size_t (*rx)(void *buf, size_t len, struct mip_if *);  // Receive frame (poll)
> -  bool (*up)(struct mip_if *);                           // Up/down status
> -};
> +#ifndef MG_OTA
> +#define MG_OTA MG_OTA_NONE
> +#endif
> +
> +#if defined(__GNUC__) && !defined(__APPLE__)
> +#define MG_IRAM __attribute__((section(".iram")))
> +#else
> +#define MG_IRAM
> +#endif
>
> -// Receive queue - single producer, single consumer queue.  Interrupt-based
> -// drivers copy received frames to the queue in interrupt context. mip_poll()
> -// function runs in event loop context, reads from the queue
> -struct queue {
> -  uint8_t *buf;
> -  size_t len;
> -  volatile size_t tail, head;
> +// Firmware update API
> +bool mg_ota_begin(size_t new_firmware_size);     // Start writing
> +bool mg_ota_write(const void *buf, size_t len);  // Write chunk, aligned to 1k
> +bool mg_ota_end(void);                           // Stop writing
> +
> +enum {
> +  MG_OTA_UNAVAILABLE = 0,  // No OTA information is present
> +  MG_OTA_FIRST_BOOT = 1,   // Device booting the first time after the OTA
> +  MG_OTA_UNCOMMITTED = 2,  // Ditto, but marking us for the rollback
> +  MG_OTA_COMMITTED = 3     // The firmware is good
>   };
> +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
> +
> +int mg_ota_status(int firmware);          // Return firmware status MG_OTA_*
> +uint32_t mg_ota_crc32(int firmware);      // Return firmware checksum
> +uint32_t mg_ota_timestamp(int firmware);  // Firmware timestamp, UNIX UTC epoch
> +size_t mg_ota_size(int firmware);         // Firmware size
> +
> +bool mg_ota_commit(void);        // Commit current firmware
> +bool mg_ota_rollback(void);      // Rollback to the previous firmware
> +MG_IRAM void mg_ota_boot(void);  // Bootloader function
> +// Copyright (c) 2023 Cesanta Software Limited
> +// All rights reserved
>
> -#define MIP_ARP_ENTRIES 5  // Number of ARP cache entries. Maximum 21
> -#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES)  // ARP cache size
> +
> +
> +
> +
> +#define MG_DEVICE_NONE 0  // Dummy system
> +
> +#define MG_DEVICE_STM32H5 1     // STM32 H5
> +#define MG_DEVICE_STM32H7 2     // STM32 H7
> +#define MG_DEVICE_CH32V307 100  // WCH CH32V307
> +#define MG_DEVICE_U2A 200       // Renesas U2A16, U2A8, U2A6
> +#define MG_DEVICE_RT1020 300    // IMXRT1020
> +#define MG_DEVICE_RT1060 301    // IMXRT1060
> +#define MG_DEVICE_CUSTOM 1000   // Custom implementation
> +
> +#ifndef MG_DEVICE
> +#define MG_DEVICE MG_DEVICE_NONE
> +#endif
> +
> +// Flash information
> +void *mg_flash_start(void);         // Return flash start address
> +size_t mg_flash_size(void);         // Return flash size
> +size_t mg_flash_sector_size(void);  // Return flash sector size
> +size_t mg_flash_write_align(void);  // Return flash write align, minimum 4
> +int mg_flash_bank(void);            // 0: not dual bank, 1: bank1, 2: bank2
> +
> +// Write, erase, swap bank
> +bool mg_flash_write(void *addr, const void *buf, size_t len);
> +bool mg_flash_erase(void *sector);
> +bool mg_flash_swap_bank(void);
> +
> +// Convenience functions to store data on a flash sector with wear levelling
> +// If `sector` is NULL, then the last sector of flash is used
> +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len);
> +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len);
> +
> +void mg_device_reset(void);  // Reboot device immediately
> +
> +
> +
> +
> +
> +
> +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> +struct mg_tcpip_if;  // Mongoose TCP/IP network interface
> +
> +struct mg_tcpip_driver {
> +  bool (*init)(struct mg_tcpip_if *);                         // Init driver
> +  size_t (*tx)(const void *, size_t, struct mg_tcpip_if *);   // Transmit frame
> +  size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *);  // Receive frame
> +  bool (*up)(struct mg_tcpip_if *);                           // Up/down status
> +};
>
>   // Network interface
> -struct mip_if {
> -  uint8_t mac[6];             // MAC address. Must be set to a valid MAC
> -  uint32_t ip, mask, gw;      // IP address, mask, default gateway
> -  struct mg_str rx;           // Output (TX) buffer
> -  struct mg_str tx;           // Input (RX) buffer
> -  bool enable_dhcp_client;    // Enable DCHP client
> -  bool enable_dhcp_server;    // Enable DCHP server
> -  struct mip_driver *driver;  // Low level driver
> -  void *driver_data;          // Driver-specific data
> -  struct mg_mgr *mgr;         // Mongoose event manager
> -  struct queue queue;         // Set queue.len for interrupt based drivers
> +struct mg_tcpip_if {
> +  uint8_t mac[6];                  // MAC address. Must be set to a valid MAC
> +  uint32_t ip, mask, gw;           // IP address, mask, default gateway
> +  struct mg_str tx;                // Output (TX) buffer
> +  bool enable_dhcp_client;         // Enable DCHP client
> +  bool enable_dhcp_server;         // Enable DCHP server
> +  bool enable_get_gateway;         // DCHP server sets client as gateway
> +  bool enable_crc32_check;         // Do a CRC check on RX frames and strip it
> +  bool enable_mac_check;           // Do a MAC check on RX frames
> +  struct mg_tcpip_driver *driver;  // Low level driver
> +  void *driver_data;               // Driver-specific data
> +  struct mg_mgr *mgr;              // Mongoose event manager
> +  struct mg_queue recv_queue;      // Receive queue
> +  uint16_t mtu;                    // Interface MTU
> +#define MG_TCPIP_MTU_DEFAULT 1500
>
>     // Internal state, user can use it but should not change it
> -  uint64_t now;                   // Current time
> -  uint64_t timer_1000ms;          // 1000 ms timer: for DHCP and link state
> -  uint64_t lease_expire;          // Lease expiration time
> -  uint8_t arp_cache[MIP_ARP_CS];  // Each entry is 12 bytes
> -  uint16_t eport;                 // Next ephemeral port
> -  uint16_t dropped;               // Number of dropped frames
> -  uint8_t state;                  // Current state
> -#define MIP_STATE_DOWN 0          // Interface is down
> -#define MIP_STATE_UP 1            // Interface is up
> -#define MIP_STATE_READY 2         // Interface is up and has IP
> +  uint8_t gwmac[6];             // Router's MAC
> +  uint64_t now;                 // Current time
> +  uint64_t timer_1000ms;        // 1000 ms timer: for DHCP and link state
> +  uint64_t lease_expire;        // Lease expiration time, in ms
> +  uint16_t eport;               // Next ephemeral port
> +  volatile uint32_t ndrop;      // Number of received, but dropped frames
> +  volatile uint32_t nrecv;      // Number of received frames
> +  volatile uint32_t nsent;      // Number of transmitted frames
> +  volatile uint32_t nerr;       // Number of driver errors
> +  uint8_t state;                // Current state
> +#define MG_TCPIP_STATE_DOWN 0   // Interface is down
> +#define MG_TCPIP_STATE_UP 1     // Interface is up
> +#define MG_TCPIP_STATE_REQ 2    // Interface is up and has requested an IP
> +#define MG_TCPIP_STATE_READY 3  // Interface is up and has an IP assigned
>   };
>
> -void mip_init(struct mg_mgr *, struct mip_if *);
> -void mip_free(struct mip_if *);
> -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
> -size_t mip_qread(void *buf, struct mip_if *ifp);
> -// conveniency rx function for IRQ-driven drivers
> -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
> -
> -extern struct mip_driver mip_driver_stm32;
> -extern struct mip_driver mip_driver_w5500;
> -extern struct mip_driver mip_driver_tm4c;
> +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
> +void mg_tcpip_free(struct mg_tcpip_if *);
> +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
> +
> +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
> +extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
> +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
> +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
> +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
> +extern struct mg_tcpip_driver mg_tcpip_driver_same54;
> +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
> +extern struct mg_tcpip_driver mg_tcpip_driver_ra;
> +extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
> +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
>
>   // Drivers that require SPI, can use this SPI abstraction
> -struct mip_spi {
> +struct mg_tcpip_spi {
>     void *spi;                        // Opaque SPI bus descriptor
>     void (*begin)(void *);            // SPI begin: slave select low
>     void (*end)(void *);              // SPI end: slave select high
>     uint8_t (*txn)(void *, uint8_t);  // SPI transaction: write 1 byte, read reply
>   };
> +#endif
>
> -#ifdef MIP_QPROFILE
> -enum {
> -  QP_IRQTRIGGERED = 0,  // payload is number of interrupts so far
> -  QP_FRAMEPUSHED,       // available space in the frame queue
> -  QP_FRAMEPOPPED,       // available space in the frame queue
> -  QP_FRAMEDONE,         // available space in the frame queue
> -  QP_FRAMEDROPPED,      // number of dropped frames
> -  QP_QUEUEOVF  // profiling queue is full, payload is number of frame drops
> +
> +
> +// Macros to record timestamped events that happens with a connection.
> +// They are saved into a c->prof IO buffer, each event is a name and a 32-bit
> +// timestamp in milliseconds since connection init time.
> +//
> +// Test (run in two separate terminals):
> +//   make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
> +//   curl localhost:8000
> +// Output:
> +//   1ea1f1e7 2 net.c:150:mg_close_conn      3 profile:
> +//   1ea1f1e8 2 net.c:150:mg_close_conn      1ea1f1e6 init
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_OPEN
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_ACCEPT
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_READ
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_HTTP_MSG
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          0 EV_WRITE
> +//   1ea1f1e8 2 net.c:150:mg_close_conn          1 EV_CLOSE
> +//
> +// Usage:
> +//   Enable profiling by setting MG_ENABLE_PROFILE=1
> +//   Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure
> +
> +#if MG_ENABLE_PROFILE
> +struct mg_profitem {
> +  const char *name;    // Event name
> +  uint32_t timestamp;  // Milliseconds since connection creation (MG_EV_OPEN)
>   };
>
> -void qp_mark(unsigned int type, int len);
> -void qp_log(void);  // timestamp, type, payload
> -void qp_init(void);
> +#define MG_PROFILE_ALLOC_GRANULARITY 256  // Can save 32 items wih to realloc
> +
> +// Adding a profile item to the c->prof. Must be as fast as possible.
> +// Reallocation of the c->prof iobuf is not desirable here, that's why we
> +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
> +// This macro just inits and copies 8 bytes, and calls mg_millis(),
> +// which should be fast enough.
> +#define MG_PROF_ADD(c, name_)                                             \
> +  do {                                                                    \
> +    struct mg_iobuf *io = &c->prof;                                       \
> +    uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp;      \
> +    struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \
> +    mg_iobuf_add(io, io->len, &item, sizeof(item));                       \
> +  } while (0)
> +
> +// Initialising profile for a new connection. Not time sensitive
> +#define MG_PROF_INIT(c)                                          \
> +  do {                                                           \
> +    struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
> +    mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY);  \
> +    mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first));  \
> +  } while (0)
> +
> +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
> +
> +// Dumping the profile. Not time sensitive
> +#define MG_PROF_DUMP(c)                                            \
> +  do {                                                             \
> +    struct mg_iobuf *io = &c->prof;                                \
> +    struct mg_profitem *p = (struct mg_profitem *) io->buf;        \
> +    struct mg_profitem *e = &p[io->len / sizeof(*p)];              \
> +    MG_INFO(("%lu profile:", c->id));                              \
> +    while (p < e) {                                                \
> +      MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
> +      p++;                                                         \
> +    }                                                              \
> +  } while (0)
> +
>   #else
> -#define qp_mark(a, b)
> +#define MG_PROF_INIT(c)
> +#define MG_PROF_FREE(c)
> +#define MG_PROF_ADD(c, name)
> +#define MG_PROF_DUMP(c)
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS
> +
> +#include "Driver_ETH_MAC.h"  // keep this include
> +#include "Driver_ETH_PHY.h"  // keep this include
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT
> +
> +struct mg_tcpip_driver_imxrt_data {
> +  // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk),
> +  // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz
> +  // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
> +  //    ipg_clk       mdc_cr VALUE
> +  //    --------------------------
> +  //                  -1  <-- TODO() tell driver to guess the value
> +  //    25 MHz         4
> +  //    33 MHz         6
> +  //    40 MHz         7
> +  //    50 MHz         9
> +  //    66 MHz        13
> +  int mdc_cr;  // Valid values: -1 to 63
> +
> +  uint8_t phy_addr;  // PHY address
> +};
> +
> +#ifndef MG_TCPIP_PHY_ADDR
> +#define MG_TCPIP_PHY_ADDR 2
> +#endif
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 24
> +#endif
> +
> +#define MG_TCPIP_DRIVER_INIT(mgr)                                \
> +  do {                                                           \
> +    static struct mg_tcpip_driver_imxrt_data driver_data_;       \
> +    static struct mg_tcpip_if mif_;                              \
> +    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                      \
> +    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                   \
> +    mif_.ip = MG_TCPIP_IP;                                       \
> +    mif_.mask = MG_TCPIP_MASK;                                   \
> +    mif_.gw = MG_TCPIP_GW;                                       \
> +    mif_.driver = &mg_tcpip_driver_imxrt;                        \
> +    mif_.driver_data = &driver_data_;                            \
> +    MG_SET_MAC_ADDRESS(mif_.mac);                                \
> +    mg_tcpip_init(mgr, &mif_);                                   \
> +    MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
> +  } while (0)
> +
>   #endif
>
>
> -struct mip_driver_stm32_data {
> +
> +
> +struct mg_phy {
> +  uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
> +  void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
> +};
> +
> +// PHY configuration settings, bitmask
> +enum {
> +  MG_PHY_LEDS_ACTIVE_HIGH =
> +      (1 << 0),  // Set if PHY LEDs are connected to ground
> +  MG_PHY_CLOCKS_MAC =
> +      (1 << 1)   // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
> +};
> +
> +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
> +
> +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
> +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
> +               uint8_t *speed);
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA
> +
> +struct mg_tcpip_driver_ra_data {
> +  // MDC clock "divider". MDC clock is software generated,
> +  uint32_t clock;    // core clock frequency in Hz
> +  uint16_t irqno;    // IRQn, R_ICU->IELSR[irqno]
> +  uint8_t phy_addr;  // PHY address
> +};
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54
> +
> +struct mg_tcpip_driver_same54_data {
> +    int mdc_cr;
> +};
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 5
> +#endif
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> +    MG_ENABLE_DRIVER_STM32F
> +
> +struct mg_tcpip_driver_stm32f_data {
>     // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
>     //    HCLK range    DIVIDER    mdc_cr VALUE
>     //    -------------------------------------
> @@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
>     //    216-310 MHz   HCLK/124       5
>     //    110, 111 Reserved
>     int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
> +
> +  uint8_t phy_addr;  // PHY address
> +};
> +
> +#ifndef MG_TCPIP_PHY_ADDR
> +#define MG_TCPIP_PHY_ADDR 0
> +#endif
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 4
> +#endif
> +
> +#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
> +  do {                                                            \
> +    static struct mg_tcpip_driver_stm32f_data driver_data_;       \
> +    static struct mg_tcpip_if mif_;                               \
> +    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
> +    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
> +    mif_.ip = MG_TCPIP_IP;                                        \
> +    mif_.mask = MG_TCPIP_MASK;                                    \
> +    mif_.gw = MG_TCPIP_GW;                                        \
> +    mif_.driver = &mg_tcpip_driver_stm32f;                        \
> +    mif_.driver_data = &driver_data_;                             \
> +    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
> +    mg_tcpip_init(mgr, &mif_);                                    \
> +    MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
> +  } while (0)
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> +    MG_ENABLE_DRIVER_STM32H
> +
> +struct mg_tcpip_driver_stm32h_data {
> +  // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
> +  //    HCLK range    DIVIDER    mdc_cr VALUE
> +  //    -------------------------------------
> +  //                                -1  <-- tell driver to guess the value
> +  //    60-100 MHz    HCLK/42        0
> +  //    100-150 MHz   HCLK/62        1
> +  //    20-35 MHz     HCLK/16        2
> +  //    35-60 MHz     HCLK/26        3
> +  //    150-250 MHz   HCLK/102       4  <-- value for max speed HSI
> +  //    250-300 MHz   HCLK/124       5  <-- value for Nucleo-H* on CSI
> +  //    110, 111 Reserved
> +  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
> +
> +  uint8_t phy_addr;  // PHY address
> +  uint8_t phy_conf;  // PHY config
>   };
>
> +#ifndef MG_TCPIP_PHY_ADDR
> +#define MG_TCPIP_PHY_ADDR 0
> +#endif
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 4
> +#endif
> +
> +#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
> +  do {                                                            \
> +    static struct mg_tcpip_driver_stm32h_data driver_data_;       \
> +    static struct mg_tcpip_if mif_;                               \
> +    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
> +    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
> +    mif_.ip = MG_TCPIP_IP;                                        \
> +    mif_.mask = MG_TCPIP_MASK;                                    \
> +    mif_.gw = MG_TCPIP_GW;                                        \
> +    mif_.driver = &mg_tcpip_driver_stm32h;                        \
> +    mif_.driver_data = &driver_data_;                             \
> +    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
> +    mg_tcpip_init(mgr, &mif_);                                    \
> +    MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
> +  } while (0)
> +
> +#endif
> +
>
> -struct mip_driver_tm4c_data {
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
> +
> +struct mg_tcpip_driver_tm4c_data {
>     // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz
>     //    SYSCLK range   DIVIDER   mdc_cr VALUE
>     //    -------------------------------------
> @@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
>     int mdc_cr;  // Valid values: -1, 0, 1, 2, 3
>   };
>
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 1
> +#endif
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500
> +
> +#undef MG_ENABLE_TCPIP_DRIVER_INIT
> +#define MG_ENABLE_TCPIP_DRIVER_INIT 0
> +
> +#endif
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7
> +
> +struct mg_tcpip_driver_xmc7_data {
> +  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
> +  uint8_t phy_addr;
> +};
> +
> +#ifndef MG_TCPIP_PHY_ADDR
> +#define MG_TCPIP_PHY_ADDR 0
> +#endif
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 3
> +#endif
> +
> +#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
> +  do {                                                            \
> +    static struct mg_tcpip_driver_xmc7_data driver_data_;       \
> +    static struct mg_tcpip_if mif_;                               \
> +    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
> +    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
> +    mif_.ip = MG_TCPIP_IP;                                        \
> +    mif_.mask = MG_TCPIP_MASK;                                    \
> +    mif_.gw = MG_TCPIP_GW;                                        \
> +    mif_.driver = &mg_tcpip_driver_xmc7;                        \
> +    mif_.driver_data = &driver_data_;                             \
> +    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
> +    mg_tcpip_init(mgr, &mif_);                                    \
> +    MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
> +  } while (0)
> +
> +#endif
> +
> +
> +
> +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC
> +
> +struct mg_tcpip_driver_xmc_data {
> +  // 13.2.8.1 Station Management Functions
> +  // MDC clock divider (). MDC clock is derived from ETH MAC clock
> +  // It must not exceed 2.5MHz
> +  // ETH Clock range  DIVIDER       mdc_cr VALUE
> +  // --------------------------------------------
> +  //                                     -1  <-- tell driver to guess the value
> +  // 60-100 MHz       ETH Clock/42        0
> +  // 100-150 MHz      ETH Clock/62        1
> +  // 20-35 MHz        ETH Clock/16        2
> +  // 35-60 MHz        ETH Clock/26        3
> +  // 150-250 MHz      ETH Clock/102       4
> +  // 250-300 MHz      ETH Clock/124       5
> +  // 110, 111 Reserved
> +  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
> +  uint8_t phy_addr;
> +};
> +
> +#ifndef MG_TCPIP_PHY_ADDR
> +#define MG_TCPIP_PHY_ADDR 0
> +#endif
> +
> +#ifndef MG_DRIVER_MDC_CR
> +#define MG_DRIVER_MDC_CR 4
> +#endif
> +
> +#define MG_TCPIP_DRIVER_INIT(mgr)                                 \
> +  do {                                                            \
> +    static struct mg_tcpip_driver_xmc_data driver_data_;       \
> +    static struct mg_tcpip_if mif_;                               \
> +    driver_data_.mdc_cr = MG_DRIVER_MDC_CR;                       \
> +    driver_data_.phy_addr = MG_TCPIP_PHY_ADDR;                    \
> +    mif_.ip = MG_TCPIP_IP;                                        \
> +    mif_.mask = MG_TCPIP_MASK;                                    \
> +    mif_.gw = MG_TCPIP_GW;                                        \
> +    mif_.driver = &mg_tcpip_driver_xmc;                        \
> +    mif_.driver_data = &driver_data_;                             \
> +    MG_SET_MAC_ADDRESS(mif_.mac);                                 \
> +    mg_tcpip_init(mgr, &mif_);                                    \
> +    MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
> +  } while (0)
> +
> +#endif
> +
>   #ifdef __cplusplus
>   }
>   #endif
Michael Glembotzki July 9, 2024, 11:31 a.m. UTC | #2
Problem still exists with V3. The patch files I created with format patch 
are OK, but when sending it to Patchwork something seems to break with the 
header. I could post a github link, or do you have any idea what the 
problem could be?

Best regards
Michael
Stefano Babic schrieb am Sonntag, 7. Juli 2024 um 11:41:01 UTC+2:

> Hi Michael,
>
> I wanted to work on this, but I cannot apply clean, git am reports a
> malformed patch. Can you check and repost ? Thanks.
>
> Best regards,
> Stefano
>
> On 15.06.24 21:11, Michael Glembotzki wrote:
> > mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05
> >
> > Signed-off-by: Michael Glembotzki <Michael.G...@iris-sensing.com>
> > ---
> > mongoose/mongoose.c | 21614 +++++++++++++++++++++++++++++++-----------
> > mongoose/mongoose.h | 2003 +++-
> > 2 files changed, 17594 insertions(+), 6023 deletions(-)
> >
> > diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
> > index 83d8fe18..3fc0e14a 100644
> > --- a/mongoose/mongoose.c
> > +++ b/mongoose/mongoose.c
> > @@ -1,5 +1,5 @@
> > // Copyright (c) 2004-2013 Sergey Lyubka
> > -// Copyright (c) 2013-2022 Cesanta Software Limited
> > +// Copyright (c) 2013-2024 Cesanta Software Limited
> > // All rights reserved
> > //
> > // This software is dual-licensed: you can redistribute it and/or modify
> > @@ -15,7 +15,7 @@
> > // Alternatively, you can license this software under a commercial
> > // license, as set out in https://www.mongoose.ws/licensing/
> > //
> > -// SPDX-License-Identifier: GPL-2.0-only
> > +// SPDX-License-Identifier: GPL-2.0-only or commercial
> >
> > #include "mongoose.h"
> >
> > @@ -24,8 +24,7 @@
> > #endif
> >
> >
> > -
> > -static int mg_b64idx(int c) {
> > +static int mg_base64_encode_single(int c) {
> > if (c < 26) {
> > return c + 'A';
> > } else if (c < 52) {
> > @@ -37,7 +36,7 @@ static int mg_b64idx(int c) {
> > }
> > }
> >
> > -static int mg_b64rev(int c) {
> > +static int mg_base64_decode_single(int c) {
> > if (c >= 'A' && c <= 'Z') {
> > return c - 'A';
> > } else if (c >= 'a' && c <= 'z') {
> > @@ -55,24 +54,24 @@ static int mg_b64rev(int c) {
> > }
> > }
> >
> > -int mg_base64_update(unsigned char ch, char *to, int n) {
> > - int rem = (n & 3) % 3;
> > +size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
> > + unsigned long rem = (n & 3) % 3;
> > if (rem == 0) {
> > - to[n] = (char) mg_b64idx(ch >> 2);
> > + to[n] = (char) mg_base64_encode_single(ch >> 2);
> > to[++n] = (char) ((ch & 3) << 4);
> > } else if (rem == 1) {
> > - to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
> > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
> > to[++n] = (char) ((ch & 15) << 2);
> > } else {
> > - to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
> > - to[++n] = (char) mg_b64idx(ch & 63);
> > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
> > + to[++n] = (char) mg_base64_encode_single(ch & 63);
> > n++;
> > }
> > return n;
> > }
> >
> > -int mg_base64_final(char *to, int n) {
> > - int saved = n;
> > +size_t mg_base64_final(char *to, size_t n) {
> > + size_t saved = n;
> > // printf("---[%.*s]\n", n, to);
> > if (n & 3) n = mg_base64_update(0, to, n);
> > if ((saved & 3) == 2) n--;
> > @@ -82,20 +81,27 @@ int mg_base64_final(char *to, int n) {
> > return n;
> > }
> >
> > -int mg_base64_encode(const unsigned char *p, int n, char *to) {
> > - int i, len = 0;
> > +size_t mg_base64_encode(const unsigned char *p, size_t n, char *to, 
> size_t dl) {
> > + size_t i, len = 0;
> > + if (dl > 0) to[0] = '\0';
> > + if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
> > for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
> > len = mg_base64_final(to, len);
> > return len;
> > }
> >
> > -int mg_base64_decode(const char *src, int n, char *dst) {
> > +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t 
> dl) {
> > const char *end = src == NULL ? NULL : src + n; // Cannot add to NULL
> > - int len = 0;
> > + size_t len = 0;
> > + if (dl < n / 4 * 3 + 1) goto fail;
> > while (src != NULL && src + 3 < end) {
> > - int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = 
> mg_b64rev(src[2]),
> > - d = mg_b64rev(src[3]);
> > - if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0;
> > + int a = mg_base64_decode_single(src[0]),
> > + b = mg_base64_decode_single(src[1]),
> > + c = mg_base64_decode_single(src[2]),
> > + d = mg_base64_decode_single(src[3]);
> > + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
> > + goto fail;
> > + }
> > dst[len++] = (char) ((a << 2) | (b >> 4));
> > if (src[2] != '=') {
> > dst[len++] = (char) ((b << 4) | (c >> 2));
> > @@ -105,1942 +111,2915 @@ int mg_base64_decode(const char *src, int n, 
> char *dst) {
> > }
> > dst[len] = '\0';
> > return len;
> > +fail:
> > + if (dl > 0) dst[0] = '\0';
> > + return 0;
> > }
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/dns.c"
> > +#line 1 "src/device_ch32v307.c"
> > #endif
> >
> >
> >
> > +#if MG_DEVICE == MG_DEVICE_CH32V307
> > +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
> >
> > +#define FLASH_BASE 0x40022000
> > +#define FLASH_ACTLR (FLASH_BASE + 0)
> > +#define FLASH_KEYR (FLASH_BASE + 4)
> > +#define FLASH_OBKEYR (FLASH_BASE + 8)
> > +#define FLASH_STATR (FLASH_BASE + 12)
> > +#define FLASH_CTLR (FLASH_BASE + 16)
> > +#define FLASH_ADDR (FLASH_BASE + 20)
> > +#define FLASH_OBR (FLASH_BASE + 28)
> > +#define FLASH_WPR (FLASH_BASE + 32)
> >
> > -
> > -
> > -struct dns_data {
> > - struct dns_data *next;
> > - struct mg_connection *c;
> > - uint64_t expire;
> > - uint16_t txnid;
> > -};
> > -
> > -static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
> > - struct mg_dns *, bool);
> > -
> > -static void mg_dns_free(struct mg_connection *c, struct dns_data *d) {
> > - LIST_DELETE(struct dns_data,
> > - (struct dns_data **) &c->mgr->active_dns_requests, d);
> > - free(d);
> > +void *mg_flash_start(void) {
> > + return (void *) 0x08000000;
> > }
> > -
> > -void mg_resolve_cancel(struct mg_connection *c) {
> > - struct dns_data *tmp, *d = (struct dns_data *) 
> c->mgr->active_dns_requests;
> > - for (; d != NULL; d = tmp) {
> > - tmp = d->next;
> > - if (d->c == c) mg_dns_free(c, d);
> > +size_t mg_flash_size(void) {
> > + return 480 * 1024; // First 320k is 0-wait
> > +}
> > +size_t mg_flash_sector_size(void) {
> > + return 4096;
> > +}
> > +size_t mg_flash_write_align(void) {
> > + return 4;
> > +}
> > +int mg_flash_bank(void) {
> > + return 0;
> > +}
> > +void mg_device_reset(void) {
> > + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset()
> > +}
> > +static void flash_unlock(void) {
> > + static bool unlocked;
> > + if (unlocked == false) {
> > + MG_REG(FLASH_KEYR) = 0x45670123;
> > + MG_REG(FLASH_KEYR) = 0xcdef89ab;
> > + unlocked = true;
> > }
> > }
> > +static void flash_wait(void) {
> > + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
> > +}
> >
> > -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, 
> size_t ofs,
> > - char *to, size_t tolen, size_t j,
> > - int depth) {
> > - size_t i = 0;
> > - if (tolen > 0 && depth == 0) to[0] = '\0';
> > - if (depth > 5) return 0;
> > - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
> > - while (ofs + i + 1 < len) {
> > - size_t n = s[ofs + i];
> > - if (n == 0) {
> > - i++;
> > - break;
> > - }
> > - if (n & 0xc0) {
> > - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len
> > - // MG_INFO(("PTR %lx", (unsigned long) ptr));
> > - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> > - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
> > - return 0;
> > - i += 2;
> > - break;
> > - }
> > - if (ofs + i + n + 1 >= len) return 0;
> > - if (j > 0) {
> > - if (j < tolen) to[j] = '.';
> > - j++;
> > - }
> > - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> > - j += n;
> > - i += n + 1;
> > - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
> > - // MG_INFO(("--> [%s]", to));
> > - }
> > - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is 
> nul-term
> > - return i;
> > +bool mg_flash_erase(void *addr) {
> > + //MG_INFO(("%p", addr));
> > + flash_unlock();
> > + flash_wait();
> > + MG_REG(FLASH_ADDR) = (uint32_t) addr;
> > + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT;
> > + flash_wait();
> > + return true;
> > }
> >
> > -static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
> > - char *dst, size_t dstlen) {
> > - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
> > +static bool is_page_boundary(const void *addr) {
> > + uint32_t val = (uint32_t) addr;
> > + return (val & (mg_flash_sector_size() - 1)) == 0;
> > }
> >
> > -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> > - bool is_question, struct mg_dns_rr *rr) {
> > - const uint8_t *s = buf + ofs, *e = &buf[len];
> > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> > + //MG_INFO(("%p %p %lu", addr, buf, len));
> > + //mg_hexdump(buf, len);
> > + flash_unlock();
> > + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
> > + uint16_t *dst = (uint16_t *) addr;
> > + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG
> > + //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
> > + while (src < end) {
> > + if (is_page_boundary(dst)) mg_flash_erase(dst);
> > + *dst++ = *src++;
> > + flash_wait();
> > + }
> > + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG
> > + return true;
> > +}
> > +#endif
> >
> > - memset(rr, 0, sizeof(*rr));
> > - if (len < sizeof(struct mg_dns_header)) return 0; // Too small
> > - if (len > 512) return 0; // Too large, we don't expect that
> > - if (s >= e) return 0; // Overflow
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/device_dummy.c"
> > +#endif
> >
> > - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) 
> == 0)
> > - return 0;
> > - s += rr->nlen + 4;
> > - if (s > e) return 0;
> > - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> > - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> > - if (is_question) return (size_t) (rr->nlen + 4);
> >
> > - s += 6;
> > - if (s > e) return 0;
> > - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> > - if (s + rr->alen > e) return 0;
> > - return (size_t) (rr->nlen + rr->alen + 10);
> > +#if MG_DEVICE == MG_DEVICE_NONE
> > +void *mg_flash_start(void) {
> > + return NULL;
> > +}
> > +size_t mg_flash_size(void) {
> > + return 0;
> > +}
> > +size_t mg_flash_sector_size(void) {
> > + return 0;
> > +}
> > +size_t mg_flash_write_align(void) {
> > + return 0;
> > +}
> > +int mg_flash_bank(void) {
> > + return 0;
> > +}
> > +bool mg_flash_erase(void *location) {
> > + (void) location;
> > + return false;
> > +}
> > +bool mg_flash_swap_bank(void) {
> > + return true;
> > +}
> > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> > + (void) addr, (void) buf, (void) len;
> > + return false;
> > +}
> > +void mg_device_reset(void) {
> > }
> > +#endif
> >
> > -bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message 
> *dm) {
> > - const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> > - struct mg_dns_rr rr;
> > - size_t i, n, ofs = sizeof(*h);
> > - memset(dm, 0, sizeof(*dm));
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/device_flash.c"
> > +#endif
> >
> > - if (len < sizeof(*h)) return 0; // Too small, headers dont fit
> > - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
> > - if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity
> > - dm->txnid = mg_ntohs(h->txnid);
> >
> > - for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> > - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
> > - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> > - ofs += n;
> > +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || 
> \
> > + MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> > +// Flash can be written only if it is erased. Erased flash is 0xff (all 
> bits 1)
> > +// Writes must be mg_flash_write_align() - aligned. Thus if we want to 
> save an
> > +// object, we pad it at the end for alignment.
> > +//
> > +// Objects in the flash sector are stored sequentially:
> > +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | 
> ......
> > +//
> > +// In order to get to the next object, read its size, then align up.
> > +
> > +// Traverse the list of saved objects
> > +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) {
> > + size_t aligned_size = 0, align = mg_flash_write_align(), left = end - 
> p;
> > + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
> > + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
> > + if (size) *size = (size_t) p32[0];
> > + if (key) *key = p32[1];
> > + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
> > + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail
> > }
> > - for (i = 0; i < mg_ntohs(h->num_answers); i++) {
> > - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return 
> false;
> > - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
> > - // dm->name));
> > - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> > - ofs += n;
> > -
> > - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> > - dm->addr.is_ip6 = false;
> > - memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> > - dm->resolved = true;
> > - break; // Return success
> > - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> > - dm->addr.is_ip6 = true;
> > - memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
> > - dm->resolved = true;
> > - break; // Return success
> > + return aligned_size;
> > +}
> > +
> > +// Return the last sector of Bank 2
> > +static char *flash_last_sector(void) {
> > + size_t ss = mg_flash_sector_size(), size = mg_flash_size();
> > + char *base = (char *) mg_flash_start(), *last = base + size - ss;
> > + if (mg_flash_bank() == 2) last -= size / 2;
> > + return last;
> > +}
> > +
> > +// Find a saved object with a given key
> > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
> > + char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = 
> NULL;
> > + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
> > + bool ok = false;
> > + if (s == NULL) s = flash_last_sector();
> > + if (s < base || s >= base + mg_flash_size()) {
> > + MG_ERROR(("%p is outsize of flash", sector));
> > + } else if (((s - base) % ss) != 0) {
> > + MG_ERROR(("%p is not a sector boundary", sector));
> > + } else {
> > + uint32_t k, scanned = 0;
> > + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
> > + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key));
> > + // mg_hexdump(s + ofs, n);
> > + if (k == key && sz == len) {
> > + res = s + ofs + sizeof(uint32_t) * 2;
> > + memcpy(buf, res, len); // Copy object
> > + ok = true; // Keep scanning for the newer versions of it
> > + }
> > + ofs += n, scanned++;
> > }
> > + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res));
> > }
> > - return true;
> > + return ok;
> > }
> >
> > -static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
> > - void *fn_data) {
> > - struct dns_data *d, *tmp;
> > - if (ev == MG_EV_POLL) {
> > - uint64_t now = *(uint64_t *) ev_data;
> > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> > - d = tmp) {
> > - tmp = d->next;
> > - // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> > - if (now > d->expire) mg_error(d->c, "DNS timeout");
> > +// For all saved objects in the sector, delete old versions of objects
> > +static void mg_flash_sector_cleanup(char *sector) {
> > + // Buffer all saved objects into an IO buffer (backed by RAM)
> > + // erase sector, and re-save them.
> > + struct mg_iobuf io = {0, 0, 0, 2048};
> > + size_t ss = mg_flash_sector_size();
> > + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
> > + uint32_t key;
> > + // Traverse all objects
> > + MG_DEBUG(("Cleaning up sector %p", sector));
> > + while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 
> 0) {
> > + // Delete an old copy of this object in the cache
> > + for (size_t o = 0; o < io.len; o += size2 + hs) {
> > + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
> > + size2 = *(uint32_t *) (io.buf + o);
> > + if (k == key) {
> > + mg_iobuf_del(&io, o, size2 + hs);
> > + break;
> > + }
> > }
> > - } else if (ev == MG_EV_READ) {
> > - struct mg_dns_message dm;
> > - int resolved = 0;
> > - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> > - MG_ERROR(("Unexpected DNS response:"));
> > - mg_hexdump(c->recv.buf, c->recv.len);
> > - } else {
> > - // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
> > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> > - d = tmp) {
> > - tmp = d->next;
> > - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> > - if (dm.txnid != d->txnid) continue;
> > - if (d->c->is_resolving) {
> > - if (dm.resolved) {
> > - dm.addr.port = d->c->rem.port; // Save port
> > - d->c->rem = dm.addr; // Copy resolved address
> > - MG_DEBUG(
> > - ("%lu %s is %I", d->c->id, dm.name, d->c->rem.is_ip6 ? 16 : 4,
> > - d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
> > - mg_connect_resolved(d->c);
> > -#if MG_ENABLE_IPV6
> > - } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
> > - c->mgr->use_dns6 == false) {
> > - struct mg_str x = mg_str(dm.name);
> > - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> > -#endif
> > - } else {
> > - mg_error(d->c, "%s DNS lookup failed", dm.name);
> > + // And add the new copy
> > + mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
> > + ofs += n;
> > + }
> > + // All objects are cached in RAM now
> > + if (mg_flash_erase(sector)) { // Erase sector. If successful,
> > + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached 
> objects
> > + size = *(uint32_t *) (io.buf + ofs);
> > + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
> > + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash
> > + }
> > + }
> > + mg_iobuf_free(&io);
> > +}
> > +
> > +// Save an object with a given key - append to the end of an object list
> > +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t 
> len) {
> > + char *base = (char *) mg_flash_start(), *s = (char *) sector;
> > + size_t ss = mg_flash_sector_size(), ofs = 0, n;
> > + bool ok = false;
> > + if (s == NULL) s = flash_last_sector();
> > + if (s < base || s >= base + mg_flash_size()) {
> > + MG_ERROR(("%p is outsize of flash", sector));
> > + } else if (((s - base) % ss) != 0) {
> > + MG_ERROR(("%p is not a sector boundary", sector));
> > + } else {
> > + char ab[mg_flash_write_align()]; // Aligned write block
> > + uint32_t hdr[2] = {(uint32_t) len, key};
> > + size_t needed = sizeof(hdr) + len;
> > + size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
> > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
> > +
> > + // If there is not enough space left, cleanup sector and re-eval ofs
> > + if (ofs + needed_aligned >= ss) {
> > + mg_flash_sector_cleanup(s);
> > + ofs = 0;
> > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
> > + }
> > +
> > + if (ofs + needed_aligned <= ss) {
> > + // Enough space to save this object
> > + if (sizeof(ab) < sizeof(hdr)) {
> > + // Flash write granularity is 32 bit or less, write with no buffering
> > + ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
> > + if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
> > + } else {
> > + // Flash granularity is sizeof(hdr) or more. We need to save in
> > + // 3 chunks: initial block, bulk, rest. This is because we have
> > + // two memory chunks to write: hdr and buf, on aligned boundaries.
> > + n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
> > + if (n > len) n = len; // is
> > + memset(ab, 0xff, sizeof(ab)); // initialized to all-one
> > + memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
> > + memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
> > + MG_INFO(("saving initial block of %lu", sizeof(ab)));
> > + ok = mg_flash_write(s + ofs, ab, sizeof(ab));
> > + if (ok && len > n) {
> > + size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
> > + if (n2 > 0) {
> > + MG_INFO(("saving bulk, %lu", n2));
> > + ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
> > + }
> > + if (ok && len > n) {
> > + size_t n3 = len - n - n2;
> > + if (n3 > sizeof(ab)) n3 = sizeof(ab);
> > + memset(ab, 0xff, sizeof(ab));
> > + memcpy(ab, (char *) buf + n + n2, n3);
> > + MG_INFO(("saving rest, %lu", n3));
> > + ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
> > }
> > - } else {
> > - MG_ERROR(("%lu already resolved", d->c->id));
> > }
> > - mg_dns_free(c, d);
> > - resolved = 1;
> > }
> > - }
> > - if (!resolved) MG_ERROR(("stray DNS reply"));
> > - c->recv.len = 0;
> > - } else if (ev == MG_EV_CLOSE) {
> > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
> > - d = tmp) {
> > - tmp = d->next;
> > - mg_error(d->c, "DNS error");
> > - mg_dns_free(c, d);
> > + MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
> > + s + ofs, key, ok));
> > + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
> > + } else {
> > + MG_ERROR(("Sector is full"));
> > }
> > }
> > - (void) fn_data;
> > + return ok;
> > +}
> > +#else
> > +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t 
> len) {
> > + (void) sector, (void) key, (void) buf, (void) len;
> > + return false;
> > +}
> > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
> > + (void) sector, (void) key, (void) buf, (void) len;
> > + return false;
> > }
> > +#endif
> >
> > -static bool mg_dns_send(struct mg_connection *c, const struct mg_str 
> *name,
> > - uint16_t txnid, bool ipv6) {
> > - struct {
> > - struct mg_dns_header header;
> > - uint8_t data[256];
> > - } pkt;
> > - size_t i, n;
> > - memset(&pkt, 0, sizeof(pkt));
> > - pkt.header.txnid = mg_htons(txnid);
> > - pkt.header.flags = mg_htons(0x100);
> > - pkt.header.num_questions = mg_htons(1);
> > - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> > - if (name->ptr[i] == '.' || i >= name->len) {
> > - pkt.data[n] = (uint8_t) (i - n);
> > - memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
> > - n = i + 1;
> > - }
> > - if (i >= name->len) break;
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/device_imxrt.c"
> > +#endif
> > +
> > +
> > +
> > +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> > +
> > +struct mg_flexspi_lut_seq {
> > + uint8_t seqNum;
> > + uint8_t seqId;
> > + uint16_t reserved;
> > +};
> > +
> > +struct mg_flexspi_mem_config {
> > + uint32_t tag;
> > + uint32_t version;
> > + uint32_t reserved0;
> > + uint8_t readSampleClkSrc;
> > + uint8_t csHoldTime;
> > + uint8_t csSetupTime;
> > + uint8_t columnAddressWidth;
> > + uint8_t deviceModeCfgEnable;
> > + uint8_t deviceModeType;
> > + uint16_t waitTimeCfgCommands;
> > + struct mg_flexspi_lut_seq deviceModeSeq;
> > + uint32_t deviceModeArg;
> > + uint8_t configCmdEnable;
> > + uint8_t configModeType[3];
> > + struct mg_flexspi_lut_seq configCmdSeqs[3];
> > + uint32_t reserved1;
> > + uint32_t configCmdArgs[3];
> > + uint32_t reserved2;
> > + uint32_t controllerMiscOption;
> > + uint8_t deviceType;
> > + uint8_t sflashPadType;
> > + uint8_t serialClkFreq;
> > + uint8_t lutCustomSeqEnable;
> > + uint32_t reserved3[2];
> > + uint32_t sflashA1Size;
> > + uint32_t sflashA2Size;
> > + uint32_t sflashB1Size;
> > + uint32_t sflashB2Size;
> > + uint32_t csPadSettingOverride;
> > + uint32_t sclkPadSettingOverride;
> > + uint32_t dataPadSettingOverride;
> > + uint32_t dqsPadSettingOverride;
> > + uint32_t timeoutInMs;
> > + uint32_t commandInterval;
> > + uint16_t dataValidTime[2];
> > + uint16_t busyOffset;
> > + uint16_t busyBitPolarity;
> > + uint32_t lookupTable[64];
> > + struct mg_flexspi_lut_seq lutCustomSeq[12];
> > + uint32_t reserved4[4];
> > +};
> > +
> > +struct mg_flexspi_nor_config {
> > + struct mg_flexspi_mem_config memConfig;
> > + uint32_t pageSize;
> > + uint32_t sectorSize;
> > + uint8_t ipcmdSerialClkFreq;
> > + uint8_t isUniformBlockSize;
> > + uint8_t reserved0[2];
> > + uint8_t serialNorType;
> > + uint8_t needExitNoCmdMode;
> > + uint8_t halfClkForNonReadCmd;
> > + uint8_t needRestoreNoCmdMode;
> > + uint32_t blockSize;
> > + uint32_t reserve2[11];
> > +};
> > +
> > +/* FLEXSPI memory config block related defintions */
> > +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian
> > +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0
> > +
> > +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \
> > + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | 
> MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
> > + MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | 
> MG_FLEXSPI_LUT_OPCODE1(cmd1))
> > +
> > +#define MG_CMD_SDR 0x01
> > +#define MG_CMD_DDR 0x21
> > +#define MG_DUMMY_SDR 0x0C
> > +#define MG_DUMMY_DDR 0x2C
> > +#define MG_RADDR_SDR 0x02
> > +#define MG_RADDR_DDR 0x22
> > +#define MG_READ_SDR 0x09
> > +#define MG_READ_DDR 0x29
> > +#define MG_WRITE_SDR 0x08
> > +#define MG_WRITE_DDR 0x28
> > +#define MG_STOP 0
> > +
> > +#define MG_FLEXSPI_1PAD 0
> > +#define MG_FLEXSPI_2PAD 1
> > +#define MG_FLEXSPI_4PAD 2
> > +#define MG_FLEXSPI_8PAD 3
> > +
> > +#define MG_FLEXSPI_QSPI_LUT \
> > + { \
> > + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, 
> MG_RADDR_SDR, MG_FLEXSPI_4PAD, \
> > + 0x18), \
> > + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, 
> MG_READ_SDR, MG_FLEXSPI_4PAD, \
> > + 0x04), \
> > + [4 * 1 + 0] = \
> > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, 
> MG_FLEXSPI_1PAD, 0x04), \
> > + [4 * 3 + 0] = \
> > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, 
> MG_FLEXSPI_1PAD, 0x0), \
> > + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, 
> MG_RADDR_SDR, \
> > + MG_FLEXSPI_1PAD, 0x18), \
> > + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, 
> MG_RADDR_SDR, \
> > + MG_FLEXSPI_1PAD, 0x18), \
> > + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, 
> MG_RADDR_SDR, \
> > + MG_FLEXSPI_1PAD, 0x18), \
> > + [4 * 9 + 1] = \
> > + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, 
> MG_FLEXSPI_1PAD, 0x0), \
> > + [4 * 11 + 0] = \
> > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, 
> MG_FLEXSPI_1PAD, 0x0), \
> > }
> > - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
> > - n += 5;
> > - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
> > - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query
> > - // n += 6;
> > - return mg_send(c, &pkt, sizeof(pkt.header) + n);
> > +
> > +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 
> 0xFFU)
> > +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 
> 8U)) & 0x300U)
> > +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 
> 10U)) & 0xFC00U)
> > +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 
> 16U)) & 0xFF0000U)
> > +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 
> 24U)) & 0x3000000U)
> > +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 
> 26U)) & 0xFC000000U)
> > +
> > +#define FLEXSPI_NOR_INSTANCE 0
> > +
> > +#if MG_DEVICE == MG_DEVICE_RT1020
> > +struct mg_flexspi_nor_driver_interface {
> > + uint32_t version;
> > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
> > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config 
> *config, uint32_t dst_addr,
> > + const uint32_t *src);
> > + uint32_t reserved;
> > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, 
> uint32_t start,
> > + uint32_t lengthInBytes);
> > + uint32_t reserved2;
> > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t 
> *lutBase,
> > + uint32_t seqNumber);
> > + int (*xfer)(uint32_t instance, char *xfer);
> > + void (*clear_cache)(uint32_t instance);
> > +};
> > +#elif MG_DEVICE == MG_DEVICE_RT1060
> > +struct mg_flexspi_nor_driver_interface {
> > + uint32_t version;
> > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
> > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config 
> *config, uint32_t dst_addr,
> > + const uint32_t *src);
> > + int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config 
> *config);
> > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, 
> uint32_t start,
> > + uint32_t lengthInBytes);
> > + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, 
> uint32_t *dst, uint32_t addr,
> > + uint32_t lengthInBytes);
> > + void (*clear_cache)(uint32_t instance);
> > + int (*xfer)(uint32_t instance, char *xfer);
> > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t 
> *lutBase,
> > + uint32_t seqNumber);
> > + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config 
> *config, uint32_t *option);
> > +};
> > +#endif
> > +
> > +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
> > + (*(uint32_t*)0x0020001c + 16)))
> > +
> > +static bool s_flash_irq_disabled;
> > +
> > +MG_IRAM void *mg_flash_start(void) {
> > + return (void *) 0x60000000;
> > +}
> > +MG_IRAM size_t mg_flash_size(void) {
> > + return 8 * 1024 * 1024;
> > +}
> > +MG_IRAM size_t mg_flash_sector_size(void) {
> > + return 4 * 1024; // 4k
> > +}
> > +MG_IRAM size_t mg_flash_write_align(void) {
> > + return 256;
> > +}
> > +MG_IRAM int mg_flash_bank(void) {
> > + return 0;
> > }
> >
> > -static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, 
> int ms,
> > - struct mg_dns *dnsc, bool ipv6) {
> > - struct dns_data *d = NULL;
> > - if (dnsc->url == NULL) {
> > - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> > - } else if (dnsc->c == NULL) {
> > - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> > - if (dnsc->c != NULL) {
> > - dnsc->c->pfn = dns_cb;
> > - // dnsc->c->is_hexdumping = 1;
> > - }
> > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> > + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> > + volatile char *p = (char *) dst;
> > + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) 
> == 0;
> > +}
> > +
> > +// Note: the get_config function below works both for RT1020 and 1060
> > +#if MG_DEVICE == MG_DEVICE_RT1020
> > +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config 
> *config) {
> > + struct mg_flexspi_nor_config default_config = {
> > + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
> > + .version = MG_FLEXSPI_CFG_BLK_VERSION,
> > + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad
> > + .csHoldTime = 3,
> > + .csSetupTime = 3,
> > + .controllerMiscOption = MG_BIT(4),
> > + .deviceType = 1, // serial NOR
> > + .sflashPadType = 4,
> > + .serialClkFreq = 7, // 133MHz
> > + .sflashA1Size = 8 * 1024 * 1024,
> > + .lookupTable = MG_FLEXSPI_QSPI_LUT},
> > + .pageSize = 256,
> > + .sectorSize = 4 * 1024,
> > + .ipcmdSerialClkFreq = 1,
> > + .blockSize = 64 * 1024,
> > + .isUniformBlockSize = false};
> > +
> > + *config = default_config;
> > + return 0;
> > +}
> > +#else
> > +MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config 
> *config) {
> > + uint32_t options[] = {0xc0000000, 0x00};
> > +
> > + MG_ARM_DISABLE_IRQ();
> > + uint32_t status =
> > + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
> > + if (!s_flash_irq_disabled) {
> > + MG_ARM_ENABLE_IRQ();
> > }
> > - if (dnsc->c == NULL) {
> > - mg_error(c, "resolver");
> > - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
> > - mg_error(c, "resolve OOM");
> > - } else {
> > - struct dns_data *reqs = (struct dns_data *) 
> c->mgr->active_dns_requests;
> > - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> > - d->next = (struct dns_data *) c->mgr->active_dns_requests;
> > - c->mgr->active_dns_requests = d;
> > - d->expire = mg_millis() + (uint64_t) ms;
> > - d->c = c;
> > - c->is_resolving = 1;
> > - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) 
> name->len,
> > - name->ptr, &dnsc->url, d->txnid));
> > - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> > - mg_error(dnsc->c, "DNS send");
> > - }
> > + if (status) {
> > + MG_ERROR(("Failed to extract flash configuration: status %u", status));
> > }
> > + return status;
> > }
> > +#endif
> >
> > -void mg_resolve(struct mg_connection *c, const char *url) {
> > - struct mg_str host = mg_url_host(url);
> > - c->rem.port = mg_htons(mg_url_port(url));
> > - if (mg_aton(host, &c->rem)) {
> > - // host is an IP address, do not fire name resolution
> > - mg_connect_resolved(c);
> > - } else {
> > - // host is not an IP, send DNS resolution request
> > - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
> > - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> > +MG_IRAM bool mg_flash_erase(void *addr) {
> > + struct mg_flexspi_nor_config config;
> > + if (flexspi_nor_get_config(&config) != 0) {
> > + return false;
> > + }
> > + if (flash_page_start(addr) == false) {
> > + MG_ERROR(("%p is not on a sector boundary", addr));
> > + return false;
> > + }
> > +
> > + void *dst = (void *)((char *) addr - (char *) mg_flash_start());
> > +
> > + // Note: Interrupts must be disabled before any call to the ROM API on 
> RT1020
> > + // and 1060
> > + MG_ARM_DISABLE_IRQ();
> > + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, 
> (uint32_t) dst,
> > + mg_flash_sector_size()) == 0);
> > + if (!s_flash_irq_disabled) {
> > + MG_ARM_ENABLE_IRQ(); // Reenable them after the call
> > }
> > + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : 
> "fail"));
> > + return ok;
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/event.c"
> > -#endif
> > +MG_IRAM bool mg_flash_swap_bank(void) {
> > + return true;
> > +}
> > +
> > +static inline void spin(volatile uint32_t count) {
> > + while (count--) (void) 0;
> > +}
> > +
> > +static inline void flash_wait(void) {
> > + while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0)
> > + spin(1);
> > +}
> >
> > +MG_IRAM static void *flash_code_location(void) {
> > + return (void *) ((char *) mg_flash_start() + 0x2000);
> > +}
> >
> > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
> > + struct mg_flexspi_nor_config config;
> > + if (flexspi_nor_get_config(&config) != 0) {
> > + return false;
> > + }
> > + if ((len % mg_flash_write_align()) != 0) {
> > + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> > + return false;
> > + }
> >
> > + if ((char *) addr < (char *) mg_flash_start()) {
> > + MG_ERROR(("Invalid flash write address: %p", addr));
> > + return false;
> > + }
> >
> > + uint32_t *dst = (uint32_t *) addr;
> > + uint32_t *src = (uint32_t *) buf;
> > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> > + bool ok = true;
> > +
> > + // Note: If we overwrite the flash irq section of the image, we must 
> also
> > + // make sure interrupts are disabled and are not reenabled until we 
> write
> > + // this sector with another irq table.
> > + if ((char *) addr == (char *) flash_code_location()) {
> > + s_flash_irq_disabled = true;
> > + MG_ARM_DISABLE_IRQ();
> > + }
> >
> > -void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> > - // Run user-defined handler first, in order to give it an ability
> > - // to intercept processing (e.g. clean input buffer) before the
> > - // protocol handler kicks in
> > - if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
> > - if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
> > + while (ok && src < end) {
> > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
> > + break;
> > + }
> > + uint32_t status;
> > + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
> > + if ((char *) buf >= (char *) mg_flash_start()) {
> > + // If we copy from FLASH to FLASH, then we first need to copy the 
> source
> > + // to RAM
> > + size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
> > + uint32_t tmp[tmp_buf_size];
> > +
> > + for (size_t i = 0; i < tmp_buf_size; i++) {
> > + flash_wait();
> > + tmp[i] = src[i];
> > + }
> > + MG_ARM_DISABLE_IRQ();
> > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> > + (uint32_t) dst_ofs, tmp);
> > + } else {
> > + MG_ARM_DISABLE_IRQ();
> > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> > + (uint32_t) dst_ofs, src);
> > + }
> > + if (!s_flash_irq_disabled) {
> > + MG_ARM_ENABLE_IRQ();
> > + }
> > + src = (uint32_t *) ((char *) src + mg_flash_write_align());
> > + dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
> > + if (status != 0) {
> > + ok = false;
> > + }
> > + }
> > + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : 
> "fail"));
> > + return ok;
> > }
> >
> > -void mg_error(struct mg_connection *c, const char *fmt, ...) {
> > - char buf[64];
> > - va_list ap;
> > - va_start(ap, fmt);
> > - mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> > - va_end(ap);
> > - MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
> > - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
> > - mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it
> > +MG_IRAM void mg_device_reset(void) {
> > + MG_DEBUG(("Resetting device..."));
> > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> > }
> >
> > +#endif
> > +
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/fmt.c"
> > +#line 1 "src/device_stm32h5.c"
> > #endif
> >
> >
> >
> > +#if MG_DEVICE == MG_DEVICE_STM32H5
> >
> > -static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
> > - struct mg_iobuf *io = (struct mg_iobuf *) param;
> > - if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
> > - if (io->len + 2 <= io->size) {
> > - io->buf[io->len++] = (uint8_t) ch;
> > - io->buf[io->len] = 0;
> > - } else if (io->len < io->size) {
> > - io->buf[io->len++] = 0; // Guarantee to 0-terminate
> > - }
> > -}
> > +#define FLASH_BASE 0x40022000 // Base address of the flash controller
> > +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
> > +#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
> > +#define FLASH_OPTCR (FLASH_BASE + 0x1c)
> > +#define FLASH_NSSR (FLASH_BASE + 0x20)
> > +#define FLASH_NSCR (FLASH_BASE + 0x28)
> > +#define FLASH_NSCCR (FLASH_BASE + 0x30)
> > +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
> > +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
> >
> > -static void mg_putchar_iobuf_static(char ch, void *param) {
> > - mg_pfn_iobuf_private(ch, param, false);
> > +void *mg_flash_start(void) {
> > + return (void *) 0x08000000;
> > }
> > -
> > -void mg_pfn_iobuf(char ch, void *param) {
> > - mg_pfn_iobuf_private(ch, param, true);
> > +size_t mg_flash_size(void) {
> > + return 2 * 1024 * 1024; // 2Mb
> > }
> > -
> > -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list 
> *ap) {
> > - struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> > - size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> > - if (n < len) buf[n] = '\0';
> > - return n;
> > +size_t mg_flash_sector_size(void) {
> > + return 8 * 1024; // 8k
> > }
> > -
> > -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> > - va_list ap;
> > - size_t n;
> > - va_start(ap, fmt);
> > - n = mg_vsnprintf(buf, len, fmt, &ap);
> > - va_end(ap);
> > - return n;
> > +size_t mg_flash_write_align(void) {
> > + return 16; // 128 bit
> > }
> > -
> > -char *mg_vmprintf(const char *fmt, va_list *ap) {
> > - struct mg_iobuf io = {0, 0, 0, 256};
> > - mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> > - return (char *) io.buf;
> > +int mg_flash_bank(void) {
> > + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
> > }
> >
> > -char *mg_mprintf(const char *fmt, ...) {
> > - char *s;
> > - va_list ap;
> > - va_start(ap, fmt);
> > - s = mg_vmprintf(fmt, &ap);
> > - va_end(ap);
> > - return s;
> > +static void flash_unlock(void) {
> > + static bool unlocked = false;
> > + if (unlocked == false) {
> > + MG_REG(FLASH_KEYR) = 0x45670123;
> > + MG_REG(FLASH_KEYR) = 0Xcdef89ab;
> > + MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
> > + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
> > + unlocked = true;
> > + }
> > }
> >
> > -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char 
> *fmt, ...) {
> > - size_t len = 0;
> > - va_list ap;
> > - va_start(ap, fmt);
> > - len = mg_vxprintf(out, ptr, fmt, &ap);
> > - va_end(ap);
> > - return len;
> > +static int flash_page_start(volatile uint32_t *dst) {
> > + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> > + volatile char *p = (char *) dst;
> > + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) 
> == 0;
> > }
> >
> > -static bool is_digit(int c) {
> > - return c >= '0' && c <= '9';
> > +static bool flash_is_err(void) {
> > + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9
> > }
> >
> > -static int addexp(char *buf, int e, int sign) {
> > - int n = 0;
> > - buf[n++] = 'e';
> > - buf[n++] = (char) sign;
> > - if (e > 400) return 0;
> > - if (e < 10) buf[n++] = '0';
> > - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
> > - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> > - buf[n++] = (char) (e + '0');
> > - return n;
> > +static void flash_wait(void) {
> > + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
> > + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
> > + (void) 0;
> > + }
> > }
> >
> > -static int xisinf(double x) {
> > - union {
> > - double f;
> > - uint64_t u;
> > - } ieee754 = {x};
> > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
> > - ((unsigned) ieee754.u == 0);
> > +static void flash_clear_err(void) {
> > + flash_wait(); // Wait until ready
> > + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors
> > }
> >
> > -static int xisnan(double x) {
> > - union {
> > - double f;
> > - uint64_t u;
> > - } ieee754 = {x};
> > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> > - ((unsigned) ieee754.u != 0) >
> > - 0x7ff00000;
> > +static bool flash_bank_is_swapped(void) {
> > + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8
> > }
> >
> > -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width) {
> > - char buf[40];
> > - int i, s = 0, n = 0, e = 0;
> > - double t, mul, saved;
> > - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> > - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : 
> "-inf");
> > - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> > - if (d < 0.0) d = -d, buf[s++] = '-';
> > -
> > - // Round
> > - saved = d;
> > - mul = 1.0;
> > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> > - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> > - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> > - d += t;
> > - // Calculate exponent, and 'mul' for scientific representation
> > - mul = 1.0;
> > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> > - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> > - // printf(" --> %g %d %g %g\n", saved, e, t, mul);
> > -
> > - if (e >= width) {
> > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> > - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> > - n += addexp(buf + s + n, e, '+');
> > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> > - } else if (e <= -width) {
> > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> > - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> > - n += addexp(buf + s + n, -e, '-');
> > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> > +bool mg_flash_erase(void *location) {
> > + bool ok = false;
> > + if (flash_page_start(location) == false) {
> > + MG_ERROR(("%p is not on a sector boundary"));
> > } else {
> > - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> > - int ch = (int) (d / t);
> > - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> > - d -= ch * t;
> > - t /= 10.0;
> > - }
> > - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, 
> buf);
> > - if (n == 0) buf[s++] = '0';
> > - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 
> 10.0;
> > - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> > - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> > - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
> > - int ch = (int) (d / t);
> > - buf[s + n++] = (char) (ch + '0');
> > - d -= ch * t;
> > - t /= 10.0;
> > + uintptr_t diff = (char *) location - (char *) mg_flash_start();
> > + uint32_t sector = diff / mg_flash_sector_size();
> > + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
> > + flash_unlock();
> > + flash_clear_err();
> > + MG_REG(FLASH_NSCR) = 0;
> > + if ((sector < 128 && flash_bank_is_swapped()) ||
> > + (sector > 127 && !flash_bank_is_swapped())) {
> > + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL
> > }
> > + if (sector > 127) sector -= 128;
> > + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num
> > + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing
> > + flash_wait();
> > + ok = !flash_is_err();
> > + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, 
> location,
> > + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
> > + // mg_hexdump(location, 32);
> > + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
> > }
> > - while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
> > - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
> > - n += s;
> > - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> > - buf[n] = '\0';
> > - return mg_snprintf(dst, dstlen, "%s", buf);
> > + return ok;
> > }
> >
> > -static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool 
> is_hex) {
> > - const char *letters = "0123456789abcdef";
> > - uint64_t v = (uint64_t) val;
> > - size_t s = 0, n, i;
> > - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> > - // This loop prints a number in reverse order. I guess this is because 
> we
> > - // write numbers from right to left: least significant digit comes 
> last.
> > - // Maybe because we use Arabic numbers, and Arabs write RTL?
> > - if (is_hex) {
> > - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> > - } else {
> > - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
> > +bool mg_flash_swap_bank(void) {
> > + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
> > + flash_unlock();
> > + flash_clear_err();
> > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> > + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
> > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> > + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
> > + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
> > + return true;
> > +}
> > +
> > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> > + if ((len % mg_flash_write_align()) != 0) {
> > + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> > + return false;
> > }
> > - // Reverse a string
> > - for (i = 0; i < n / 2; i++) {
> > - char t = buf[s + i];
> > - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> > + uint32_t *dst = (uint32_t *) addr;
> > + uint32_t *src = (uint32_t *) buf;
> > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> > + bool ok = true;
> > + flash_unlock();
> > + flash_clear_err();
> > + MG_ARM_DISABLE_IRQ();
> > + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
> > + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
> > + while (ok && src < end) {
> > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> > + *(volatile uint32_t *) dst++ = *src++;
> > + flash_wait();
> > + if (flash_is_err()) ok = false;
> > }
> > - if (val == 0) buf[n++] = '0'; // Handle special case
> > - return n + s;
> > + MG_ARM_ENABLE_IRQ();
> > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
> > + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
> > + MG_REG(FLASH_NSSR)));
> > + MG_REG(FLASH_NSCR) = 0; // Clear flags
> > + return ok;
> > }
> >
> > -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> > - size_t len) {
> > - size_t i = 0;
> > - while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> > - return i;
> > +void mg_device_reset(void) {
> > + // SCB->AIRCR = ((0x5fa << 
> SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> > }
> > +#endif
> >
> > -static char mg_esc(int c, bool esc) {
> > - const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> > - for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> > - if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/device_stm32h7.c"
> > +#endif
> > +
> > +
> > +
> > +#if MG_DEVICE == MG_DEVICE_STM32H7
> > +
> > +#define FLASH_BASE1 0x52002000 // Base address for bank1
> > +#define FLASH_BASE2 0x52002100 // Base address for bank2
> > +#define FLASH_KEYR 0x04 // See RM0433 4.9.2
> > +#define FLASH_OPTKEYR 0x08
> > +#define FLASH_OPTCR 0x18
> > +#define FLASH_SR 0x10
> > +#define FLASH_CR 0x0c
> > +#define FLASH_CCR 0x14
> > +#define FLASH_OPTSR_CUR 0x1c
> > +#define FLASH_OPTSR_PRG 0x20
> > +#define FLASH_SIZE_REG 0x1ff1e880
> > +
> > +MG_IRAM void *mg_flash_start(void) {
> > + return (void *) 0x08000000;
> > +}
> > +MG_IRAM size_t mg_flash_size(void) {
> > + return MG_REG(FLASH_SIZE_REG) * 1024;
> > +}
> > +MG_IRAM size_t mg_flash_sector_size(void) {
> > + return 128 * 1024; // 128k
> > +}
> > +MG_IRAM size_t mg_flash_write_align(void) {
> > + return 32; // 256 bit
> > +}
> > +MG_IRAM int mg_flash_bank(void) {
> > + if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank 
> support
> > + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
> > +}
> > +
> > +MG_IRAM static void flash_unlock(void) {
> > + static bool unlocked = false;
> > + if (unlocked == false) {
> > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
> > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
> > + if (mg_flash_bank() > 0) {
> > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
> > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
> > + }
> > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is 
> "shared"
> > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once
> > + unlocked = true;
> > }
> > - return 0;
> > }
> >
> > -static char mg_escape(int c) {
> > - return mg_esc(c, true);
> > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> > + char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
> > + volatile char *p = (char *) dst;
> > + return p >= base && p < end && ((p - base) % mg_flash_sector_size()) 
> == 0;
> > }
> >
> > -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> > - size_t len) {
> > - size_t i = 0, extra = 0;
> > - for (i = 0; i < len && buf[i] != '\0'; i++) {
> > - char c = mg_escape(buf[i]);
> > - if (c) {
> > - out('\\', ptr), out(c, ptr), extra++;
> > - } else {
> > - out(buf[i], ptr);
> > - }
> > - }
> > - return i + extra;
> > +MG_IRAM static bool flash_is_err(uint32_t bank) {
> > + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 
> 4.9.5
> > }
> >
> > -static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
> > - size_t len) {
> > - size_t n = 2;
> > - out('"', ptr);
> > - n += qcpy(out, ptr, buf, len);
> > - out('"', ptr);
> > - return n;
> > +MG_IRAM static void flash_wait(uint32_t bank) {
> > + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0;
> > }
> >
> > -static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t *buf,
> > - size_t len) {
> > - size_t i, n = 0;
> > - const char *t =
> > - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> > - out('"', ptr), n++;
> > - for (i = 0; i < len; i += 3) {
> > - uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> > - c3 = i + 2 < len ? buf[i + 2] : 0;
> > - char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
> > - if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> > - if (i + 2 < len) tmp[3] = t[c3 & 63];
> > - n += scpy(out, ptr, tmp, sizeof(tmp));
> > - }
> > - out('"', ptr), n++;
> > - return n;
> > +MG_IRAM static void flash_clear_err(uint32_t bank) {
> > + flash_wait(bank); // Wait until ready
> > + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all 
> errors
> > }
> >
> > -size_t mg_vxprintf(void (*out)(char, void *), void *param, const char 
> *fmt,
> > - va_list *ap) {
> > - size_t i = 0, n = 0;
> > - while (fmt[i] != '\0') {
> > - if (fmt[i] == '%') {
> > - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec 
> */;
> > - char pad = ' ', minus = 0, c = fmt[++i];
> > - if (c == '#') x++, c = fmt[++i];
> > - if (c == '-') minus++, c = fmt[++i];
> > - if (c == '0') pad = '0', c = fmt[++i];
> > - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
> > - if (c == '.') {
> > - c = fmt[++i];
> > - if (c == '*') {
> > - pr = (size_t) va_arg(*ap, int);
> > - c = fmt[++i];
> > - } else {
> > - pr = 0;
> > - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
> > - }
> > - }
> > - while (c == 'h') c = fmt[++i]; // Treat h and hh as int
> > - if (c == 'l') {
> > - is_long++, c = fmt[++i];
> > - if (c == 'l') is_long++, c = fmt[++i];
> > - }
> > - if (c == 'p') x = 1, is_long = 1;
> > - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> > - c == 'g' || c == 'f') {
> > - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> > - char tmp[40];
> > - size_t xl = x ? 2 : 0;
> > - if (c == 'g' || c == 'f') {
> > - double v = va_arg(*ap, double);
> > - if (pr == ~0U) pr = 6;
> > - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
> > - } else if (is_long == 2) {
> > - int64_t v = va_arg(*ap, int64_t);
> > - k = mg_lld(tmp, v, s, h);
> > - } else if (is_long == 1) {
> > - long v = va_arg(*ap, long);
> > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
> > - } else {
> > - int v = va_arg(*ap, int);
> > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> > - }
> > - for (j = 0; j < xl && w > 0; j++) w--;
> > - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> > - n += scpy(out, param, &pad, 1);
> > - n += scpy(out, param, (char *) "0x", xl);
> > - for (j = 0; pad == '0' && k < w && j + k < w; j++)
> > - n += scpy(out, param, &pad, 1);
> > - n += scpy(out, param, tmp, k);
> > - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> > - n += scpy(out, param, &pad, 1);
> > - } else if (c == 'M') {
> > - mg_pm_t f = va_arg(*ap, mg_pm_t);
> > - n += f(out, param, ap);
> > - } else if (c == 'c') {
> > - int ch = va_arg(*ap, int);
> > - out((char) ch, param);
> > - n++;
> > - } else if (c == 'H') {
> > - // Print hex-encoded double-quoted string
> > - size_t bl = (size_t) va_arg(*ap, int);
> > - uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
> > - const char *hex = "0123456789abcdef";
> > - n += scpy(out, param, (char *) &dquote, 1);
> > - for (j = 0; j < bl; j++) {
> > - n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
> > - n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
> > - }
> > - n += scpy(out, param, (char *) &dquote, 1);
> > - } else if (c == 'I') {
> > - // Print IPv4 or IPv6 address
> > - size_t len = (size_t) va_arg(*ap, int); // Length 16 means IPv6 address
> > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the IP address
> > - if (len == 6) {
> > - uint16_t *p = (uint16_t *) buf;
> > - n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]),
> > - mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
> > - mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
> > - mg_htons(p[7]));
> > - } else {
> > - n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int) buf[1],
> > - (int) buf[2], (int) buf[3]);
> > - }
> > - } else if (c == 'A') {
> > - // Print hardware addresses (currently Ethernet MAC)
> > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the hw address
> > - n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
> > - (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
> > - (int) buf[4], (int) buf[5]);
> > - } else if (c == 'V') {
> > - // Print base64-encoded double-quoted string
> > - size_t len = (size_t) va_arg(*ap, int);
> > - uint8_t *buf = va_arg(*ap, uint8_t *);
> > - n += bcpy(out, param, buf, len);
> > - } else if (c == 's' || c == 'Q' || c == 'q') {
> > - char *p = va_arg(*ap, char *);
> > - size_t (*f)(void (*)(char, void *), void *, char *, size_t) = scpy;
> > - if (c == 'Q') f = Qcpy;
> > - if (c == 'q') f = qcpy;
> > - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> > - for (j = 0; !minus && pr < w && j + pr < w; j++)
> > - n += f(out, param, &pad, 1);
> > - n += f(out, param, p, pr);
> > - for (j = 0; minus && pr < w && j + pr < w; j++)
> > - n += f(out, param, &pad, 1);
> > - } else if (c == '%') {
> > - out('%', param);
> > - n++;
> > - } else {
> > - out('%', param);
> > - out(c, param);
> > - n += 2;
> > - }
> > - i++;
> > - } else {
> > - out(fmt[i], param), n++, i++;
> > - }
> > - }
> > - return n;
> > +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
> > + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/fs.c"
> > -#endif
> > -
> > -
> > -
> > -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) 
> {
> > - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> > - if (fd != NULL) {
> > - fd->fd = fs->op(path, flags);
> > - fd->fs = fs;
> > - if (fd->fd == NULL) {
> > - free(fd);
> > - fd = NULL;
> > - }
> > - }
> > - return fd;
> > +// Figure out flash bank based on the address
> > +MG_IRAM static uint32_t flash_bank(void *addr) {
> > + size_t ofs = (char *) addr - (char *) mg_flash_start();
> > + if (mg_flash_bank() == 0) return FLASH_BASE1;
> > + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
> > }
> >
> > -void mg_fs_close(struct mg_fd *fd) {
> > - if (fd != NULL) {
> > - fd->fs->cl(fd->fd);
> > - free(fd);
> > +MG_IRAM bool mg_flash_erase(void *addr) {
> > + bool ok = false;
> > + if (flash_page_start(addr) == false) {
> > + MG_ERROR(("%p is not on a sector boundary", addr));
> > + } else {
> > + uintptr_t diff = (char *) addr - (char *) mg_flash_start();
> > + uint32_t sector = diff / mg_flash_sector_size();
> > + uint32_t bank = flash_bank(addr);
> > + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
> > +
> > + flash_unlock();
> > + if (sector > 7) sector -= 8;
> > +
> > + flash_clear_err(bank);
> > + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
> > + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
> > + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
> > + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
> > + ok = !flash_is_err(bank);
> > + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
> > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> > + MG_REG(bank + FLASH_SR)));
> > + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
> > }
> > + return ok;
> > }
> >
> > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) {
> > - struct mg_fd *fd;
> > - char *data = NULL;
> > - size_t size = 0;
> > - fs->st(path, &size, NULL);
> > - if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
> > - data = (char *) calloc(1, size + 1);
> > - if (data != NULL) {
> > - if (fs->rd(fd->fd, data, size) != size) {
> > - free(data);
> > - data = NULL;
> > - } else {
> > - data[size] = '\0';
> > - if (sizep != NULL) *sizep = size;
> > - }
> > - }
> > - mg_fs_close(fd);
> > - }
> > - return data;
> > +MG_IRAM bool mg_flash_swap_bank(void) {
> > + if (mg_flash_bank() == 0) return true;
> > + uint32_t bank = FLASH_BASE1;
> > + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
> > + flash_unlock();
> > + flash_clear_err(bank);
> > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> > + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
> > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> > + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
> > + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) 
> (void) 0;
> > + return true;
> > }
> >
> > -bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
> > - size_t len) {
> > - bool result = false;
> > - struct mg_fd *fd;
> > - char tmp[MG_PATH_MAX];
> > - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> > - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> > - result = fs->wr(fd->fd, buf, len) == len;
> > - mg_fs_close(fd);
> > - if (result) {
> > - fs->rm(path);
> > - fs->mv(tmp, path);
> > - } else {
> > - fs->rm(tmp);
> > - }
> > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
> > + if ((len % mg_flash_write_align()) != 0) {
> > + MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
> > + return false;
> > }
> > - return result;
> > + uint32_t bank = flash_bank(addr);
> > + uint32_t *dst = (uint32_t *) addr;
> > + uint32_t *src = (uint32_t *) buf;
> > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> > + bool ok = true;
> > + flash_unlock();
> > + flash_clear_err(bank);
> > + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
> > + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
> > + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
> > + MG_ARM_DISABLE_IRQ();
> > + while (ok && src < end) {
> > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> > + *(volatile uint32_t *) dst++ = *src++;
> > + flash_wait(bank);
> > + if (flash_is_err(bank)) ok = false;
> > + }
> > + MG_ARM_ENABLE_IRQ();
> > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
> > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> > + MG_REG(bank + FLASH_SR)));
> > + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
> > + return ok;
> > }
> >
> > -bool mg_file_printf(struct mg_fs *fs, const char *path, const char 
> *fmt, ...) {
> > - va_list ap;
> > - char *data;
> > - bool result = false;
> > - va_start(ap, fmt);
> > - data = mg_vmprintf(fmt, &ap);
> > - va_end(ap);
> > - result = mg_file_write(fs, path, data, strlen(data));
> > - free(data);
> > - return result;
> > +MG_IRAM void mg_device_reset(void) {
> > + // SCB->AIRCR = ((0x5fa << 
> SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> > }
> > +#endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/fs_fat.c"
> > +#line 1 "src/dns.c"
> > #endif
> >
> >
> >
> > -#if MG_ENABLE_FATFS
> > -#include <ff.h>
> >
> > -static int mg_days_from_epoch(int y, int m, int d) {
> > - y -= m <= 2;
> > - int era = y / 400;
> > - int yoe = y - era * 400;
> > - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> > - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> > - return era * 146097 + doe - 719468;
> > -}
> >
> > -static time_t mg_timegm(const struct tm *t) {
> > - int year = t->tm_year + 1900;
> > - int month = t->tm_mon; // 0-11
> > - if (month > 11) {
> > - year += month / 12;
> > - month %= 12;
> > - } else if (month < 0) {
> > - int years_diff = (11 - month) / 12;
> > - year -= years_diff;
> > - month += 12 * years_diff;
> > - }
> > - int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> > - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
> > -}
> >
> > -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> > - struct tm tm;
> > - memset(&tm, 0, sizeof(struct tm));
> > - tm.tm_sec = (ftime << 1) & 0x3e;
> > - tm.tm_min = ((ftime >> 5) & 0x3f);
> > - tm.tm_hour = ((ftime >> 11) & 0x1f);
> > - tm.tm_mday = (fdate & 0x1f);
> > - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> > - tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> > - return mg_timegm(&tm);
> > -}
> >
> > -static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> > - FILINFO fi;
> > - if (path[0] == '\0') {
> > - if (size) *size = 0;
> > - if (mtime) *mtime = 0;
> > - return MG_FS_DIR;
> > - } else if (f_stat(path, &fi) == 0) {
> > - if (size) *size = (size_t) fi.fsize;
> > - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> > - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 
> 0);
> > - } else {
> > - return 0;
> > - }
> > -}
> >
> > -static void ff_list(const char *dir, void (*fn)(const char *, void *),
> > - void *userdata) {
> > - DIR d;
> > - FILINFO fi;
> > - if (f_opendir(&d, dir) == FR_OK) {
> > - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> > - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> > - fn(fi.fname, userdata);
> > - }
> > - f_closedir(&d);
> > - }
> > -}
> > +struct dns_data {
> > + struct dns_data *next;
> > + struct mg_connection *c;
> > + uint64_t expire;
> > + uint16_t txnid;
> > +};
> >
> > -static void *ff_open(const char *path, int flags) {
> > - FIL f;
> > - unsigned char mode = FA_READ;
> > - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | 
> FA_OPEN_APPEND;
> > - if (f_open(&f, path, mode) == 0) {
> > - FIL *fp = calloc(1, sizeof(*fp));
> > - memcpy(fp, &f, sizeof(*fp));
> > - return fp;
> > - } else {
> > - return NULL;
> > - }
> > +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
> > + struct mg_dns *, bool);
> > +
> > +static void mg_dns_free(struct dns_data **head, struct dns_data *d) {
> > + LIST_DELETE(struct dns_data, head, d);
> > + free(d);
> > }
> >
> > -static void ff_close(void *fp) {
> > - if (fp != NULL) {
> > - f_close((FIL *) fp);
> > - free(fp);
> > +void mg_resolve_cancel(struct mg_connection *c) {
> > + struct dns_data *tmp, *d;
> > + struct dns_data **head = (struct dns_data **) 
> &c->mgr->active_dns_requests;
> > + for (d = *head; d != NULL; d = tmp) {
> > + tmp = d->next;
> > + if (d->c == c) mg_dns_free(head, d);
> > }
> > }
> >
> > -static size_t ff_read(void *fp, void *buf, size_t len) {
> > - UINT n = 0, misalign = ((size_t) buf) & 3;
> > - if (misalign) {
> > - char aligned[4];
> > - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> > - memcpy(buf, aligned, n);
> > - } else {
> > - f_read((FIL *) fp, buf, len, &n);
> > +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, 
> size_t ofs,
> > + char *to, size_t tolen, size_t j,
> > + int depth) {
> > + size_t i = 0;
> > + if (tolen > 0 && depth == 0) to[0] = '\0';
> > + if (depth > 5) return 0;
> > + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
> > + while (ofs + i + 1 < len) {
> > + size_t n = s[ofs + i];
> > + if (n == 0) {
> > + i++;
> > + break;
> > + }
> > + if (n & 0xc0) {
> > + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len
> > + // MG_INFO(("PTR %lx", (unsigned long) ptr));
> > + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> > + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
> > + return 0;
> > + i += 2;
> > + break;
> > + }
> > + if (ofs + i + n + 1 >= len) return 0;
> > + if (j > 0) {
> > + if (j < tolen) to[j] = '.';
> > + j++;
> > + }
> > + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> > + j += n;
> > + i += n + 1;
> > + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
> > + // MG_INFO(("--> [%s]", to));
> > }
> > - return n;
> > + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is 
> nul-term
> > + return i;
> > }
> >
> > -static size_t ff_write(void *fp, const void *buf, size_t len) {
> > - UINT n = 0;
> > - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
> > +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
> > + char *dst, size_t dstlen) {
> > + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
> > }
> >
> > -static size_t ff_seek(void *fp, size_t offset) {
> > - f_lseek((FIL *) fp, offset);
> > - return offset;
> > +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> > + bool is_question, struct mg_dns_rr *rr) {
> > + const uint8_t *s = buf + ofs, *e = &buf[len];
> > +
> > + memset(rr, 0, sizeof(*rr));
> > + if (len < sizeof(struct mg_dns_header)) return 0; // Too small
> > + if (len > 512) return 0; // Too large, we don't expect that
> > + if (s >= e) return 0; // Overflow
> > +
> > + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) 
> == 0)
> > + return 0;
> > + s += rr->nlen + 4;
> > + if (s > e) return 0;
> > + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> > + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> > + if (is_question) return (size_t) (rr->nlen + 4);
> > +
> > + s += 6;
> > + if (s > e) return 0;
> > + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> > + if (s + rr->alen > e) return 0;
> > + return (size_t) (rr->nlen + rr->alen + 10);
> > }
> >
> > -static bool ff_rename(const char *from, const char *to) {
> > - return f_rename(from, to) == FR_OK;
> > +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message 
> *dm) {
> > + const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> > + struct mg_dns_rr rr;
> > + size_t i, n, num_answers, ofs = sizeof(*h);
> > + memset(dm, 0, sizeof(*dm));
> > +
> > + if (len < sizeof(*h)) return 0; // Too small, headers dont fit
> > + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
> > + num_answers = mg_ntohs(h->num_answers);
> > + if (num_answers > 10) {
> > + MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers));
> > + num_answers = 10; // Sanity cap
> > + }
> > + dm->txnid = mg_ntohs(h->txnid);
> > +
> > + for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> > + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
> > + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> > + ofs += n;
> > + }
> > + for (i = 0; i < num_answers; i++) {
> > + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return 
> false;
> > + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
> > + // dm->name));
> > + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> > + ofs += n;
> > +
> > + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> > + dm->addr.is_ip6 = false;
> > + memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> > + dm->resolved = true;
> > + break; // Return success
> > + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> > + dm->addr.is_ip6 = true;
> > + memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
> > + dm->resolved = true;
> > + break; // Return success
> > + }
> > + }
> > + return true;
> > }
> >
> > -static bool ff_remove(const char *path) {
> > - return f_unlink(path) == FR_OK;
> > +static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
> > + struct dns_data *d, *tmp;
> > + struct dns_data **head = (struct dns_data **) 
> &c->mgr->active_dns_requests;
> > + if (ev == MG_EV_POLL) {
> > + uint64_t now = *(uint64_t *) ev_data;
> > + for (d = *head; d != NULL; d = tmp) {
> > + tmp = d->next;
> > + // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> > + if (now > d->expire) mg_error(d->c, "DNS timeout");
> > + }
> > + } else if (ev == MG_EV_READ) {
> > + struct mg_dns_message dm;
> > + int resolved = 0;
> > + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> > + MG_ERROR(("Unexpected DNS response:"));
> > + mg_hexdump(c->recv.buf, c->recv.len);
> > + } else {
> > + // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
> > + for (d = *head; d != NULL; d = tmp) {
> > + tmp = d->next;
> > + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> > + if (dm.txnid != d->txnid) continue;
> > + if (d->c->is_resolving) {
> > + if (dm.resolved) {
> > + dm.addr.port = d->c->rem.port; // Save port
> > + d->c->rem = dm.addr; // Copy resolved address
> > + MG_DEBUG(
> > + ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem));
> > + mg_connect_resolved(d->c);
> > +#if MG_ENABLE_IPV6
> > + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
> > + c->mgr->use_dns6 == false) {
> > + struct mg_str x = mg_str(dm.name);
> > + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> > +#endif
> > + } else {
> > + mg_error(d->c, "%s DNS lookup failed", dm.name);
> > + }
> > + } else {
> > + MG_ERROR(("%lu already resolved", d->c->id));
> > + }
> > + mg_dns_free(head, d);
> > + resolved = 1;
> > + }
> > + }
> > + if (!resolved) MG_ERROR(("stray DNS reply"));
> > + c->recv.len = 0;
> > + } else if (ev == MG_EV_CLOSE) {
> > + for (d = *head; d != NULL; d = tmp) {
> > + tmp = d->next;
> > + mg_error(d->c, "DNS error");
> > + mg_dns_free(head, d);
> > + }
> > + }
> > }
> >
> > -static bool ff_mkdir(const char *path) {
> > - return f_mkdir(path) == FR_OK;
> > +static bool mg_dns_send(struct mg_connection *c, const struct mg_str 
> *name,
> > + uint16_t txnid, bool ipv6) {
> > + struct {
> > + struct mg_dns_header header;
> > + uint8_t data[256];
> > + } pkt;
> > + size_t i, n;
> > + memset(&pkt, 0, sizeof(pkt));
> > + pkt.header.txnid = mg_htons(txnid);
> > + pkt.header.flags = mg_htons(0x100);
> > + pkt.header.num_questions = mg_htons(1);
> > + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> > + if (name->buf[i] == '.' || i >= name->len) {
> > + pkt.data[n] = (uint8_t) (i - n);
> > + memcpy(&pkt.data[n + 1], name->buf + n, i - n);
> > + n = i + 1;
> > + }
> > + if (i >= name->len) break;
> > + }
> > + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
> > + n += 5;
> > + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
> > + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query
> > + // n += 6;
> > + return mg_send(c, &pkt, sizeof(pkt.header) + n);
> > }
> >
> > -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read,
> > - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> > -#endif
> > +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, 
> int ms,
> > + struct mg_dns *dnsc, bool ipv6) {
> > + struct dns_data *d = NULL;
> > + if (dnsc->url == NULL) {
> > + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> > + } else if (dnsc->c == NULL) {
> > + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> > + if (dnsc->c != NULL) {
> > + dnsc->c->pfn = dns_cb;
> > + // dnsc->c->is_hexdumping = 1;
> > + }
> > + }
> > + if (dnsc->c == NULL) {
> > + mg_error(c, "resolver");
> > + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
> > + mg_error(c, "resolve OOM");
> > + } else {
> > + struct dns_data *reqs = (struct dns_data *) 
> c->mgr->active_dns_requests;
> > + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> > + d->next = (struct dns_data *) c->mgr->active_dns_requests;
> > + c->mgr->active_dns_requests = d;
> > + d->expire = mg_millis() + (uint64_t) ms;
> > + d->c = c;
> > + c->is_resolving = 1;
> > + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) 
> name->len,
> > + name->buf, dnsc->url, d->txnid));
> > + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> > + mg_error(dnsc->c, "DNS send");
> > + }
> > + }
> > +}
> > +
> > +void mg_resolve(struct mg_connection *c, const char *url) {
> > + struct mg_str host = mg_url_host(url);
> > + c->rem.port = mg_htons(mg_url_port(url));
> > + if (mg_aton(host, &c->rem)) {
> > + // host is an IP address, do not fire name resolution
> > + mg_connect_resolved(c);
> > + } else {
> > + // host is not an IP, send DNS resolution request
> > + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
> > + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> > + }
> > +}
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/fs_packed.c"
> > +#line 1 "src/event.c"
> > #endif
> >
> >
> >
> >
> > -struct packed_file {
> > - const char *data;
> > - size_t size;
> > - size_t pos;
> > -};
> >
> > -const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
> > -const char *mg_unlist(size_t no);
> >
> > -#if MG_ENABLE_PACKED_FS
> > -#else
> > -const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
> > - (void) path, (void) size, (void) mtime;
> > - return NULL;
> > -}
> > -const char *mg_unlist(size_t no) {
> > - (void) no;
> > - return NULL;
> > -}
> > +void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> > +#if MG_ENABLE_PROFILE
> > + const char *names[] = {
> > + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE",
> > + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ",
> > + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK",
> > + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD",
> > + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
> > + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) 
> {
> > + MG_PROF_ADD(c, names[ev]);
> > + }
> > #endif
> > -
> > -static int is_dir_prefix(const char *prefix, size_t n, const char 
> *path) {
> > - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> > - return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> > - (n == 0 || path[n] == '/' || path[n - 1] == '/');
> > + // Fire protocol handler first, user handler second. See #2559
> > + if (c->pfn != NULL) c->pfn(c, ev, ev_data);
> > + if (c->fn != NULL) c->fn(c, ev, ev_data);
> > }
> >
> > -static int packed_stat(const char *path, size_t *size, time_t *mtime) {
> > - const char *p;
> > - size_t i, n = strlen(path);
> > - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file
> > - // Scan all files. If `path` is a dir prefix for any of them, it's a 
> dir
> > - for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> > - if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
> > - }
> > - return 0;
> > -}
> > -
> > -static void packed_list(const char *dir, void (*fn)(const char *, void 
> *),
> > - void *userdata) {
> > - char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> > - const char *path, *begin, *end;
> > - size_t i, n = strlen(dir);
> > - tmp[0] = '\0'; // Previously listed entry
> > - for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> > - if (!is_dir_prefix(dir, n, path)) continue;
> > - begin = &path[n + 1];
> > - end = strchr(begin, '/');
> > - if (end == NULL) end = begin + strlen(begin);
> > - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> > - buf[sizeof(buf) - 1] = '\0';
> > - // If this entry has been already listed, skip
> > - // NOTE: we're assuming that file list is sorted alphabetically
> > - if (strcmp(buf, tmp) == 0) continue;
> > - fn(buf, userdata); // Not yet listed, call user function
> > - strcpy(tmp, buf); // And save this entry as listed
> > - }
> > -}
> > -
> > -static void *packed_open(const char *path, int flags) {
> > - size_t size = 0;
> > - const char *data = mg_unpack(path, &size, NULL);
> > - struct packed_file *fp = NULL;
> > - if (data == NULL) return NULL;
> > - if (flags & MG_FS_WRITE) return NULL;
> > - fp = (struct packed_file *) calloc(1, sizeof(*fp));
> > - fp->size = size;
> > - fp->data = data;
> > - return (void *) fp;
> > +void mg_error(struct mg_connection *c, const char *fmt, ...) {
> > + char buf[64];
> > + va_list ap;
> > + va_start(ap, fmt);
> > + mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> > + va_end(ap);
> > + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
> > + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
> > + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it
> > }
> >
> > -static void packed_close(void *fp) {
> > - if (fp != NULL) free(fp);
> > -}
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/fmt.c"
> > +#endif
> >
> > -static size_t packed_read(void *fd, void *buf, size_t len) {
> > - struct packed_file *fp = (struct packed_file *) fd;
> > - if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> > - memcpy(buf, &fp->data[fp->pos], len);
> > - fp->pos += len;
> > - return len;
> > -}
> >
> > -static size_t packed_write(void *fd, const void *buf, size_t len) {
> > - (void) fd, (void) buf, (void) len;
> > - return 0;
> > -}
> >
> > -static size_t packed_seek(void *fd, size_t offset) {
> > - struct packed_file *fp = (struct packed_file *) fd;
> > - fp->pos = offset;
> > - if (fp->pos > fp->size) fp->pos = fp->size;
> > - return fp->pos;
> > -}
> >
> > -static bool packed_rename(const char *from, const char *to) {
> > - (void) from, (void) to;
> > - return false;
> > +static bool is_digit(int c) {
> > + return c >= '0' && c <= '9';
> > }
> >
> > -static bool packed_remove(const char *path) {
> > - (void) path;
> > - return false;
> > +static int addexp(char *buf, int e, int sign) {
> > + int n = 0;
> > + buf[n++] = 'e';
> > + buf[n++] = (char) sign;
> > + if (e > 400) return 0;
> > + if (e < 10) buf[n++] = '0';
> > + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
> > + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> > + buf[n++] = (char) (e + '0');
> > + return n;
> > }
> >
> > -static bool packed_mkdir(const char *path) {
> > - (void) path;
> > - return false;
> > +static int xisinf(double x) {
> > + union {
> > + double f;
> > + uint64_t u;
> > + } ieee754 = {x};
> > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
> > + ((unsigned) ieee754.u == 0);
> > }
> >
> > -struct mg_fs mg_fs_packed = {
> > - packed_stat, packed_list, packed_open, packed_close, packed_read,
> > - packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
> > -
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/fs_posix.c"
> > -#endif
> > -
> > -
> > -#if MG_ENABLE_FILE
> > -
> > -#ifndef MG_STAT_STRUCT
> > -#define MG_STAT_STRUCT stat
> > -#endif
> > -
> > -#ifndef MG_STAT_FUNC
> > -#define MG_STAT_FUNC stat
> > -#endif
> > -
> > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> > -#if !defined(S_ISDIR)
> > - MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
> > - return 0;
> > -#else
> > -#if MG_ARCH == MG_ARCH_WIN32
> > - struct _stati64 st;
> > - wchar_t tmp[MG_PATH_MAX];
> > - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / 
> sizeof(tmp[0]));
> > - if (_wstati64(tmp, &st) != 0) return 0;
> > -#else
> > - struct MG_STAT_STRUCT st;
> > - if (MG_STAT_FUNC(path, &st) != 0) return 0;
> > -#endif
> > - if (size) *size = (size_t) st.st_size;
> > - if (mtime) *mtime = st.st_mtime;
> > - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 
> 0);
> > -#endif
> > +static int xisnan(double x) {
> > + union {
> > + double f;
> > + uint64_t u;
> > + } ieee754 = {x};
> > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> > + ((unsigned) ieee754.u != 0) >
> > + 0x7ff00000;
> > }
> >
> > -#if MG_ARCH == MG_ARCH_WIN32
> > -struct dirent {
> > - char d_name[MAX_PATH];
> > -};
> > -
> > -typedef struct win32_dir {
> > - HANDLE handle;
> > - WIN32_FIND_DATAW info;
> > - struct dirent result;
> > -} DIR;
> > +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, 
> bool tz) {
> > + char buf[40];
> > + int i, s = 0, n = 0, e = 0;
> > + double t, mul, saved;
> > + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> > + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : 
> "-inf");
> > + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> > + if (d < 0.0) d = -d, buf[s++] = '-';
> >
> > -int gettimeofday(struct timeval *tv, void *tz) {
> > - FILETIME ft;
> > - unsigned __int64 tmpres = 0;
> > + // Round
> > + saved = d;
> > + mul = 1.0;
> > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> > + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> > + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> > + d += t;
> > + // Calculate exponent, and 'mul' for scientific representation
> > + mul = 1.0;
> > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> > + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> > + // printf(" --> %g %d %g %g\n", saved, e, t, mul);
> >
> > - if (tv != NULL) {
> > - GetSystemTimeAsFileTime(&ft);
> > - tmpres |= ft.dwHighDateTime;
> > - tmpres <<= 32;
> > - tmpres |= ft.dwLowDateTime;
> > - tmpres /= 10; // convert into microseconds
> > - tmpres -= (int64_t) 11644473600000000;
> > - tv->tv_sec = (long) (tmpres / 1000000UL);
> > - tv->tv_usec = (long) (tmpres % 1000000UL);
> > + if (e >= width && width > 1) {
> > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> > + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> > + n += addexp(buf + s + n, e, '+');
> > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> > + } else if (e <= -width && width > 1) {
> > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> > + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> > + n += addexp(buf + s + n, -e, '-');
> > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> > + } else {
> > + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> > + int ch = (int) (d / t);
> > + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> > + d -= ch * t;
> > + t /= 10.0;
> > + }
> > + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, 
> buf);
> > + if (n == 0) buf[s++] = '0';
> > + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 
> 10.0;
> > + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> > + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> > + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
> > + int ch = (int) (d / t);
> > + buf[s + n++] = (char) (ch + '0');
> > + d -= ch * t;
> > + t /= 10.0;
> > + }
> > }
> > - (void) tz;
> > - return 0;
> > + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing 
> zeroes
> > + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
> > + n += s;
> > + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> > + buf[n] = '\0';
> > + return mg_snprintf(dst, dstlen, "%s", buf);
> > }
> >
> > -static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
> > - int ret;
> > - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> > - strncpy(buf, path, sizeof(buf));
> > - buf[sizeof(buf) - 1] = '\0';
> > - // Trim trailing slashes. Leave backslash for paths like "X:\"
> > - p = buf + strlen(buf) - 1;
> > - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- 
> = '\0';
> > - memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> > - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
> > - // Convert back to Unicode. If doubly-converted string does not match 
> the
> > - // original, something is fishy, reject.
> > - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, 
> sizeof(buf2),
> > - NULL, NULL);
> > - if (strcmp(buf, buf2) != 0) {
> > - wbuf[0] = L'\0';
> > - ret = 0;
> > +static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool 
> is_hex) {
> > + const char *letters = "0123456789abcdef";
> > + uint64_t v = (uint64_t) val;
> > + size_t s = 0, n, i;
> > + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> > + // This loop prints a number in reverse order. I guess this is because 
> we
> > + // write numbers from right to left: least significant digit comes 
> last.
> > + // Maybe because we use Arabic numbers, and Arabs write RTL?
> > + if (is_hex) {
> > + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> > + } else {
> > + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
> > }
> > - return ret;
> > + // Reverse a string
> > + for (i = 0; i < n / 2; i++) {
> > + char t = buf[s + i];
> > + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> > + }
> > + if (val == 0) buf[n++] = '0'; // Handle special case
> > + return n + s;
> > }
> >
> > -DIR *opendir(const char *name) {
> > - DIR *d = NULL;
> > - wchar_t wpath[MAX_PATH];
> > - DWORD attrs;
> > +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> > + size_t len) {
> > + size_t i = 0;
> > + while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> > + return i;
> > +}
> >
> > - if (name == NULL) {
> > - SetLastError(ERROR_BAD_ARGUMENTS);
> > - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> > - SetLastError(ERROR_NOT_ENOUGH_MEMORY);
> > - } else {
> > - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> > - attrs = GetFileAttributesW(wpath);
> > - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> > - (void) wcscat(wpath, L"\\*");
> > - d->handle = FindFirstFileW(wpath, &d->info);
> > - d->result.d_name[0] = '\0';
> > +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char 
> *fmt, ...) {
> > + size_t len = 0;
> > + va_list ap;
> > + va_start(ap, fmt);
> > + len = mg_vxprintf(out, ptr, fmt, &ap);
> > + va_end(ap);
> > + return len;
> > +}
> > +
> > +size_t mg_vxprintf(void (*out)(char, void *), void *param, const char 
> *fmt,
> > + va_list *ap) {
> > + size_t i = 0, n = 0;
> > + while (fmt[i] != '\0') {
> > + if (fmt[i] == '%') {
> > + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec 
> */;
> > + char pad = ' ', minus = 0, c = fmt[++i];
> > + if (c == '#') x++, c = fmt[++i];
> > + if (c == '-') minus++, c = fmt[++i];
> > + if (c == '0') pad = '0', c = fmt[++i];
> > + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
> > + if (c == '.') {
> > + c = fmt[++i];
> > + if (c == '*') {
> > + pr = (size_t) va_arg(*ap, int);
> > + c = fmt[++i];
> > + } else {
> > + pr = 0;
> > + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
> > + }
> > + }
> > + while (c == 'h') c = fmt[++i]; // Treat h and hh as int
> > + if (c == 'l') {
> > + is_long++, c = fmt[++i];
> > + if (c == 'l') is_long++, c = fmt[++i];
> > + }
> > + if (c == 'p') x = 1, is_long = 1;
> > + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> > + c == 'g' || c == 'f') {
> > + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> > + char tmp[40];
> > + size_t xl = x ? 2 : 0;
> > + if (c == 'g' || c == 'f') {
> > + double v = va_arg(*ap, double);
> > + if (pr == ~0U) pr = 6;
> > + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
> > + } else if (is_long == 2) {
> > + int64_t v = va_arg(*ap, int64_t);
> > + k = mg_lld(tmp, v, s, h);
> > + } else if (is_long == 1) {
> > + long v = va_arg(*ap, long);
> > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
> > + } else {
> > + int v = va_arg(*ap, int);
> > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> > + }
> > + for (j = 0; j < xl && w > 0; j++) w--;
> > + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> > + n += scpy(out, param, &pad, 1);
> > + n += scpy(out, param, (char *) "0x", xl);
> > + for (j = 0; pad == '0' && k < w && j + k < w; j++)
> > + n += scpy(out, param, &pad, 1);
> > + n += scpy(out, param, tmp, k);
> > + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> > + n += scpy(out, param, &pad, 1);
> > + } else if (c == 'm' || c == 'M') {
> > + mg_pm_t f = va_arg(*ap, mg_pm_t);
> > + if (c == 'm') out('"', param);
> > + n += f(out, param, ap);
> > + if (c == 'm') n += 2, out('"', param);
> > + } else if (c == 'c') {
> > + int ch = va_arg(*ap, int);
> > + out((char) ch, param);
> > + n++;
> > + } else if (c == 's') {
> > + char *p = va_arg(*ap, char *);
> > + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> > + for (j = 0; !minus && pr < w && j + pr < w; j++)
> > + n += scpy(out, param, &pad, 1);
> > + n += scpy(out, param, p, pr);
> > + for (j = 0; minus && pr < w && j + pr < w; j++)
> > + n += scpy(out, param, &pad, 1);
> > + } else if (c == '%') {
> > + out('%', param);
> > + n++;
> > + } else {
> > + out('%', param);
> > + out(c, param);
> > + n += 2;
> > + }
> > + i++;
> > } else {
> > - free(d);
> > - d = NULL;
> > + out(fmt[i], param), n++, i++;
> > }
> > }
> > - return d;
> > + return n;
> > }
> >
> > -int closedir(DIR *d) {
> > - int result = 0;
> > - if (d != NULL) {
> > - if (d->handle != INVALID_HANDLE_VALUE)
> > - result = FindClose(d->handle) ? 0 : -1;
> > - free(d);
> > - } else {
> > - result = -1;
> > - SetLastError(ERROR_BAD_ARGUMENTS);
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/fs.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) 
> {
> > + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> > + if (fd != NULL) {
> > + fd->fd = fs->op(path, flags);
> > + fd->fs = fs;
> > + if (fd->fd == NULL) {
> > + free(fd);
> > + fd = NULL;
> > + }
> > }
> > - return result;
> > + return fd;
> > }
> >
> > -struct dirent *readdir(DIR *d) {
> > - struct dirent *result = NULL;
> > - if (d != NULL) {
> > - memset(&d->result, 0, sizeof(d->result));
> > - if (d->handle != INVALID_HANDLE_VALUE) {
> > - result = &d->result;
> > - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
> > - sizeof(result->d_name), NULL, NULL);
> > - if (!FindNextFileW(d->handle, &d->info)) {
> > - FindClose(d->handle);
> > - d->handle = INVALID_HANDLE_VALUE;
> > - }
> > - } else {
> > - SetLastError(ERROR_FILE_NOT_FOUND);
> > - }
> > - } else {
> > - SetLastError(ERROR_BAD_ARGUMENTS);
> > +void mg_fs_close(struct mg_fd *fd) {
> > + if (fd != NULL) {
> > + fd->fs->cl(fd->fd);
> > + free(fd);
> > }
> > - return result;
> > }
> > -#endif
> >
> > -static void p_list(const char *dir, void (*fn)(const char *, void *),
> > - void *userdata) {
> > -#if MG_ENABLE_DIRLIST
> > - struct dirent *dp;
> > - DIR *dirp;
> > - if ((dirp = (opendir(dir))) == NULL) return;
> > - while ((dp = readdir(dirp)) != NULL) {
> > - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
> > - fn(dp->d_name, userdata);
> > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
> > + struct mg_str result = {NULL, 0};
> > + void *fp;
> > + fs->st(path, &result.len, NULL);
> > + if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
> > + result.buf = (char *) calloc(1, result.len + 1);
> > + if (result.buf != NULL &&
> > + fs->rd(fp, (void *) result.buf, result.len) != result.len) {
> > + free((void *) result.buf);
> > + result.buf = NULL;
> > + }
> > + fs->cl(fp);
> > }
> > - closedir(dirp);
> > -#else
> > - (void) dir, (void) fn, (void) userdata;
> > -#endif
> > + if (result.buf == NULL) result.len = 0;
> > + return result;
> > }
> >
> > -static void *p_open(const char *path, int flags) {
> > - const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> > -#if MG_ARCH == MG_ARCH_WIN32
> > - wchar_t b1[MG_PATH_MAX], b2[10];
> > - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / 
> sizeof(b1[0]));
> > - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / 
> sizeof(b2[0]));
> > - return (void *) _wfopen(b1, b2);
> > -#else
> > - return (void *) fopen(path, mode);
> > -#endif
> > +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
> > + size_t len) {
> > + bool result = false;
> > + struct mg_fd *fd;
> > + char tmp[MG_PATH_MAX];
> > + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> > + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> > + result = fs->wr(fd->fd, buf, len) == len;
> > + mg_fs_close(fd);
> > + if (result) {
> > + fs->rm(path);
> > + fs->mv(tmp, path);
> > + } else {
> > + fs->rm(tmp);
> > + }
> > + }
> > + return result;
> > }
> >
> > -static void p_close(void *fp) {
> > - fclose((FILE *) fp);
> > +bool mg_file_printf(struct mg_fs *fs, const char *path, const char 
> *fmt, ...) {
> > + va_list ap;
> > + char *data;
> > + bool result = false;
> > + va_start(ap, fmt);
> > + data = mg_vmprintf(fmt, &ap);
> > + va_end(ap);
> > + result = mg_file_write(fs, path, data, strlen(data));
> > + free(data);
> > + return result;
> > }
> >
> > -static size_t p_read(void *fp, void *buf, size_t len) {
> > - return fread(buf, 1, len, (FILE *) fp);
> > +// This helper function allows to scan a filesystem in a sequential way,
> > +// without using callback function:
> > +// char buf[100] = "";
> > +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
> > +// ...
> > +static void mg_fs_ls_fn(const char *filename, void *param) {
> > + struct mg_str *s = (struct mg_str *) param;
> > + if (s->buf[0] == '\0') {
> > + mg_snprintf((char *) s->buf, s->len, "%s", filename);
> > + } else if (strcmp(s->buf, filename) == 0) {
> > + ((char *) s->buf)[0] = '\0'; // Fetch next file
> > + }
> > }
> >
> > -static size_t p_write(void *fp, const void *buf, size_t len) {
> > - return fwrite(buf, 1, len, (FILE *) fp);
> > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t 
> len) {
> > + struct mg_str s = {buf, len};
> > + fs->ls(path, mg_fs_ls_fn, &s);
> > + return buf[0] != '\0';
> > }
> >
> > -static size_t p_seek(void *fp, size_t offset) {
> > -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
> > - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> > - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> > - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> > -#else
> > - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/fs_fat.c"
> > #endif
> > - return (size_t) ftell((FILE *) fp);
> > -}
> >
> > -static bool p_rename(const char *from, const char *to) {
> > - return rename(from, to) == 0;
> > -}
> >
> > -static bool p_remove(const char *path) {
> > - return remove(path) == 0;
> > -}
> >
> > -static bool p_mkdir(const char *path) {
> > - return mkdir(path, 0775) == 0;
> > -}
> > +#if MG_ENABLE_FATFS
> > +#include <ff.h>
> >
> > -#else
> > +static int mg_days_from_epoch(int y, int m, int d) {
> > + y -= m <= 2;
> > + int era = y / 400;
> > + int yoe = y - era * 400;
> > + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> > + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> > + return era * 146097 + doe - 719468;
> > +}
> >
> > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> > - (void) path, (void) size, (void) mtime;
> > - return 0;
> > +static time_t mg_timegm(const struct tm *t) {
> > + int year = t->tm_year + 1900;
> > + int month = t->tm_mon; // 0-11
> > + if (month > 11) {
> > + year += month / 12;
> > + month %= 12;
> > + } else if (month < 0) {
> > + int years_diff = (11 - month) / 12;
> > + year -= years_diff;
> > + month += 12 * years_diff;
> > + }
> > + int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> > + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
> > }
> > -static void p_list(const char *path, void (*fn)(const char *, void *),
> > - void *userdata) {
> > - (void) path, (void) fn, (void) userdata;
> > -}
> > -static void *p_open(const char *path, int flags) {
> > - (void) path, (void) flags;
> > - return NULL;
> > -}
> > -static void p_close(void *fp) {
> > - (void) fp;
> > -}
> > -static size_t p_read(void *fd, void *buf, size_t len) {
> > - (void) fd, (void) buf, (void) len;
> > - return 0;
> > -}
> > -static size_t p_write(void *fd, const void *buf, size_t len) {
> > - (void) fd, (void) buf, (void) len;
> > - return 0;
> > -}
> > -static size_t p_seek(void *fd, size_t offset) {
> > - (void) fd, (void) offset;
> > - return (size_t) ~0;
> > -}
> > -static bool p_rename(const char *from, const char *to) {
> > - (void) from, (void) to;
> > - return false;
> > -}
> > -static bool p_remove(const char *path) {
> > - (void) path;
> > - return false;
> > -}
> > -static bool p_mkdir(const char *path) {
> > - (void) path;
> > - return false;
> > -}
> > -#endif
> > -
> > -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
> > - p_write, p_seek, p_rename, p_remove, p_mkdir};
> > -
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/http.c"
> > -#endif
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -
> > -// Chunk deletion marker is the MSB in the "processed" counter
> > -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> > -
> > -// Multipart POST example:
> > -// --xyz
> > -// Content-Disposition: form-data; name="val"
> > -//
> > -// abcdef
> > -// --xyz
> > -// Content-Disposition: form-data; name="foo"; filename="a.txt"
> > -// Content-Type: text/plain
> > -//
> > -// hello world
> > -//
> > -// --xyz--
> > -size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> > - struct mg_http_part *part) {
> > - struct mg_str cd = mg_str_n("Content-Disposition", 19);
> > - const char *s = body.ptr;
> > - size_t b = ofs, h1, h2, b1, b2, max = body.len;
> > -
> > - // Init part params
> > - if (part != NULL) part->name = part->filename = part->body = 
> mg_str_n(0, 0);
> > -
> > - // Skip boundary
> > - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> > - if (b <= ofs || b + 2 >= max) return 0;
> > - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
> > -
> > - // Skip headers
> > - h1 = h2 = b + 2;
> > - for (;;) {
> > - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> > - if (h2 == h1) break;
> > - if (h2 + 2 >= max) return 0;
> > - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> > - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
> > - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
> > - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 
> 2));
> > - part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> > - part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
> > - }
> > - h1 = h2 = h2 + 2;
> > - }
> > - b1 = b2 = h2 + 2;
> > - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == 
> '\n' &&
> > - memcmp(&s[b2 + 2], s, b - ofs) == 0))
> > - b2++;
> >
> > - if (b2 + 2 >= max) return 0;
> > - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> > - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> > - return b2 + 2;
> > +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> > + struct tm tm;
> > + memset(&tm, 0, sizeof(struct tm));
> > + tm.tm_sec = (ftime << 1) & 0x3e;
> > + tm.tm_min = ((ftime >> 5) & 0x3f);
> > + tm.tm_hour = ((ftime >> 11) & 0x1f);
> > + tm.tm_mday = (fdate & 0x1f);
> > + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> > + tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> > + return mg_timegm(&tm);
> > }
> >
> > -void mg_http_bauth(struct mg_connection *c, const char *user,
> > - const char *pass) {
> > - struct mg_str u = mg_str(user), p = mg_str(pass);
> > - size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> > - if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> > - if (c->send.size >= need) {
> > - int i, n = 0;
> > - char *buf = (char *) &c->send.buf[c->send.len];
> > - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
> > - for (i = 0; i < (int) u.len; i++) {
> > - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
> > - }
> > - if (p.len > 0) {
> > - n = mg_base64_update(':', buf + 21, n);
> > - for (i = 0; i < (int) p.len; i++) {
> > - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
> > - }
> > - }
> > - n = mg_base64_final(buf + 21, n);
> > - c->send.len += 21 + (size_t) n + 2;
> > - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> > +static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> > + FILINFO fi;
> > + if (path[0] == '\0') {
> > + if (size) *size = 0;
> > + if (mtime) *mtime = 0;
> > + return MG_FS_DIR;
> > + } else if (f_stat(path, &fi) == 0) {
> > + if (size) *size = (size_t) fi.fsize;
> > + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> > + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 
> 0);
> > } else {
> > - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
> > + return 0;
> > }
> > }
> >
> > -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> > - struct mg_str k, v, result = mg_str_n(NULL, 0);
> > - while (mg_split(&buf, &k, &v, '&')) {
> > - if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) {
> > - result = v;
> > - break;
> > +static void ff_list(const char *dir, void (*fn)(const char *, void *),
> > + void *userdata) {
> > + DIR d;
> > + FILINFO fi;
> > + if (f_opendir(&d, dir) == FR_OK) {
> > + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> > + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> > + fn(fi.fname, userdata);
> > }
> > + f_closedir(&d);
> > }
> > - return result;
> > }
> >
> > -int mg_http_get_var(const struct mg_str *buf, const char *name, char 
> *dst,
> > - size_t dst_len) {
> > - int len;
> > - if (dst == NULL || dst_len == 0) {
> > - len = -2; // Bad destination
> > - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
> > - len = -1; // Bad source
> > - dst[0] = '\0';
> > - } else {
> > - struct mg_str v = mg_http_var(*buf, mg_str(name));
> > - if (v.ptr == NULL) {
> > - len = -4; // Name does not exist
> > - } else {
> > - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
> > - if (len < 0) len = -3; // Failed to decode
> > +static void *ff_open(const char *path, int flags) {
> > + FIL f;
> > + unsigned char mode = FA_READ;
> > + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | 
> FA_OPEN_APPEND;
> > + if (f_open(&f, path, mode) == 0) {
> > + FIL *fp;
> > + if ((fp = calloc(1, sizeof(*fp))) != NULL) {
> > + memcpy(fp, &f, sizeof(*fp));
> > + return fp;
> > }
> > }
> > - return len;
> > + return NULL;
> > }
> >
> > -static bool isx(int c) {
> > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> > - (c >= 'A' && c <= 'F');
> > +static void ff_close(void *fp) {
> > + if (fp != NULL) {
> > + f_close((FIL *) fp);
> > + free(fp);
> > + }
> > }
> >
> > -int mg_url_decode(const char *src, size_t src_len, char *dst, size_t 
> dst_len,
> > - int is_form_url_encoded) {
> > - size_t i, j;
> > - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> > - if (src[i] == '%') {
> > - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
> > - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> > - mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
> > - i += 2;
> > - } else {
> > - return -1;
> > - }
> > - } else if (is_form_url_encoded && src[i] == '+') {
> > - dst[j] = ' ';
> > - } else {
> > - dst[j] = src[i];
> > - }
> > +static size_t ff_read(void *fp, void *buf, size_t len) {
> > + UINT n = 0, misalign = ((size_t) buf) & 3;
> > + if (misalign) {
> > + char aligned[4];
> > + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> > + memcpy(buf, aligned, n);
> > + } else {
> > + f_read((FIL *) fp, buf, len, &n);
> > }
> > - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
> > - return i >= src_len && j < dst_len ? (int) j : -1;
> > + return n;
> > }
> >
> > -static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' 
> '; }
> > -
> > -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
> > - size_t i;
> > - for (i = 0; i < buf_len; i++) {
> > - if (!isok(buf[i])) return -1;
> > - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> > - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
> > - return (int) i + 1;
> > - }
> > - return 0;
> > +static size_t ff_write(void *fp, const void *buf, size_t len) {
> > + UINT n = 0;
> > + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
> > }
> >
> > -static const char *skip(const char *s, const char *e, const char *d,
> > - struct mg_str *v) {
> > - v->ptr = s;
> > - while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
> > - v->len = (size_t) (s - v->ptr);
> > - while (s < e && strchr(d, *s) != NULL) s++;
> > - return s;
> > +static size_t ff_seek(void *fp, size_t offset) {
> > + f_lseek((FIL *) fp, offset);
> > + return offset;
> > }
> >
> > -struct mg_str *mg_http_get_header(struct mg_http_message *h, const char 
> *name) {
> > - size_t i, n = strlen(name), max = sizeof(h->headers) / 
> sizeof(h->headers[0]);
> > - for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> > - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> > - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
> > - }
> > - return NULL;
> > +static bool ff_rename(const char *from, const char *to) {
> > + return f_rename(from, to) == FR_OK;
> > }
> >
> > -static void mg_http_parse_headers(const char *s, const char *end,
> > - struct mg_http_header *h, int max_headers) {
> > - int i;
> > - for (i = 0; i < max_headers; i++) {
> > - struct mg_str k, v, tmp;
> > - const char *he = skip(s, end, "\n", &tmp);
> > - s = skip(s, he, ": \r\n", &k);
> > - s = skip(s, he, "\r\n", &v);
> > - if (k.len == tmp.len) continue;
> > - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
> > - if (k.len == 0) break;
> > - // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
> > - //(int) k.len, k.ptr, (int) v.len, v.ptr));
> > - h[i].name = k;
> > - h[i].value = v;
> > - }
> > +static bool ff_remove(const char *path) {
> > + return f_unlink(path) == FR_OK;
> > }
> >
> > -int mg_http_parse(const char *s, size_t len, struct mg_http_message 
> *hm) {
> > - int is_response, req_len = mg_http_get_request_len((unsigned char *) 
> s, len);
> > - const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add 
> to NULL
> > - struct mg_str *cl;
> > +static bool ff_mkdir(const char *path) {
> > + return f_mkdir(path) == FR_OK;
> > +}
> >
> > - memset(hm, 0, sizeof(*hm));
> > - if (req_len <= 0) return req_len;
> > +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read,
> > + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> > +#endif
> >
> > - hm->message.ptr = hm->head.ptr = s;
> > - hm->body.ptr = end;
> > - hm->head.len = (size_t) req_len;
> > - hm->chunk.ptr = end;
> > - hm->message.len = hm->body.len = (size_t) ~0; // Set body length to 
> infinite
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/fs_packed.c"
> > +#endif
> >
> > - // Parse request line
> > - s = skip(s, end, " ", &hm->method);
> > - s = skip(s, end, " ", &hm->uri);
> > - s = skip(s, end, "\r\n", &hm->proto);
> >
> > - // Sanity check. Allow protocol/reason to be empty
> > - if (hm->method.len == 0 || hm->uri.len == 0) return -1;
> >
> > - // If URI contains '?' character, setup query string
> > - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != 
> NULL) {
> > - hm->query.ptr = qs + 1;
> > - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
> > - hm->uri.len = (size_t) (qs - hm->uri.ptr);
> > - }
> >
> > - mg_http_parse_headers(s, end, hm->headers,
> > - sizeof(hm->headers) / sizeof(hm->headers[0]));
> > - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> > - hm->body.len = (size_t) mg_to64(*cl);
> > - hm->message.len = (size_t) req_len + hm->body.len;
> > - }
> > +struct packed_file {
> > + const char *data;
> > + size_t size;
> > + size_t pos;
> > +};
> >
> > - // mg_http_parse() is used to parse both HTTP requests and HTTP
> > - // responses. If HTTP response does not have Content-Length set, then
> > - // body is read until socket is closed, i.e. body.len is infinite (~0).
> > - //
> > - // For HTTP requests though, according to
> > - // http://tools.ietf.org/html/rfc7231#section-8.1.3,
> > - // only POST and PUT methods have defined body semantics.
> > - // Therefore, if Content-Length is not specified and methods are
> > - // not one of PUT or POST, set body length to 0.
> > - //
> > - // So, if it is HTTP request, and Content-Length is not set,
> > - // and method is not (PUT or POST) then reset body length to zero.
> > - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
> > - if (hm->body.len == (size_t) ~0 && !is_response &&
> > - mg_vcasecmp(&hm->method, "PUT") != 0 &&
> > - mg_vcasecmp(&hm->method, "POST") != 0) {
> > - hm->body.len = 0;
> > - hm->message.len = (size_t) req_len;
> > - }
> > +#if MG_ENABLE_PACKED_FS
> > +#else
> > +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
> > + *size = 0, *mtime = 0;
> > + (void) path;
> > + return NULL;
> > +}
> > +const char *mg_unlist(size_t no) {
> > + (void) no;
> > + return NULL;
> > +}
> > +#endif
> >
> > - // The 204 (No content) responses also have 0 body length
> > - if (hm->body.len == (size_t) ~0 && is_response &&
> > - mg_vcasecmp(&hm->uri, "204") == 0) {
> > - hm->body.len = 0;
> > - hm->message.len = (size_t) req_len;
> > - }
> > +struct mg_str mg_unpacked(const char *path) {
> > + size_t len = 0;
> > + const char *buf = mg_unpack(path, &len, NULL);
> > + return mg_str_n(buf, len);
> > +}
> >
> > - return req_len;
> > +static int is_dir_prefix(const char *prefix, size_t n, const char 
> *path) {
> > + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> > + return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> > + (n == 0 || path[n] == '/' || path[n - 1] == '/');
> > }
> >
> > -static void mg_http_vprintf_chunk(struct mg_connection *c, const char 
> *fmt,
> > - va_list *ap) {
> > - size_t len = c->send.len;
> > - mg_send(c, " \r\n", 10);
> > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> > - if (c->send.len >= len + 10) {
> > - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len 
> - 10);
> > - c->send.buf[len + 8] = '\r';
> > - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset 
> marker
> > +static int packed_stat(const char *path, size_t *size, time_t *mtime) {
> > + const char *p;
> > + size_t i, n = strlen(path);
> > + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file
> > + // Scan all files. If `path` is a dir prefix for any of them, it's a 
> dir
> > + for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> > + if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
> > }
> > - mg_send(c, "\r\n", 2);
> > + return 0;
> > }
> >
> > -void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, 
> ...) {
> > - va_list ap;
> > - va_start(ap, fmt);
> > - mg_http_vprintf_chunk(c, fmt, &ap);
> > - va_end(ap);
> > +static void packed_list(const char *dir, void (*fn)(const char *, void 
> *),
> > + void *userdata) {
> > + char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> > + const char *path, *begin, *end;
> > + size_t i, n = strlen(dir);
> > + tmp[0] = '\0'; // Previously listed entry
> > + for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> > + if (!is_dir_prefix(dir, n, path)) continue;
> > + begin = &path[n + 1];
> > + end = strchr(begin, '/');
> > + if (end == NULL) end = begin + strlen(begin);
> > + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> > + buf[sizeof(buf) - 1] = '\0';
> > + // If this entry has been already listed, skip
> > + // NOTE: we're assuming that file list is sorted alphabetically
> > + if (strcmp(buf, tmp) == 0) continue;
> > + fn(buf, userdata); // Not yet listed, call user function
> > + strcpy(tmp, buf); // And save this entry as listed
> > + }
> > }
> >
> > -void mg_http_write_chunk(struct mg_connection *c, const char *buf, 
> size_t len) {
> > - mg_printf(c, "%lx\r\n", (unsigned long) len);
> > - mg_send(c, buf, len);
> > - mg_send(c, "\r\n", 2);
> > - if (len == 0) c->is_resp = 0;
> > +static void *packed_open(const char *path, int flags) {
> > + size_t size = 0;
> > + const char *data = mg_unpack(path, &size, NULL);
> > + struct packed_file *fp = NULL;
> > + if (data == NULL) return NULL;
> > + if (flags & MG_FS_WRITE) return NULL;
> > + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) {
> > + fp->size = size;
> > + fp->data = data;
> > + }
> > + return (void *) fp;
> > }
> >
> > -// clang-format off
> > -static const char *mg_http_status_code_str(int status_code) {
> > - switch (status_code) {
> > - case 100: return "Continue";
> > - case 201: return "Created";
> > - case 202: return "Accepted";
> > - case 204: return "No Content";
> > - case 206: return "Partial Content";
> > - case 301: return "Moved Permanently";
> > - case 302: return "Found";
> > - case 304: return "Not Modified";
> > - case 400: return "Bad Request";
> > - case 401: return "Unauthorized";
> > - case 403: return "Forbidden";
> > - case 404: return "Not Found";
> > - case 418: return "I'm a teapot";
> > - case 500: return "Internal Server Error";
> > - case 501: return "Not Implemented";
> > - default: return "OK";
> > - }
> > +static void packed_close(void *fp) {
> > + if (fp != NULL) free(fp);
> > }
> > -// clang-format on
> >
> > -void mg_http_reply(struct mg_connection *c, int code, const char 
> *headers,
> > - const char *fmt, ...) {
> > - va_list ap;
> > - size_t len;
> > - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
> > - mg_http_status_code_str(code), headers == NULL ? "" : headers);
> > - len = c->send.len;
> > - va_start(ap, fmt);
> > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> > - va_end(ap);
> > - if (c->send.len > 15) {
> > - mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
> > - (unsigned long) (c->send.len - len));
> > - c->is_resp = 0;
> > - c->send.buf[len - 4] = '\r'; // Change ending 0 to space
> > - }
> > - c->is_resp = 0;
> > +static size_t packed_read(void *fd, void *buf, size_t len) {
> > + struct packed_file *fp = (struct packed_file *) fd;
> > + if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> > + memcpy(buf, &fp->data[fp->pos], len);
> > + fp->pos += len;
> > + return len;
> > }
> >
> > -static void http_cb(struct mg_connection *, int, void *, void *);
> > -static void restore_http_cb(struct mg_connection *c) {
> > - mg_fs_close((struct mg_fd *) c->pfn_data);
> > - c->pfn_data = NULL;
> > - c->pfn = http_cb;
> > - c->is_resp = 0;
> > +static size_t packed_write(void *fd, const void *buf, size_t len) {
> > + (void) fd, (void) buf, (void) len;
> > + return 0;
> > }
> >
> > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
> > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
> > - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) 
> size);
> > - return buf;
> > +static size_t packed_seek(void *fd, size_t offset) {
> > + struct packed_file *fp = (struct packed_file *) fd;
> > + fp->pos = offset;
> > + if (fp->pos > fp->size) fp->pos = fp->size;
> > + return fp->pos;
> > }
> >
> > -static void static_cb(struct mg_connection *c, int ev, void *ev_data,
> > - void *fn_data) {
> > - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> > - struct mg_fd *fd = (struct mg_fd *) fn_data;
> > - // Read to send IO buffer directly, avoid extra on-stack buffer
> > - size_t n, max = MG_IO_SIZE, space;
> > - size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> > - sizeof(size_t) * sizeof(size_t)];
> > - if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> > - if (c->send.len >= c->send.size) return; // Rate limit
> > - if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> > - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> > - c->send.len += n;
> > - *cl -= n;
> > - if (n == 0) restore_http_cb(c);
> > - } else if (ev == MG_EV_CLOSE) {
> > - restore_http_cb(c);
> > - }
> > - (void) ev_data;
> > +static bool packed_rename(const char *from, const char *to) {
> > + (void) from, (void) to;
> > + return false;
> > }
> >
> > -// Known mime types. Keep it outside guess_content_type() function, 
> since
> > -// some environments don't like it defined there.
> > -// clang-format off
> > -static struct mg_str s_known_types[] = {
> > - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> > - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> > - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> > - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> > - MG_C_STR("gif"), MG_C_STR("image/gif"),
> > - MG_C_STR("png"), MG_C_STR("image/png"),
> > - MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> > - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> > - MG_C_STR("woff"), MG_C_STR("font/woff"),
> > - MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> > - MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> > - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> > - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> > - MG_C_STR("csv"), MG_C_STR("text/csv"),
> > - MG_C_STR("doc"), MG_C_STR("application/msword"),
> > - MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> > - MG_C_STR("gz"), MG_C_STR("application/gzip"),
> > - MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> > - MG_C_STR("json"), MG_C_STR("application/json"),
> > - MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> > - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> > - MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> > - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> > - MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> > - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> > - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> > - MG_C_STR("wav"), MG_C_STR("audio/wav"),
> > - MG_C_STR("webp"), MG_C_STR("image/webp"),
> > - MG_C_STR("zip"), MG_C_STR("application/zip"),
> > - MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> > - {0, 0},
> > -};
> > -// clang-format on
> > +static bool packed_remove(const char *path) {
> > + (void) path;
> > + return false;
> > +}
> >
> > -static struct mg_str guess_content_type(struct mg_str path, const char 
> *extra) {
> > - struct mg_str k, v, s = mg_str(extra);
> > - size_t i = 0;
> > +static bool packed_mkdir(const char *path) {
> > + (void) path;
> > + return false;
> > +}
> >
> > - // Shrink path to its extension only
> > - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
> > - path.ptr += path.len - i;
> > - path.len = i;
> > +struct mg_fs mg_fs_packed = {
> > + packed_stat, packed_list, packed_open, packed_close, packed_read,
> > + packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
> >
> > - // Process user-provided mime type overrides, if any
> > - while (mg_commalist(&s, &k, &v)) {
> > - if (mg_strcmp(path, k) == 0) return v;
> > - }
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/fs_posix.c"
> > +#endif
> >
> > - // Process built-in mime types
> > - for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
> > - if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 
> 1];
> > - }
> >
> > - return mg_str("text/plain; charset=utf-8");
> > -}
> > +#if MG_ENABLE_POSIX_FS
> >
> > -static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
> > - size_t i, numparsed = 0;
> > - // MG_INFO(("%.*s", (int) s->len, s->ptr));
> > - for (i = 0; i + 6 < s->len; i++) {
> > - if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
> > - struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
> > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> > - *a = mg_to64(p);
> > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> > - while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
> > - if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
> > - *b = mg_to64(p);
> > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> > - break;
> > - }
> > - }
> > - return (int) numparsed;
> > -}
> > +#ifndef MG_STAT_STRUCT
> > +#define MG_STAT_STRUCT stat
> > +#endif
> >
> > -void mg_http_serve_file(struct mg_connection *c, struct mg_http_message 
> *hm,
> > - const char *path,
> > - const struct mg_http_serve_opts *opts) {
> > - char etag[64], tmp[MG_PATH_MAX];
> > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > - struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path, 
> MG_FS_READ);
> > - size_t size = 0;
> > - time_t mtime = 0;
> > - struct mg_str *inm = NULL;
> > - struct mg_str mime = guess_content_type(mg_str(path), 
> opts->mime_types);
> > - bool gzip = false;
> > +#ifndef MG_STAT_FUNC
> > +#define MG_STAT_FUNC stat
> > +#endif
> >
> > - // If file does not exist, we try to open file PATH.gz - and if such
> > - // pre-compressed .gz file exists, serve it with the Content-Encoding: 
> gzip
> > - // Note - we ignore Accept-Encoding, cause we don't have a choice
> > - if (fd == NULL) {
> > - MG_DEBUG(("NULL [%s]", path));
> > - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> > - if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
> > - gzip = true;
> > - path = tmp;
> > - } else if (opts->page404 != NULL) {
> > - // No precompressed file, serve 404
> > - fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> > - mime = guess_content_type(mg_str(path), opts->mime_types);
> > - path = opts->page404;
> > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> > +#if !defined(S_ISDIR)
> > + MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
> > + return 0;
> > +#else
> > +#if MG_ARCH == MG_ARCH_WIN32
> > + struct _stati64 st;
> > + wchar_t tmp[MG_PATH_MAX];
> > + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / 
> sizeof(tmp[0]));
> > + if (_wstati64(tmp, &st) != 0) return 0;
> > + // If path is a symlink, windows reports 0 in st.st_size.
> > + // Get a real file size by opening it and jumping to the end
> > + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
> > + FILE *fp = _wfopen(tmp, L"rb");
> > + if (fp != NULL) {
> > + fseek(fp, 0, SEEK_END);
> > + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+
> > + fclose(fp);
> > }
> > }
> > +#else
> > + struct MG_STAT_STRUCT st;
> > + if (MG_STAT_FUNC(path, &st) != 0) return 0;
> > +#endif
> > + if (size) *size = (size_t) st.st_size;
> > + if (mtime) *mtime = st.st_mtime;
> > + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 
> 0);
> > +#endif
> > +}
> >
> > - if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> > - mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> > - mg_fs_close(fd);
> > - // NOTE: mg_http_etag() call should go first!
> > - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
> > - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> > - mg_vcasecmp(inm, etag) == 0) {
> > - mg_fs_close(fd);
> > - mg_http_reply(c, 304, opts->extra_headers, "");
> > - } else {
> > - int n, status = 200;
> > - char range[100];
> > - int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
> > +#if MG_ARCH == MG_ARCH_WIN32
> > +struct dirent {
> > + char d_name[MAX_PATH];
> > +};
> >
> > - // Handle Range header
> > - struct mg_str *rh = mg_http_get_header(hm, "Range");
> > - range[0] = '\0';
> > - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 
> 0) {
> > - // If range is specified like "400-", set second limit to content len
> > - if (n == 1) r2 = cl - 1;
> > - if (r1 > r2 || r2 >= cl) {
> > - status = 416;
> > - cl = 0;
> > - mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
> > - (int64_t) size);
> > - } else {
> > - status = 206;
> > - cl = r2 - r1 + 1;
> > - mg_snprintf(range, sizeof(range),
> > - "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
> > - (int64_t) size);
> > - fs->sk(fd->fd, (size_t) r1);
> > - }
> > - }
> > - mg_printf(c,
> > - "HTTP/1.1 %d %s\r\n"
> > - "Content-Type: %.*s\r\n"
> > - "Etag: %s\r\n"
> > - "Content-Length: %llu\r\n"
> > - "%s%s%s\r\n",
> > - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
> > - etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
> > - opts->extra_headers ? opts->extra_headers : "");
> > - if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
> > - c->is_draining = 1;
> > - c->is_resp = 0;
> > - mg_fs_close(fd);
> > - } else {
> > - // Track to-be-sent content length at the end of c->data, aligned
> > - size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> > - sizeof(size_t) * sizeof(size_t)];
> > - c->pfn = static_cb;
> > - c->pfn_data = fd;
> > - *clp = (size_t) cl;
> > - }
> > +typedef struct win32_dir {
> > + HANDLE handle;
> > + WIN32_FIND_DATAW info;
> > + struct dirent result;
> > +} DIR;
> > +
> > +#if 0
> > +int gettimeofday(struct timeval *tv, void *tz) {
> > + FILETIME ft;
> > + unsigned __int64 tmpres = 0;
> > +
> > + if (tv != NULL) {
> > + GetSystemTimeAsFileTime(&ft);
> > + tmpres |= ft.dwHighDateTime;
> > + tmpres <<= 32;
> > + tmpres |= ft.dwLowDateTime;
> > + tmpres /= 10; // convert into microseconds
> > + tmpres -= (int64_t) 11644473600000000;
> > + tv->tv_sec = (long) (tmpres / 1000000UL);
> > + tv->tv_usec = (long) (tmpres % 1000000UL);
> > }
> > + (void) tz;
> > + return 0;
> > }
> > +#endif
> >
> > -struct printdirentrydata {
> > - struct mg_connection *c;
> > - struct mg_http_message *hm;
> > - const struct mg_http_serve_opts *opts;
> > - const char *dir;
> > -};
> > +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
> > + int ret;
> > + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> > + strncpy(buf, path, sizeof(buf));
> > + buf[sizeof(buf) - 1] = '\0';
> > + // Trim trailing slashes. Leave backslash for paths like "X:\"
> > + p = buf + strlen(buf) - 1;
> > + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- 
> = '\0';
> > + memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> > + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
> > + // Convert back to Unicode. If doubly-converted string does not match 
> the
> > + // original, something is fishy, reject.
> > + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, 
> sizeof(buf2),
> > + NULL, NULL);
> > + if (strcmp(buf, buf2) != 0) {
> > + wbuf[0] = L'\0';
> > + ret = 0;
> > + }
> > + return ret;
> > +}
> >
> > -static void printdirentry(const char *name, void *userdata) {
> > - struct printdirentrydata *d = (struct printdirentrydata *) userdata;
> > - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
> > - size_t size = 0;
> > - time_t t = 0;
> > - char path[MG_PATH_MAX], sz[40], mod[40];
> > - int flags, n = 0;
> > +DIR *opendir(const char *name) {
> > + DIR *d = NULL;
> > + wchar_t wpath[MAX_PATH];
> > + DWORD attrs;
> >
> > - // MG_DEBUG(("[%s] [%s]", d->dir, name));
> > - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> > - sizeof(path)) {
> > - MG_ERROR(("%s truncated", name));
> > - } else if ((flags = fs->st(path, &size, &t)) == 0) {
> > - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> > + if (name == NULL) {
> > + SetLastError(ERROR_BAD_ARGUMENTS);
> > + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> > + SetLastError(ERROR_NOT_ENOUGH_MEMORY);
> > } else {
> > - const char *slash = flags & MG_FS_DIR ? "/" : "";
> > - if (flags & MG_FS_DIR) {
> > - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> > + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> > + attrs = GetFileAttributesW(wpath);
> > + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> > + (void) wcscat(wpath, L"\\*");
> > + d->handle = FindFirstFileW(wpath, &d->info);
> > + d->result.d_name[0] = '\0';
> > } else {
> > - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> > + free(d);
> > + d = NULL;
> > }
> > -#if defined(MG_HTTP_DIRLIST_TIME)
> > - char time_str[30];
> > - struct tm *time_info = localtime(&t);
> > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
> > -#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
> > - char time_str[30];
> > - struct tm *time_info = gmtime(&t);
> > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
> > -#else
> > - mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
> > -#endif
> > - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> > - mg_printf(d->c,
> > - " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> > - "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> > - n, path, slash, name, slash, (unsigned long) t, mod,
> > - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
> > }
> > + return d;
> > }
> >
> > -static void listdir(struct mg_connection *c, struct mg_http_message *hm,
> > - const struct mg_http_serve_opts *opts, char *dir) {
> > - const char *sort_js_code =
> > - "<script>function srt(tb, sc, so, d) {"
> > - "var tr = Array.prototype.slice.call(tb.rows, 0),"
> > - "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = 
> b.cells[sc],"
> > - "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> > - "t1 = a.cells[2].getAttribute('name'), "
> > - "t2 = b.cells[2].getAttribute('name'); "
> > - "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> > - "n1 ? parseInt(n2) - parseInt(n1) : "
> > - "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
> > - const char *sort_js_code2 =
> > - "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> > - "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> > - "};"
> > - "window.onload = function() {"
> > - "var tb = document.getElementById('tb');"
> > - "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 
> 1];"
> > - "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> > - "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); 
> "
> > - "sc = c; ev.preventDefault();}};"
> > - "srt(tb, sc, so, true);"
> > - "}"
> > - "</script>";
> > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > - struct printdirentrydata d = {c, hm, opts, dir};
> > - char tmp[10], buf[MG_PATH_MAX];
> > - size_t off, n;
> > - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0);
> > - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
> > -
> > - mg_printf(c,
> > - "HTTP/1.1 200 OK\r\n"
> > - "Content-Type: text/html; charset=utf-8\r\n"
> > - "%s"
> > - "Content-Length: \r\n\r\n",
> > - opts->extra_headers == NULL ? "" : opts->extra_headers);
> > - off = c->send.len; // Start of body
> > - mg_printf(c,
> > - "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> > - "<style>th,td {text-align: left; padding-right: 1em; "
> > - "font-family: monospace; }</style></head>"
> > - "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> > - "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> > - "<a href=\"#\" rel=\"1\">Modified</a></th>"
> > - "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> > - "<tr><td colspan=\"3\"><hr></td></tr>"
> > - "</thead>"
> > - "<tbody id=\"tb\">\n",
> > - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len,
> > - uri.ptr);
> > - mg_printf(c, "%s",
> > - " <tr><td><a href=\"..\">..</a></td>"
> > - "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> > -
> > - fs->ls(dir, printdirentry, &d);
> > - mg_printf(c,
> > - "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> > - "</table><address>Mongoose v.%s</address></body></html>\n",
> > - MG_VERSION);
> > - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len 
> - off));
> > - if (n > sizeof(tmp)) n = 0;
> > - memcpy(c->send.buf + off - 12, tmp, n); // Set content length
> > - c->is_resp = 0; // Mark response end
> > +int closedir(DIR *d) {
> > + int result = 0;
> > + if (d != NULL) {
> > + if (d->handle != INVALID_HANDLE_VALUE)
> > + result = FindClose(d->handle) ? 0 : -1;
> > + free(d);
> > + } else {
> > + result = -1;
> > + SetLastError(ERROR_BAD_ARGUMENTS);
> > + }
> > + return result;
> > }
> >
> > -// Resolve requested file into `path` and return its fs->st() result
> > -static int uri_to_path2(struct mg_connection *c, struct mg_http_message 
> *hm,
> > - struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> > - char *path, size_t path_size) {
> > - int flags, tmp;
> > - // Append URI to the root_dir, and sanitize it
> > - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, 
> dir.ptr);
> > - if (n > path_size) n = path_size;
> > - path[path_size - 1] = '\0';
> > - if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
> > - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n,
> > - path_size - n, 0);
> > - path[path_size - 1] = '\0'; // Double-check
> > - mg_remove_double_dots(path);
> > - n = strlen(path);
> > - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing 
> slashes
> > - flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, 
> NULL);
> > - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, 
> hm->uri.ptr, path,
> > - flags));
> > - if (flags == 0) {
> > - // Do nothing - let's caller decide
> > - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> > - hm->uri.ptr[hm->uri.len - 1] != '/') {
> > - mg_printf(c,
> > - "HTTP/1.1 301 Moved\r\n"
> > - "Location: %.*s/\r\n"
> > - "Content-Length: 0\r\n"
> > +struct dirent *readdir(DIR *d) {
> > + struct dirent *result = NULL;
> > + if (d != NULL) {
> > + memset(&d->result, 0, sizeof(d->result));
> > + if (d->handle != INVALID_HANDLE_VALUE) {
> > + result = &d->result;
> > + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
> > + sizeof(result->d_name), NULL, NULL);
> > + if (!FindNextFileW(d->handle, &d->info)) {
> > + FindClose(d->handle);
> > + d->handle = INVALID_HANDLE_VALUE;
> > + }
> > + } else {
> > + SetLastError(ERROR_FILE_NOT_FOUND);
> > + }
> > + } else {
> > + SetLastError(ERROR_BAD_ARGUMENTS);
> > + }
> > + return result;
> > +}
> > +#endif
> > +
> > +static void p_list(const char *dir, void (*fn)(const char *, void *),
> > + void *userdata) {
> > +#if MG_ENABLE_DIRLIST
> > + struct dirent *dp;
> > + DIR *dirp;
> > + if ((dirp = (opendir(dir))) == NULL) return;
> > + while ((dp = readdir(dirp)) != NULL) {
> > + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
> > + fn(dp->d_name, userdata);
> > + }
> > + closedir(dirp);
> > +#else
> > + (void) dir, (void) fn, (void) userdata;
> > +#endif
> > +}
> > +
> > +static void *p_open(const char *path, int flags) {
> > +#if MG_ARCH == MG_ARCH_WIN32
> > + const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> > + wchar_t b1[MG_PATH_MAX], b2[10];
> > + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / 
> sizeof(b1[0]));
> > + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / 
> sizeof(b2[0]));
> > + return (void *) _wfopen(b1, b2);
> > +#else
> > + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e for 
> CLOEXEC
> > + return (void *) fopen(path, mode);
> > +#endif
> > +}
> > +
> > +static void p_close(void *fp) {
> > + fclose((FILE *) fp);
> > +}
> > +
> > +static size_t p_read(void *fp, void *buf, size_t len) {
> > + return fread(buf, 1, len, (FILE *) fp);
> > +}
> > +
> > +static size_t p_write(void *fp, const void *buf, size_t len) {
> > + return fwrite(buf, 1, len, (FILE *) fp);
> > +}
> > +
> > +static size_t p_seek(void *fp, size_t offset) {
> > +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
> > + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> > + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> > + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> > +#else
> > + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> > +#endif
> > + return (size_t) ftell((FILE *) fp);
> > +}
> > +
> > +static bool p_rename(const char *from, const char *to) {
> > + return rename(from, to) == 0;
> > +}
> > +
> > +static bool p_remove(const char *path) {
> > + return remove(path) == 0;
> > +}
> > +
> > +static bool p_mkdir(const char *path) {
> > + return mkdir(path, 0775) == 0;
> > +}
> > +
> > +#else
> > +
> > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> > + (void) path, (void) size, (void) mtime;
> > + return 0;
> > +}
> > +static void p_list(const char *path, void (*fn)(const char *, void *),
> > + void *userdata) {
> > + (void) path, (void) fn, (void) userdata;
> > +}
> > +static void *p_open(const char *path, int flags) {
> > + (void) path, (void) flags;
> > + return NULL;
> > +}
> > +static void p_close(void *fp) {
> > + (void) fp;
> > +}
> > +static size_t p_read(void *fd, void *buf, size_t len) {
> > + (void) fd, (void) buf, (void) len;
> > + return 0;
> > +}
> > +static size_t p_write(void *fd, const void *buf, size_t len) {
> > + (void) fd, (void) buf, (void) len;
> > + return 0;
> > +}
> > +static size_t p_seek(void *fd, size_t offset) {
> > + (void) fd, (void) offset;
> > + return (size_t) ~0;
> > +}
> > +static bool p_rename(const char *from, const char *to) {
> > + (void) from, (void) to;
> > + return false;
> > +}
> > +static bool p_remove(const char *path) {
> > + (void) path;
> > + return false;
> > +}
> > +static bool p_mkdir(const char *path) {
> > + (void) path;
> > + return false;
> > +}
> > +#endif
> > +
> > +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
> > + p_write, p_seek, p_rename, p_remove, p_mkdir};
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/http.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +static int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
> > + int diff = 0;
> > + if (len > 0) do {
> > + int c = *s1++, d = *s2++;
> > + if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> > + if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
> > + diff = c - d;
> > + } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> > + return diff;
> > +}
> > +
> > +bool mg_to_size_t(struct mg_str str, size_t *val);
> > +bool mg_to_size_t(struct mg_str str, size_t *val) {
> > + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits 
> = 0;
> > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
> > + if (i < str.len && str.buf[i] == '-') return false;
> > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> > + size_t digit = (size_t) (str.buf[i] - '0');
> > + if (result > max2) return false; // Overflow
> > + result *= 10;
> > + if (result > max - digit) return false; // Overflow
> > + result += digit;
> > + i++, ndigits++;
> > + }
> > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
> > + if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT
> > + if (i != str.len) return false; // Ditto
> > + *val = (size_t) result;
> > + return true;
> > +}
> > +
> > +// Chunk deletion marker is the MSB in the "processed" counter
> > +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> > +
> > +// Multipart POST example:
> > +// --xyz
> > +// Content-Disposition: form-data; name="val"
> > +//
> > +// abcdef
> > +// --xyz
> > +// Content-Disposition: form-data; name="foo"; filename="a.txt"
> > +// Content-Type: text/plain
> > +//
> > +// hello world
> > +//
> > +// --xyz--
> > +size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> > + struct mg_http_part *part) {
> > + struct mg_str cd = mg_str_n("Content-Disposition", 19);
> > + const char *s = body.buf;
> > + size_t b = ofs, h1, h2, b1, b2, max = body.len;
> > +
> > + // Init part params
> > + if (part != NULL) part->name = part->filename = part->body = 
> mg_str_n(0, 0);
> > +
> > + // Skip boundary
> > + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> > + if (b <= ofs || b + 2 >= max) return 0;
> > + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
> > +
> > + // Skip headers
> > + h1 = h2 = b + 2;
> > + for (;;) {
> > + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> > + if (h2 == h1) break;
> > + if (h2 + 2 >= max) return 0;
> > + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> > + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
> > + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
> > + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 
> 2));
> > + part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> > + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
> > + }
> > + h1 = h2 = h2 + 2;
> > + }
> > + b1 = b2 = h2 + 2;
> > + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == 
> '\n' &&
> > + memcmp(&s[b2 + 2], s, b - ofs) == 0))
> > + b2++;
> > +
> > + if (b2 + 2 >= max) return 0;
> > + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> > + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> > + return b2 + 2;
> > +}
> > +
> > +void mg_http_bauth(struct mg_connection *c, const char *user,
> > + const char *pass) {
> > + struct mg_str u = mg_str(user), p = mg_str(pass);
> > + size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> > + if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> > + if (c->send.size >= need) {
> > + size_t i, n = 0;
> > + char *buf = (char *) &c->send.buf[c->send.len];
> > + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
> > + for (i = 0; i < u.len; i++) {
> > + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
> > + }
> > + if (p.len > 0) {
> > + n = mg_base64_update(':', buf + 21, n);
> > + for (i = 0; i < p.len; i++) {
> > + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
> > + }
> > + }
> > + n = mg_base64_final(buf + 21, n);
> > + c->send.len += 21 + (size_t) n + 2;
> > + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> > + } else {
> > + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
> > + }
> > +}
> > +
> > +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> > + struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
> > + while (mg_span(buf, &entry, &buf, '&')) {
> > + if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
> > + mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
> > + result = v;
> > + break;
> > + }
> > + }
> > + return result;
> > +}
> > +
> > +int mg_http_get_var(const struct mg_str *buf, const char *name, char 
> *dst,
> > + size_t dst_len) {
> > + int len;
> > + if (dst != NULL && dst_len > 0) {
> > + dst[0] = '\0'; // If destination buffer is valid, always nul-terminate 
> it
> > + }
> > + if (dst == NULL || dst_len == 0) {
> > + len = -2; // Bad destination
> > + } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
> > + len = -1; // Bad source
> > + } else {
> > + struct mg_str v = mg_http_var(*buf, mg_str(name));
> > + if (v.buf == NULL) {
> > + len = -4; // Name does not exist
> > + } else {
> > + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
> > + if (len < 0) len = -3; // Failed to decode
> > + }
> > + }
> > + return len;
> > +}
> > +
> > +static bool isx(int c) {
> > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> > + (c >= 'A' && c <= 'F');
> > +}
> > +
> > +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t 
> dst_len,
> > + int is_form_url_encoded) {
> > + size_t i, j;
> > + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> > + if (src[i] == '%') {
> > + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
> > + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> > + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t));
> > + i += 2;
> > + } else {
> > + return -1;
> > + }
> > + } else if (is_form_url_encoded && src[i] == '+') {
> > + dst[j] = ' ';
> > + } else {
> > + dst[j] = src[i];
> > + }
> > + }
> > + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
> > + return i >= src_len && j < dst_len ? (int) j : -1;
> > +}
> > +
> > +static bool isok(uint8_t c) {
> > + return c == '\n' || c == '\r' || c >= ' ';
> > +}
> > +
> > +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
> > + size_t i;
> > + for (i = 0; i < buf_len; i++) {
> > + if (!isok(buf[i])) return -1;
> > + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> > + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
> > + return (int) i + 1;
> > + }
> > + return 0;
> > +}
> > +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char 
> *name) {
> > + size_t i, n = strlen(name), max = sizeof(h->headers) / 
> sizeof(h->headers[0]);
> > + for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> > + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> > + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
> > + }
> > + return NULL;
> > +}
> > +
> > +// Is it a valid utf-8 continuation byte
> > +static bool vcb(uint8_t c) {
> > + return (c & 0xc0) == 0x80;
> > +}
> > +
> > +// Get character length (valid utf-8). Used to parse method, URI, 
> headers
> > +static size_t clen(const char *s, const char *end) {
> > + const unsigned char *u = (unsigned char *) s, c = *u;
> > + long n = (long) (end - s);
> > + if (c > ' ' && c < '~') return 1; // Usual ascii printed char
> > + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8
> > + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3;
> > + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3]))
> > + return 4;
> > + return 0;
> > +}
> > +
> > +// Skip until the newline. Return advanced `s`, or NULL on error
> > +static const char *skiptorn(const char *s, const char *end, struct 
> mg_str *v) {
> > + v->buf = (char *) s;
> > + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To 
> newline
> > + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray 
> \r
> > + if (s < end && s[0] == '\r') s++; // Skip \r
> > + if (s >= end || *s++ != '\n') return NULL; // Skip \n
> > + return s;
> > +}
> > +
> > +static bool mg_http_parse_headers(const char *s, const char *end,
> > + struct mg_http_header *h, size_t max_hdrs) {
> > + size_t i, n;
> > + for (i = 0; i < max_hdrs; i++) {
> > + struct mg_str k = {NULL, 0}, v = {NULL, 0};
> > + if (s >= end) return false;
> > + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
> > + k.buf = (char *) s;
> > + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len 
> += n;
> > + if (k.len == 0) return false; // Empty name
> > + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8
> > + if (*s++ != ':') return false; // Invalid, not followed by :
> > + // if (clen(s, end) == 0) return false; // Invalid UTF-8
> > + while (s < end && s[0] == ' ') s++; // Skip spaces
> > + if ((s = skiptorn(s, end, &v)) == NULL) return false;
> > + while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim spaces
> > + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, 
> v.buf));
> > + h[i].name = k, h[i].value = v; // Success. Assign values
> > + }
> > + return true;
> > +}
> > +
> > +int mg_http_parse(const char *s, size_t len, struct mg_http_message 
> *hm) {
> > + int is_response, req_len = mg_http_get_request_len((unsigned char *) 
> s, len);
> > + const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add 
> to NULL
> > + const struct mg_str *cl;
> > + size_t n;
> > +
> > + memset(hm, 0, sizeof(*hm));
> > + if (req_len <= 0) return req_len;
> > +
> > + hm->message.buf = hm->head.buf = (char *) s;
> > + hm->body.buf = (char *) end;
> > + hm->head.len = (size_t) req_len;
> > + hm->message.len = hm->body.len = (size_t) -1; // Set body length to 
> infinite
> > +
> > + // Parse request line
> > + hm->method.buf = (char *) s;
> > + while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n;
> > + while (s < end && s[0] == ' ') s++; // Skip spaces
> > + hm->uri.buf = (char *) s;
> > + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n;
> > + while (s < end && s[0] == ' ') s++; // Skip spaces
> > + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
> > +
> > + // If URI contains '?' character, setup query string
> > + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != 
> NULL) {
> > + hm->query.buf = (char *) qs + 1;
> > + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
> > + hm->uri.len = (size_t) (qs - hm->uri.buf);
> > + }
> > +
> > + // Sanity check. Allow protocol/reason to be empty
> > + // Do this check after hm->method.len and hm->uri.len are finalised
> > + if (hm->method.len == 0 || hm->uri.len == 0) return -1;
> > +
> > + if (!mg_http_parse_headers(s, end, hm->headers,
> > + sizeof(hm->headers) / sizeof(hm->headers[0])))
> > + return -1; // error when parsing
> > + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> > + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
> > + hm->message.len = (size_t) req_len + hm->body.len;
> > + }
> > +
> > + // mg_http_parse() is used to parse both HTTP requests and HTTP
> > + // responses. If HTTP response does not have Content-Length set, then
> > + // body is read until socket is closed, i.e. body.len is infinite (~0).
> > + //
> > + // For HTTP requests though, according to
> > + // http://tools.ietf.org/html/rfc7231#section-8.1.3,
> > + // only POST and PUT methods have defined body semantics.
> > + // Therefore, if Content-Length is not specified and methods are
> > + // not one of PUT or POST, set body length to 0.
> > + //
> > + // So, if it is HTTP request, and Content-Length is not set,
> > + // and method is not (PUT or POST) then reset body length to zero.
> > + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
> > + if (hm->body.len == (size_t) ~0 && !is_response &&
> > + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
> > + mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
> > + hm->body.len = 0;
> > + hm->message.len = (size_t) req_len;
> > + }
> > +
> > + // The 204 (No content) responses also have 0 body length
> > + if (hm->body.len == (size_t) ~0 && is_response &&
> > + mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
> > + hm->body.len = 0;
> > + hm->message.len = (size_t) req_len;
> > + }
> > + if (hm->message.len < (size_t) req_len) return -1; // Overflow 
> protection
> > +
> > + return req_len;
> > +}
> > +
> > +static void mg_http_vprintf_chunk(struct mg_connection *c, const char 
> *fmt,
> > + va_list *ap) {
> > + size_t len = c->send.len;
> > + mg_send(c, " \r\n", 10);
> > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> > + if (c->send.len >= len + 10) {
> > + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len 
> - 10);
> > + c->send.buf[len + 8] = '\r';
> > + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset 
> marker
> > + }
> > + mg_send(c, "\r\n", 2);
> > +}
> > +
> > +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, 
> ...) {
> > + va_list ap;
> > + va_start(ap, fmt);
> > + mg_http_vprintf_chunk(c, fmt, &ap);
> > + va_end(ap);
> > +}
> > +
> > +void mg_http_write_chunk(struct mg_connection *c, const char *buf, 
> size_t len) {
> > + mg_printf(c, "%lx\r\n", (unsigned long) len);
> > + mg_send(c, buf, len);
> > + mg_send(c, "\r\n", 2);
> > + if (len == 0) c->is_resp = 0;
> > +}
> > +
> > +// clang-format off
> > +static const char *mg_http_status_code_str(int status_code) {
> > + switch (status_code) {
> > + case 100: return "Continue";
> > + case 101: return "Switching Protocols";
> > + case 102: return "Processing";
> > + case 200: return "OK";
> > + case 201: return "Created";
> > + case 202: return "Accepted";
> > + case 203: return "Non-authoritative Information";
> > + case 204: return "No Content";
> > + case 205: return "Reset Content";
> > + case 206: return "Partial Content";
> > + case 207: return "Multi-Status";
> > + case 208: return "Already Reported";
> > + case 226: return "IM Used";
> > + case 300: return "Multiple Choices";
> > + case 301: return "Moved Permanently";
> > + case 302: return "Found";
> > + case 303: return "See Other";
> > + case 304: return "Not Modified";
> > + case 305: return "Use Proxy";
> > + case 307: return "Temporary Redirect";
> > + case 308: return "Permanent Redirect";
> > + case 400: return "Bad Request";
> > + case 401: return "Unauthorized";
> > + case 402: return "Payment Required";
> > + case 403: return "Forbidden";
> > + case 404: return "Not Found";
> > + case 405: return "Method Not Allowed";
> > + case 406: return "Not Acceptable";
> > + case 407: return "Proxy Authentication Required";
> > + case 408: return "Request Timeout";
> > + case 409: return "Conflict";
> > + case 410: return "Gone";
> > + case 411: return "Length Required";
> > + case 412: return "Precondition Failed";
> > + case 413: return "Payload Too Large";
> > + case 414: return "Request-URI Too Long";
> > + case 415: return "Unsupported Media Type";
> > + case 416: return "Requested Range Not Satisfiable";
> > + case 417: return "Expectation Failed";
> > + case 418: return "I'm a teapot";
> > + case 421: return "Misdirected Request";
> > + case 422: return "Unprocessable Entity";
> > + case 423: return "Locked";
> > + case 424: return "Failed Dependency";
> > + case 426: return "Upgrade Required";
> > + case 428: return "Precondition Required";
> > + case 429: return "Too Many Requests";
> > + case 431: return "Request Header Fields Too Large";
> > + case 444: return "Connection Closed Without Response";
> > + case 451: return "Unavailable For Legal Reasons";
> > + case 499: return "Client Closed Request";
> > + case 500: return "Internal Server Error";
> > + case 501: return "Not Implemented";
> > + case 502: return "Bad Gateway";
> > + case 503: return "Service Unavailable";
> > + case 504: return "Gateway Timeout";
> > + case 505: return "HTTP Version Not Supported";
> > + case 506: return "Variant Also Negotiates";
> > + case 507: return "Insufficient Storage";
> > + case 508: return "Loop Detected";
> > + case 510: return "Not Extended";
> > + case 511: return "Network Authentication Required";
> > + case 599: return "Network Connect Timeout Error";
> > + default: return "";
> > + }
> > +}
> > +// clang-format on
> > +
> > +void mg_http_reply(struct mg_connection *c, int code, const char 
> *headers,
> > + const char *fmt, ...) {
> > + va_list ap;
> > + size_t len;
> > + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
> > + mg_http_status_code_str(code), headers == NULL ? "" : headers);
> > + len = c->send.len;
> > + va_start(ap, fmt);
> > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> > + va_end(ap);
> > + if (c->send.len > 16) {
> > + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu",
> > + (unsigned long) (c->send.len - len));
> > + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space
> > + }
> > + c->is_resp = 0;
> > +}
> > +
> > +static void http_cb(struct mg_connection *, int, void *);
> > +static void restore_http_cb(struct mg_connection *c) {
> > + mg_fs_close((struct mg_fd *) c->pfn_data);
> > + c->pfn_data = NULL;
> > + c->pfn = http_cb;
> > + c->is_resp = 0;
> > +}
> > +
> > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
> > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
> > + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) 
> size);
> > + return buf;
> > +}
> > +
> > +static void static_cb(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> > + struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
> > + // Read to send IO buffer directly, avoid extra on-stack buffer
> > + size_t n, max = MG_IO_SIZE, space;
> > + size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> > + sizeof(size_t) * sizeof(size_t)];
> > + if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> > + if (c->send.len >= c->send.size) return; // Rate limit
> > + if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> > + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> > + c->send.len += n;
> > + *cl -= n;
> > + if (n == 0) restore_http_cb(c);
> > + } else if (ev == MG_EV_CLOSE) {
> > + restore_http_cb(c);
> > + }
> > + (void) ev_data;
> > +}
> > +
> > +// Known mime types. Keep it outside guess_content_type() function, 
> since
> > +// some environments don't like it defined there.
> > +// clang-format off
> > +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
> > +static struct mg_str s_known_types[] = {
> > + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> > + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> > + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> > + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> > + MG_C_STR("gif"), MG_C_STR("image/gif"),
> > + MG_C_STR("png"), MG_C_STR("image/png"),
> > + MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> > + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> > + MG_C_STR("woff"), MG_C_STR("font/woff"),
> > + MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> > + MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> > + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> > + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> > + MG_C_STR("csv"), MG_C_STR("text/csv"),
> > + MG_C_STR("doc"), MG_C_STR("application/msword"),
> > + MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> > + MG_C_STR("gz"), MG_C_STR("application/gzip"),
> > + MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> > + MG_C_STR("json"), MG_C_STR("application/json"),
> > + MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> > + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> > + MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> > + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> > + MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> > + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> > + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> > + MG_C_STR("wav"), MG_C_STR("audio/wav"),
> > + MG_C_STR("webp"), MG_C_STR("image/webp"),
> > + MG_C_STR("zip"), MG_C_STR("application/zip"),
> > + MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> > + {0, 0},
> > +};
> > +// clang-format on
> > +
> > +static struct mg_str guess_content_type(struct mg_str path, const char 
> *extra) {
> > + struct mg_str entry, k, v, s = mg_str(extra);
> > + size_t i = 0;
> > +
> > + // Shrink path to its extension only
> > + while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
> > + path.buf += path.len - i;
> > + path.len = i;
> > +
> > + // Process user-provided mime type overrides, if any
> > + while (mg_span(s, &entry, &s, ',')) {
> > + if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0) return v;
> > + }
> > +
> > + // Process built-in mime types
> > + for (i = 0; s_known_types[i].buf != NULL; i += 2) {
> > + if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 
> 1];
> > + }
> > +
> > + return mg_str("text/plain; charset=utf-8");
> > +}
> > +
> > +static int getrange(struct mg_str *s, size_t *a, size_t *b) {
> > + size_t i, numparsed = 0;
> > + for (i = 0; i + 6 < s->len; i++) {
> > + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
> > + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
> > + if (mg_span(v, &k, &v, '-')) {
> > + if (mg_to_size_t(k, a)) numparsed++;
> > + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
> > + } else {
> > + if (mg_to_size_t(v, a)) numparsed++;
> > + }
> > + break;
> > + }
> > + return (int) numparsed;
> > +}
> > +
> > +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message 
> *hm,
> > + const char *path,
> > + const struct mg_http_serve_opts *opts) {
> > + char etag[64], tmp[MG_PATH_MAX];
> > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > + struct mg_fd *fd = NULL;
> > + size_t size = 0;
> > + time_t mtime = 0;
> > + struct mg_str *inm = NULL;
> > + struct mg_str mime = guess_content_type(mg_str(path), 
> opts->mime_types);
> > + bool gzip = false;
> > +
> > + if (path != NULL) {
> > + // If a browser sends us "Accept-Encoding: gzip", try to open .gz first
> > + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
> > + if (ae != NULL) {
> > + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
> > + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
> > + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> > + fd = mg_fs_open(fs, tmp, MG_FS_READ);
> > + if (fd != NULL) gzip = true, path = tmp;
> > + }
> > + free(ae_);
> > + }
> > + // No luck opening .gz? Open what we've told to open
> > + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
> > + }
> > +
> > + // Failed to open, and page404 is configured? Open it, then
> > + if (fd == NULL && opts->page404 != NULL) {
> > + fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> > + path = opts->page404;
> > + mime = guess_content_type(mg_str(path), opts->mime_types);
> > + }
> > +
> > + if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> > + mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> > + mg_fs_close(fd);
> > + // NOTE: mg_http_etag() call should go first!
> > + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
> > + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> > + mg_strcasecmp(*inm, mg_str(etag)) == 0) {
> > + mg_fs_close(fd);
> > + mg_http_reply(c, 304, opts->extra_headers, "");
> > + } else {
> > + int n, status = 200;
> > + char range[100];
> > + size_t r1 = 0, r2 = 0, cl = size;
> > +
> > + // Handle Range header
> > + struct mg_str *rh = mg_http_get_header(hm, "Range");
> > + range[0] = '\0';
> > + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
> > + // If range is specified like "400-", set second limit to content len
> > + if (n == 1) r2 = cl - 1;
> > + if (r1 > r2 || r2 >= cl) {
> > + status = 416;
> > + cl = 0;
> > + mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
> > + (int64_t) size);
> > + } else {
> > + status = 206;
> > + cl = r2 - r1 + 1;
> > + mg_snprintf(range, sizeof(range),
> > + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
> > + (uint64_t) (r1 + cl - 1), (uint64_t) size);
> > + fs->sk(fd->fd, r1);
> > + }
> > + }
> > + mg_printf(c,
> > + "HTTP/1.1 %d %s\r\n"
> > + "Content-Type: %.*s\r\n"
> > + "Etag: %s\r\n"
> > + "Content-Length: %llu\r\n"
> > + "%s%s%s\r\n",
> > + status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
> > + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
> > + range, opts->extra_headers ? opts->extra_headers : "");
> > + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
> > + c->is_draining = 1;
> > + c->is_resp = 0;
> > + mg_fs_close(fd);
> > + } else {
> > + // Track to-be-sent content length at the end of c->data, aligned
> > + size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
> > + sizeof(size_t) * sizeof(size_t)];
> > + c->pfn = static_cb;
> > + c->pfn_data = fd;
> > + *clp = cl;
> > + }
> > + }
> > +}
> > +
> > +struct printdirentrydata {
> > + struct mg_connection *c;
> > + struct mg_http_message *hm;
> > + const struct mg_http_serve_opts *opts;
> > + const char *dir;
> > +};
> > +
> > +#if MG_ENABLE_DIRLIST
> > +static void printdirentry(const char *name, void *userdata) {
> > + struct printdirentrydata *d = (struct printdirentrydata *) userdata;
> > + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
> > + size_t size = 0;
> > + time_t t = 0;
> > + char path[MG_PATH_MAX], sz[40], mod[40];
> > + int flags, n = 0;
> > +
> > + // MG_DEBUG(("[%s] [%s]", d->dir, name));
> > + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> > + sizeof(path)) {
> > + MG_ERROR(("%s truncated", name));
> > + } else if ((flags = fs->st(path, &size, &t)) == 0) {
> > + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> > + } else {
> > + const char *slash = flags & MG_FS_DIR ? "/" : "";
> > + if (flags & MG_FS_DIR) {
> > + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> > + } else {
> > + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> > + }
> > +#if defined(MG_HTTP_DIRLIST_TIME_FMT)
> > + {
> > + char time_str[40];
> > + struct tm *time_info = localtime(&t);
> > + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
> > + mg_snprintf(mod, sizeof(mod), "%s", time_str);
> > + }
> > +#else
> > + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
> > +#endif
> > + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> > + mg_printf(d->c,
> > + " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> > + "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> > + n, path, slash, name, slash, (unsigned long) t, mod,
> > + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
> > + }
> > +}
> > +
> > +static void listdir(struct mg_connection *c, struct mg_http_message *hm,
> > + const struct mg_http_serve_opts *opts, char *dir) {
> > + const char *sort_js_code =
> > + "<script>function srt(tb, sc, so, d) {"
> > + "var tr = Array.prototype.slice.call(tb.rows, 0),"
> > + "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = 
> b.cells[sc],"
> > + "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> > + "t1 = a.cells[2].getAttribute('name'), "
> > + "t2 = b.cells[2].getAttribute('name'); "
> > + "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> > + "n1 ? parseInt(n2) - parseInt(n1) : "
> > + "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
> > + const char *sort_js_code2 =
> > + "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> > + "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> > + "};"
> > + "window.onload = function() {"
> > + "var tb = document.getElementById('tb');"
> > + "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 
> 1];"
> > + "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> > + "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); 
> "
> > + "sc = c; ev.preventDefault();}};"
> > + "srt(tb, sc, so, true);"
> > + "}"
> > + "</script>";
> > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > + struct printdirentrydata d = {c, hm, opts, dir};
> > + char tmp[10], buf[MG_PATH_MAX];
> > + size_t off, n;
> > + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0);
> > + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
> > +
> > + mg_printf(c,
> > + "HTTP/1.1 200 OK\r\n"
> > + "Content-Type: text/html; charset=utf-8\r\n"
> > + "%s"
> > + "Content-Length: \r\n\r\n",
> > + opts->extra_headers == NULL ? "" : opts->extra_headers);
> > + off = c->send.len; // Start of body
> > + mg_printf(c,
> > + "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> > + "<style>th,td {text-align: left; padding-right: 1em; "
> > + "font-family: monospace; }</style></head>"
> > + "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> > + "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> > + "<a href=\"#\" rel=\"1\">Modified</a></th>"
> > + "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> > + "<tr><td colspan=\"3\"><hr></td></tr>"
> > + "</thead>"
> > + "<tbody id=\"tb\">\n",
> > + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int) uri.len,
> > + uri.buf);
> > + mg_printf(c, "%s",
> > + " <tr><td><a href=\"..\">..</a></td>"
> > + "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> > +
> > + fs->ls(dir, printdirentry, &d);
> > + mg_printf(c,
> > + "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> > + "</table><address>Mongoose v.%s</address></body></html>\n",
> > + MG_VERSION);
> > + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len 
> - off));
> > + if (n > sizeof(tmp)) n = 0;
> > + memcpy(c->send.buf + off - 12, tmp, n); // Set content length
> > + c->is_resp = 0; // Mark response end
> > +}
> > +#endif
> > +
> > +// Resolve requested file into `path` and return its fs->st() result
> > +static int uri_to_path2(struct mg_connection *c, struct mg_http_message 
> *hm,
> > + struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> > + char *path, size_t path_size) {
> > + int flags, tmp;
> > + // Append URI to the root_dir, and sanitize it
> > + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, 
> dir.buf);
> > + if (n + 2 >= path_size) {
> > + mg_http_reply(c, 400, "", "Exceeded path size");
> > + return -1;
> > + }
> > + path[path_size - 1] = '\0';
> > + // Terminate root dir with slash
> > + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
> > + if (url.len < hm->uri.len) {
> > + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n,
> > + path_size - n, 0);
> > + }
> > + path[path_size - 1] = '\0'; // Double-check
> > + if (!mg_path_is_sane(mg_str_n(path, path_size))) {
> > + mg_http_reply(c, 400, "", "Invalid path");
> > + return -1;
> > + }
> > + n = strlen(path);
> > + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing 
> slashes
> > + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
> > + : fs->st(path, NULL, NULL);
> > + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, 
> hm->uri.buf, path,
> > + flags));
> > + if (flags == 0) {
> > + // Do nothing - let's caller decide
> > + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> > + hm->uri.buf[hm->uri.len - 1] != '/') {
> > + mg_printf(c,
> > + "HTTP/1.1 301 Moved\r\n"
> > + "Location: %.*s/\r\n"
> > + "Content-Length: 0\r\n"
> > "\r\n",
> > - (int) hm->uri.len, hm->uri.ptr);
> > + (int) hm->uri.len, hm->uri.buf);
> > c->is_resp = 0;
> > flags = -1;
> > } else if (flags & MG_FS_DIR) {
> > @@ -2057,3318 +3036,11754 @@ static int uri_to_path2(struct 
> mg_connection *c, struct mg_http_message *hm,
> > path[n + 1 + strlen(MG_HTTP_INDEX)] =
> > '\0'; // Remove appended .gz in index file name
> > } else {
> > - path[n] = '\0'; // Remove appended index file name
> > + path[n] = '\0'; // Remove appended index file name
> > + }
> > + }
> > + return flags;
> > +}
> > +
> > +static int uri_to_path(struct mg_connection *c, struct mg_http_message 
> *hm,
> > + const struct mg_http_serve_opts *opts, char *path,
> > + size_t path_size) {
> > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > + struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p 
> = u;
> > + while (mg_span(s, &part, &s, ',')) {
> > + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
> > + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
> > + if (hm->uri.len < k.len) continue;
> > + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
> > + u = k, p = v;
> > + }
> > + return uri_to_path2(c, hm, fs, u, p, path, path_size);
> > +}
> > +
> > +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message 
> *hm,
> > + const struct mg_http_serve_opts *opts) {
> > + char path[MG_PATH_MAX];
> > + const char *sp = opts->ssi_pattern;
> > + int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> > + if (flags < 0) {
> > + // Do nothing: the response has already been sent by uri_to_path()
> > + } else if (flags & MG_FS_DIR) {
> > +#if MG_ENABLE_DIRLIST
> > + listdir(c, hm, opts, path);
> > +#else
> > + mg_http_reply(c, 403, "", "Forbidden\n");
> > +#endif
> > + } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), 
> NULL)) {
> > + mg_http_serve_ssi(c, opts->root_dir, path);
> > + } else {
> > + mg_http_serve_file(c, hm, path, opts);
> > + }
> > +}
> > +
> > +static bool mg_is_url_safe(int c) {
> > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> > + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
> > +}
> > +
> > +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
> > + size_t i, n = 0;
> > + for (i = 0; i < sl; i++) {
> > + int c = *(unsigned char *) &s[i];
> > + if (n + 4 >= len) return 0;
> > + if (mg_is_url_safe(c)) {
> > + buf[n++] = s[i];
> > + } else {
> > + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
> > + n += 3;
> > + }
> > + }
> > + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the 
> destination
> > + if (len > 0) buf[len - 1] = '\0'; // Always.
> > + return n;
> > +}
> > +
> > +void mg_http_creds(struct mg_http_message *hm, char *user, size_t 
> userlen,
> > + char *pass, size_t passlen) {
> > + struct mg_str *v = mg_http_get_header(hm, "Authorization");
> > + user[0] = pass[0] = '\0';
> > + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
> > + char buf[256];
> > + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf));
> > + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
> > + if (p != NULL) {
> > + mg_snprintf(user, userlen, "%.*s", p - buf, buf);
> > + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1);
> > + }
> > + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 
> 0) {
> > + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
> > + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> > + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 
> 12));
> > + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf);
> > + } else {
> > + mg_http_get_var(&hm->query, "access_token", pass, passlen);
> > + }
> > +}
> > +
> > +static struct mg_str stripquotes(struct mg_str s) {
> > + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
> > + ? mg_str_n(s.buf + 1, s.len - 2)
> > + : s;
> > +}
> > +
> > +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
> > + size_t i;
> > + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> > + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) {
> > + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
> > + int q = p < x && *p == '"' ? 1 : 0;
> > + while (p < x &&
> > + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> > + p++;
> > + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len,
> > + // v.buf, (int) (p - b), b));
> > + return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> > + }
> > + }
> > + return mg_str_n(NULL, 0);
> > +}
> > +
> > +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> > + struct mg_fs *fs, const char *dir, size_t max_size) {
> > + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
> > + long res = 0, offset;
> > + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> > + mg_http_get_var(&hm->query, "file", file, sizeof(file));
> > + offset = strtol(buf, NULL, 0);
> > + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
> > + if (hm->body.len == 0) {
> > + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
> > + } else if (file[0] == '\0') {
> > + mg_http_reply(c, 400, "", "file required");
> > + res = -1;
> > + } else if (mg_path_is_sane(mg_str(file)) == false) {
> > + mg_http_reply(c, 400, "", "%s: invalid file", file);
> > + res = -2;
> > + } else if (offset < 0) {
> > + mg_http_reply(c, 400, "", "offset required");
> > + res = -3;
> > + } else if ((size_t) offset + hm->body.len > max_size) {
> > + mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> > + (unsigned long) max_size);
> > + res = -4;
> > + } else {
> > + struct mg_fd *fd;
> > + size_t current_size = 0;
> > + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
> > + if (offset == 0) fs->rm(path); // If offset if 0, truncate file
> > + fs->st(path, &current_size, NULL);
> > + if (offset > 0 && current_size != (size_t) offset) {
> > + mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> > + res = -5;
> > + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> > + mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> > + res = -6;
> > + } else {
> > + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
> > + mg_fs_close(fd);
> > + mg_http_reply(c, 200, "", "%ld", res);
> > + }
> > + }
> > + return res;
> > +}
> > +
> > +int mg_http_status(const struct mg_http_message *hm) {
> > + return atoi(hm->uri.buf);
> > +}
> > +
> > +static bool is_hex_digit(int c) {
> > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> > + (c >= 'A' && c <= 'F');
> > +}
> > +
> > +static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
> > + int i = 0, n = 0;
> > + if (len < 3) return 0;
> > + while (i < len && is_hex_digit(buf[i])) i++;
> > + if (i == 0) return -1; // Error, no length specified
> > + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is too big
> > + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1; // 
> Error
> > + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == 
> false)
> > + return -1; // Decode chunk length, overflow
> > + if (n < 0) return -1; // Error. TODO(): some checks now redundant
> > + if (n > len - i - 4) return 0; // Chunk not yet fully buffered
> > + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1; // 
> Error
> > + *pl = i + 2, *dl = n;
> > + return i + 2 + n + 2;
> > +}
> > +
> > +static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> > + struct mg_http_message hm;
> > + size_t ofs = 0; // Parsing offset
> > + while (c->is_resp == 0 && ofs < c->recv.len) {
> > + const char *buf = (char *) c->recv.buf + ofs;
> > + int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
> > + struct mg_str *te; // Transfer - encoding header
> > + bool is_chunked = false;
> > + if (n < 0) {
> > + // We don't use mg_error() here, to avoid closing pipelined requests
> > + // prematurely, see #2592
> > + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
> > + c->is_draining = 1;
> > + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
> > + c->recv.len = 0;
> > + return;
> > + }
> > + if (n == 0) break; // Request is not buffered yet
> > + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers
> > + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
> > + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG
> > + hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf);
> > + }
> > + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
> > + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
> > + is_chunked = true;
> > + } else {
> > + mg_error(c, "Invalid Transfer-Encoding"); // See #2460
> > + return;
> > + }
> > + } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
> > + // #2593: HTTP packets must contain either Transfer-Encoding or
> > + // Content-length
> > + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
> > + bool require_content_len = false;
> > + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 ||
> > + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
> > + // POST and PUT should include an entity body. Therefore, they should
> > + // contain a Content-length header. Other requests can also contain a
> > + // body, but their content has no defined semantics (RFC 7231)
> > + require_content_len = true;
> > + } else if (is_response) {
> > + // HTTP spec 7.2 Entity body: All other responses must include a body
> > + // or Content-Length header field defined with a value of 0.
> > + int status = mg_http_status(&hm);
> > + require_content_len = status >= 200 && status != 204 && status != 304;
> > + }
> > + if (require_content_len) {
> > + mg_http_reply(c, 411, "", "");
> > + MG_ERROR(("%s", "Content length missing from request"));
> > + }
> > + }
> > +
> > + if (is_chunked) {
> > + // For chunked data, strip off prefixes and suffixes from chunks
> > + // and relocate them right after the headers, then report a message
> > + char *s = (char *) c->recv.buf + ofs + n;
> > + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n);
> > +
> > + // Find zero-length chunk (the end of the body)
> > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl;
> > + if (cl == 0) break; // No zero-len chunk, buffer more data
> > + if (cl < 0) {
> > + mg_error(c, "Invalid chunk");
> > + break;
> > + }
> > +
> > + // Zero chunk found. Second pass: strip + relocate
> > + o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
> > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
> > + memmove(s + hm.body.len, s + o + pl, (size_t) dl);
> > + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl;
> > + if (dl == 0) break;
> > + }
> > + ofs += (size_t) (n + o);
> > + } else { // Normal, non-chunked data
> > + size_t len = c->recv.len - ofs - (size_t) n;
> > + if (hm.body.len > len) break; // Buffer more data
> > + ofs += (size_t) n + hm.body.len;
> > + }
> > +
> > + if (c->is_accepted) c->is_resp = 1; // Start generating response
> > + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
> > + }
> > + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data
> > + }
> > + (void) ev_data;
> > +}
> > +
> > +static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_HTTP_MSG) {
> > + struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> > + if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
> > + mg_http_reply(c, 200, "", "ok\n");
> > + c->is_draining = 1;
> > + c->data[0] = 'X';
> > + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
> > + int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
> > + mg_log_set(level);
> > + mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
> > + } else {
> > + mg_http_reply(c, 200, "", "hi\n");
> > + }
> > + } else if (ev == MG_EV_CLOSE) {
> > + if (c->data[0] == 'X') *(bool *) c->fn_data = true;
> > + }
> > +}
> > +
> > +void mg_hello(const char *url) {
> > + struct mg_mgr mgr;
> > + bool done = false;
> > + mg_mgr_init(&mgr);
> > + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
> > + while (done == false) mg_mgr_poll(&mgr, 100);
> > + mg_mgr_free(&mgr);
> > +}
> > +
> > +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char 
> *url,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> > + if (c != NULL) c->pfn = http_cb;
> > + return c;
> > +}
> > +
> > +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char 
> *url,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> > + if (c != NULL) c->pfn = http_cb;
> > + return c;
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/iobuf.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +static size_t roundup(size_t size, size_t align) {
> > + return align == 0 ? size : (size + align - 1) / align * align;
> > +}
> > +
> > +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> > + int ok = 1;
> > + new_size = roundup(new_size, io->align);
> > + if (new_size == 0) {
> > + mg_bzero(io->buf, io->size);
> > + free(io->buf);
> > + io->buf = NULL;
> > + io->len = io->size = 0;
> > + } else if (new_size != io->size) {
> > + // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease 
> the
> > + // porting to some obscure platforms like FreeRTOS
> > + void *p = calloc(1, new_size);
> > + if (p != NULL) {
> > + size_t len = new_size < io->len ? new_size : io->len;
> > + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> > + mg_bzero(io->buf, io->size);
> > + free(io->buf);
> > + io->buf = (unsigned char *) p;
> > + io->size = new_size;
> > + } else {
> > + ok = 0;
> > + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
> > + }
> > + }
> > + return ok;
> > +}
> > +
> > +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> > + io->buf = NULL;
> > + io->align = align;
> > + io->size = io->len = 0;
> > + return mg_iobuf_resize(io, size);
> > +}
> > +
> > +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
> > + size_t len) {
> > + size_t new_size = roundup(io->len + len, io->align);
> > + mg_iobuf_resize(io, new_size); // Attempt to resize
> > + if (new_size != io->size) len = 0; // Resize failure, append nothing
> > + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len 
> - ofs);
> > + if (buf != NULL) memmove(io->buf + ofs, buf, len);
> > + if (ofs > io->len) io->len += ofs - io->len;
> > + io->len += len;
> > + return len;
> > +}
> > +
> > +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> > + if (ofs > io->len) ofs = io->len;
> > + if (ofs + len > io->len) len = io->len - ofs;
> > + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs 
> - len);
> > + if (io->buf) mg_bzero(io->buf + io->len - len, len);
> > + io->len -= len;
> > + return len;
> > +}
> > +
> > +void mg_iobuf_free(struct mg_iobuf *io) {
> > + mg_iobuf_resize(io, 0);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/json.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +static const char *escapeseq(int esc) {
> > + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> > +}
> > +
> > +static char json_esc(int c, int esc) {
> > + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> > + for (p = esc1; *p != '\0'; p++) {
> > + if (*p == c) return esc2[p - esc1];
> > + }
> > + return 0;
> > +}
> > +
> > +static int mg_pass_string(const char *s, int len) {
> > + int i;
> > + for (i = 0; i < len; i++) {
> > + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> > + i++;
> > + } else if (s[i] == '\0') {
> > + return MG_JSON_INVALID;
> > + } else if (s[i] == '"') {
> > + return i;
> > + }
> > + }
> > + return MG_JSON_INVALID;
> > +}
> > +
> > +static double mg_atod(const char *p, int len, int *numlen) {
> > + double d = 0.0;
> > + int i = 0, sign = 1;
> > +
> > + // Sign
> > + if (i < len && *p == '-') {
> > + sign = -1, i++;
> > + } else if (i < len && *p == '+') {
> > + i++;
> > + }
> > +
> > + // Decimal
> > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> > + d *= 10.0;
> > + d += p[i] - '0';
> > + }
> > + d *= sign;
> > +
> > + // Fractional
> > + if (i < len && p[i] == '.') {
> > + double frac = 0.0, base = 0.1;
> > + i++;
> > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> > + frac += base * (p[i] - '0');
> > + base /= 10.0;
> > + }
> > + d += frac * sign;
> > + }
> > +
> > + // Exponential
> > + if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> > + int j, exp = 0, minus = 0;
> > + i++;
> > + if (i < len && p[i] == '-') minus = 1, i++;
> > + if (i < len && p[i] == '+') i++;
> > + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> > + exp = exp * 10 + (p[i++] - '0');
> > + if (minus) exp = -exp;
> > + for (j = 0; j < exp; j++) d *= 10.0;
> > + for (j = 0; j < -exp; j++) d /= 10.0;
> > + }
> > +
> > + if (numlen != NULL) *numlen = i;
> > + return d;
> > +}
> > +
> > +// Iterate over object or array elements
> > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
> > + struct mg_str *val) {
> > + if (ofs >= obj.len) {
> > + ofs = 0; // Out of boundaries, stop scanning
> > + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
> > + ofs = 0; // Not an array or object, stop
> > + } else {
> > + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
> > + if (ofs == 0) ofs++, sub.buf++, sub.len--;
> > + if (*obj.buf == '[') { // Iterate over an array
> > + int n = 0, o = mg_json_get(sub, "$", &n);
> > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> > + ofs = 0; // Error parsing key, stop scanning
> > + } else {
> > + if (key) *key = mg_str_n(NULL, 0);
> > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> > + }
> > + } else { // Iterate over an object
> > + int n = 0, o = mg_json_get(sub, "$", &n);
> > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> > + ofs = 0; // Error parsing key, stop scanning
> > + } else {
> > + if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
> > + sub.buf += o + n, sub.len -= (size_t) (o + n);
> > + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
> > + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
> > + n = 0, o = mg_json_get(sub, "$", &n);
> > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> > + ofs = 0; // Error parsing value, stop scanning
> > + } else {
> > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> > + }
> > + }
> > + }
> > + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
> > + while (ofs && ofs < obj.len &&
> > + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
> > + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
> > + ofs++;
> > + }
> > + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
> > + if (ofs > obj.len) ofs = 0;
> > + }
> > + return ofs;
> > +}
> > +
> > +int mg_json_get(struct mg_str json, const char *path, int *toklen) {
> > + const char *s = json.buf;
> > + int len = (int) json.len;
> > + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
> > + unsigned char nesting[MG_JSON_MAX_DEPTH];
> > + int i = 0; // Current offset in `s`
> > + int j = 0; // Offset in `s` we're looking for (return value)
> > + int depth = 0; // Current depth (nesting level)
> > + int ed = 0; // Expected depth
> > + int pos = 1; // Current position in `path`
> > + int ci = -1, ei = -1; // Current and expected index in array
> > +
> > + if (toklen) *toklen = 0;
> > + if (path[0] != '$') return MG_JSON_INVALID;
> > +
> > +#define MG_CHECKRET(x) \
> > + do { \
> > + if (depth == ed && path[pos] == '\0' && ci == ei) { \
> > + if (toklen) *toklen = i - j + 1; \
> > + return j; \
> > + } \
> > + } while (0)
> > +
> > +// In the ascii table, the distance between `[` and `]` is 2.
> > +// Ditto for `{` and `}`. Hence +2 in the code below.
> > +#define MG_EOO(x) \
> > + do { \
> > + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
> > + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> > + depth--; \
> > + MG_CHECKRET(x); \
> > + } while (0)
> > +
> > + for (i = 0; i < len; i++) {
> > + unsigned char c = ((unsigned char *) s)[i];
> > + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> > + switch (expecting) {
> > + case S_VALUE:
> > + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
> > + if (depth == ed) j = i;
> > + if (c == '{') {
> > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> > + if (depth == ed && path[pos] == '.' && ci == ei) {
> > + // If we start the object, reset array indices
> > + ed++, pos++, ci = ei = -1;
> > + }
> > + nesting[depth++] = c;
> > + expecting = S_KEY;
> > + break;
> > + } else if (c == '[') {
> > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> > + if (depth == ed && path[pos] == '[' && ei == ci) {
> > + ed++, pos++, ci = 0;
> > + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> > + ei *= 10;
> > + ei += path[pos] - '0';
> > + }
> > + if (path[pos] != 0) pos++;
> > + }
> > + nesting[depth++] = c;
> > + break;
> > + } else if (c == ']' && depth > 0) { // Empty array
> > + MG_EOO(']');
> > + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
> > + i += 3;
> > + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
> > + i += 3;
> > + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
> > + i += 4;
> > + } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> > + int numlen = 0;
> > + mg_atod(&s[i], len - i, &numlen);
> > + i += numlen - 1;
> > + } else if (c == '"') {
> > + int n = mg_pass_string(&s[i + 1], len - i - 1);
> > + if (n < 0) return n;
> > + i += n + 1;
> > + } else {
> > + return MG_JSON_INVALID;
> > + }
> > + MG_CHECKRET('V');
> > + if (depth == ed && ei >= 0) ci++;
> > + expecting = S_COMMA_OR_EOO;
> > + break;
> > +
> > + case S_KEY:
> > + if (c == '"') {
> > + int n = mg_pass_string(&s[i + 1], len - i - 1);
> > + if (n < 0) return n;
> > + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> > + if (depth < ed) return MG_JSON_NOT_FOUND;
> > + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> > + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n,
> > + // &s[i + 1], n, depth, ed, ci, ei);
> > + // NOTE(cpq): in the check sequence below is important.
> > + // strncmp() must go first: it fails fast if the remaining length
> > + // of the path is smaller than `n`.
> > + if (depth == ed && path[pos - 1] == '.' &&
> > + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> > + (path[pos + n] == '\0' || path[pos + n] == '.' ||
> > + path[pos + n] == '[')) {
> > + pos += n;
> > + }
> > + i += n + 1;
> > + expecting = S_COLON;
> > + } else if (c == '}') { // Empty object
> > + MG_EOO('}');
> > + expecting = S_COMMA_OR_EOO;
> > + if (depth == ed && ei >= 0) ci++;
> > + } else {
> > + return MG_JSON_INVALID;
> > + }
> > + break;
> > +
> > + case S_COLON:
> > + if (c == ':') {
> > + expecting = S_VALUE;
> > + } else {
> > + return MG_JSON_INVALID;
> > + }
> > + break;
> > +
> > + case S_COMMA_OR_EOO:
> > + if (depth <= 0) {
> > + return MG_JSON_INVALID;
> > + } else if (c == ',') {
> > + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> > + } else if (c == ']' || c == '}') {
> > + if (depth == ed && c == '}' && path[pos - 1] == '.')
> > + return MG_JSON_NOT_FOUND;
> > + if (depth == ed && c == ']' && path[pos - 1] == ',')
> > + return MG_JSON_NOT_FOUND;
> > + MG_EOO('O');
> > + if (depth == ed && ei >= 0) ci++;
> > + } else {
> > + return MG_JSON_INVALID;
> > + }
> > + break;
> > + }
> > + }
> > + return MG_JSON_NOT_FOUND;
> > +}
> > +
> > +struct mg_str mg_json_get_tok(struct mg_str json, const char *path) {
> > + int len = 0, ofs = mg_json_get(json, path, &len);
> > + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
> > + (size_t) (len < 0 ? 0 : len));
> > +}
> > +
> > +bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
> > + int n, toklen, found = 0;
> > + if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> > + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) {
> > + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
> > + found = 1;
> > + }
> > + return found;
> > +}
> > +
> > +bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
> > + int found = 0, off = mg_json_get(json, path, NULL);
> > + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
> > + if (v != NULL) *v = json.buf[off] == 't';
> > + found = 1;
> > + }
> > + return found;
> > +}
> > +
> > +bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
> > + size_t i, j;
> > + for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
> > + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
> > + // \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
> > + // range. More complex chars would require dragging in a UTF8 library,
> > + // which is too much for us
> > + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
> > + sizeof(uint8_t)) == false)
> > + return false;
> > + i += 5;
> > + } else if (s.buf[i] == '\\' && i + 1 < s.len) {
> > + char c = json_esc(s.buf[i + 1], 0);
> > + if (c == 0) return false;
> > + to[j] = c;
> > + i++;
> > + } else {
> > + to[j] = s.buf[i];
> > + }
> > + }
> > + if (j >= n) return false;
> > + if (n > 0) to[j] = '\0';
> > + return true;
> > +}
> > +
> > +char *mg_json_get_str(struct mg_str json, const char *path) {
> > + char *result = NULL;
> > + int len = 0, off = mg_json_get(json, path, &len);
> > + if (off >= 0 && len > 1 && json.buf[off] == '"') {
> > + if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> > + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)),
> > + result, (size_t) len)) {
> > + free(result);
> > + result = NULL;
> > + }
> > + }
> > + return result;
> > +}
> > +
> > +char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
> > + char *result = NULL;
> > + int len = 0, off = mg_json_get(json, path, &len);
> > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> > + (result = (char *) calloc(1, (size_t) len)) != NULL) {
> > + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), 
> result,
> > + (size_t) len);
> > + if (slen != NULL) *slen = (int) k;
> > + }
> > + return result;
> > +}
> > +
> > +char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
> > + char *result = NULL;
> > + int len = 0, off = mg_json_get(json, path, &len);
> > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> > + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> > + int i;
> > + for (i = 0; i < len - 2; i += 2) {
> > + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1],
> > + sizeof(uint8_t));
> > + }
> > + result[len / 2 - 1] = '\0';
> > + if (slen != NULL) *slen = len / 2 - 1;
> > + }
> > + return result;
> > +}
> > +
> > +long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
> > + double dv;
> > + long result = dflt;
> > + if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> > + return result;
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/log.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +int mg_log_level = MG_LL_INFO;
> > +static mg_pfn_t s_log_func = mg_pfn_stdout;
> > +static void *s_log_func_param = NULL;
> > +
> > +void mg_log_set_fn(mg_pfn_t fn, void *param) {
> > + s_log_func = fn;
> > + s_log_func_param = param;
> > +}
> > +
> > +static void logc(unsigned char c) {
> > + s_log_func((char) c, s_log_func_param);
> > +}
> > +
> > +static void logs(const char *buf, size_t len) {
> > + size_t i;
> > + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> > +}
> > +
> > +#if MG_ENABLE_CUSTOM_LOG
> > +// Let user define their own mg_log_prefix() and mg_log()
> > +#else
> > +void mg_log_prefix(int level, const char *file, int line, const char 
> *fname) {
> > + const char *p = strrchr(file, '/');
> > + char buf[41];
> > + size_t n;
> > + if (p == NULL) p = strrchr(file, '\\');
> > + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), 
> level,
> > + p == NULL ? file : p + 1, line, fname);
> > + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> > + while (n < sizeof(buf)) buf[n++] = ' ';
> > + logs(buf, n - 1);
> > +}
> > +
> > +void mg_log(const char *fmt, ...) {
> > + va_list ap;
> > + va_start(ap, fmt);
> > + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> > + va_end(ap);
> > + logs("\r\n", 2);
> > +}
> > +#endif
> > +
> > +static unsigned char nibble(unsigned c) {
> > + return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> > +}
> > +
> > +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> > +void mg_hexdump(const void *buf, size_t len) {
> > + const unsigned char *p = (const unsigned char *) buf;
> > + unsigned char ascii[16], alen = 0;
> > + size_t i;
> > + for (i = 0; i < len; i++) {
> > + if ((i % 16) == 0) {
> > + // Print buffered ascii chars
> > + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 
> 0;
> > + // Print hex address, then \t
> > + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> > + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
> > + }
> > + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. 
> c5
> > + logc(' '); // Space after hex number
> > + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf
> > + }
> > + while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
> > + logs(" ", 2), logs((char *) ascii, 16), logc('\n');
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/md5.c"
> > +#endif
> > +
> > +
> > +
> > +// This code implements the MD5 message-digest algorithm.
> > +// The algorithm is due to Ron Rivest. This code was
> > +// written by Colin Plumb in 1993, no copyright is claimed.
> > +// This code is in the public domain; do with it what you wish.
> > +//
> > +// Equivalent code is available from RSA Data Security, Inc.
> > +// This code has been tested against that, and is equivalent,
> > +// except that you don't need to include two pages of legalese
> > +// with every copy.
> > +//
> > +// To compute the message digest of a chunk of bytes, declare an
> > +// MD5Context structure, pass it to MD5Init, call MD5Update as
> > +// needed on buffers full of bytes, and then call MD5Final, which
> > +// will fill a supplied 16-byte array with the digest.
> > +
> > +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> > +
> > +static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> > + if (MG_BIG_ENDIAN) {
> > + do {
> > + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> > + ((unsigned) buf[1] << 8 | buf[0]);
> > + *(uint32_t *) buf = t;
> > + buf += 4;
> > + } while (--longs);
> > + } else {
> > + (void) buf, (void) longs; // Little endian. Do nothing
> > + }
> > +}
> > +
> > +#define F1(x, y, z) (z ^ (x & (y ^ z)))
> > +#define F2(x, y, z) F1(z, x, y)
> > +#define F3(x, y, z) (x ^ y ^ z)
> > +#define F4(x, y, z) (y ^ (x | ~z))
> > +
> > +#define MD5STEP(f, w, x, y, z, data, s) \
> > + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> > +
> > +/*
> > + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
> > + * initialization constants.
> > + */
> > +void mg_md5_init(mg_md5_ctx *ctx) {
> > + ctx->buf[0] = 0x67452301;
> > + ctx->buf[1] = 0xefcdab89;
> > + ctx->buf[2] = 0x98badcfe;
> > + ctx->buf[3] = 0x10325476;
> > +
> > + ctx->bits[0] = 0;
> > + ctx->bits[1] = 0;
> > +}
> > +
> > +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
> > + uint32_t a, b, c, d;
> > +
> > + a = buf[0];
> > + b = buf[1];
> > + c = buf[2];
> > + d = buf[3];
> > +
> > + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> > + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> > + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> > + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> > + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> > + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> > + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> > + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> > + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> > + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> > + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> > + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> > + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> > + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> > + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> > + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> > +
> > + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> > + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> > + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> > + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> > + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> > + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> > + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> > + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> > + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> > + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> > + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> > + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> > + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> > + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> > + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> > + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> > +
> > + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> > + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> > + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> > + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> > + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> > + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> > + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> > + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> > + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> > + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> > + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> > + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> > + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> > + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> > + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> > + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> > +
> > + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> > + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> > + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> > + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> > + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> > + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> > + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> > + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> > + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> > + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> > + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> > + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> > + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> > + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> > + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> > + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> > +
> > + buf[0] += a;
> > + buf[1] += b;
> > + buf[2] += c;
> > + buf[3] += d;
> > +}
> > +
> > +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t 
> len) {
> > + uint32_t t;
> > +
> > + t = ctx->bits[0];
> > + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
> > + ctx->bits[1] += (uint32_t) len >> 29;
> > +
> > + t = (t >> 3) & 0x3f;
> > +
> > + if (t) {
> > + unsigned char *p = (unsigned char *) ctx->in + t;
> > +
> > + t = 64 - t;
> > + if (len < t) {
> > + memcpy(p, buf, len);
> > + return;
> > + }
> > + memcpy(p, buf, t);
> > + mg_byte_reverse(ctx->in, 16);
> > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > + buf += t;
> > + len -= t;
> > + }
> > +
> > + while (len >= 64) {
> > + memcpy(ctx->in, buf, 64);
> > + mg_byte_reverse(ctx->in, 16);
> > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > + buf += 64;
> > + len -= 64;
> > + }
> > +
> > + memcpy(ctx->in, buf, len);
> > +}
> > +
> > +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> > + unsigned count;
> > + unsigned char *p;
> > + uint32_t *a;
> > +
> > + count = (ctx->bits[0] >> 3) & 0x3F;
> > +
> > + p = ctx->in + count;
> > + *p++ = 0x80;
> > + count = 64 - 1 - count;
> > + if (count < 8) {
> > + memset(p, 0, count);
> > + mg_byte_reverse(ctx->in, 16);
> > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > + memset(ctx->in, 0, 56);
> > + } else {
> > + memset(p, 0, count - 8);
> > + }
> > + mg_byte_reverse(ctx->in, 14);
> > +
> > + a = (uint32_t *) ctx->in;
> > + a[14] = ctx->bits[0];
> > + a[15] = ctx->bits[1];
> > +
> > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > + mg_byte_reverse((unsigned char *) ctx->buf, 4);
> > + memcpy(digest, ctx->buf, 16);
> > + memset((char *) ctx, 0, sizeof(*ctx));
> > +}
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/mqtt.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +#define MQTT_CLEAN_SESSION 0x02
> > +#define MQTT_HAS_WILL 0x04
> > +#define MQTT_WILL_RETAIN 0x20
> > +#define MQTT_HAS_PASSWORD 0x40
> > +#define MQTT_HAS_USER_NAME 0x80
> > +
> > +struct mg_mqtt_pmap {
> > + uint8_t id;
> > + uint8_t type;
> > +};
> > +
> > +static const struct mg_mqtt_pmap s_prop_map[] = {
> > + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> > + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
> > + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> > + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
> > + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> > + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
> > + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
> > + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> > + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> > + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
> > + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
> > + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
> > + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> > + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
> > +
> > +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t 
> flags,
> > + uint32_t len) {
> > + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> > + buf[0] = (uint8_t) ((cmd << 4) | flags);
> > + do {
> > + *vlen = len % 0x80;
> > + len /= 0x80;
> > + if (len > 0) *vlen |= 0x80;
> > + vlen++;
> > + } while (len > 0 && vlen < &buf[sizeof(buf)]);
> > + mg_send(c, buf, (size_t) (vlen - buf));
> > +}
> > +
> > +static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> > + mg_send(c, &value, sizeof(value));
> > +}
> > +
> > +static void mg_send_u32(struct mg_connection *c, uint32_t value) {
> > + mg_send(c, &value, sizeof(value));
> > +}
> > +
> > +static uint8_t varint_size(size_t length) {
> > + uint8_t bytes_needed = 0;
> > + do {
> > + bytes_needed++;
> > + length /= 0x80;
> > + } while (length > 0);
> > + return bytes_needed;
> > +}
> > +
> > +static size_t encode_varint(uint8_t *buf, size_t value) {
> > + size_t len = 0;
> > +
> > + do {
> > + uint8_t b = (uint8_t) (value % 128);
> > + value /= 128;
> > + if (value > 0) b |= 0x80;
> > + buf[len++] = b;
> > + } while (value > 0);
> > +
> > + return len;
> > +}
> > +
> > +static size_t decode_varint(const uint8_t *buf, size_t len, size_t 
> *value) {
> > + size_t multiplier = 1, offset;
> > + *value = 0;
> > +
> > + for (offset = 0; offset < 4 && offset < len; offset++) {
> > + uint8_t encoded_byte = buf[offset];
> > + *value += (encoded_byte & 0x7f) * multiplier;
> > + multiplier *= 128;
> > +
> > + if ((encoded_byte & 0x80) == 0) return offset + 1;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int mqtt_prop_type_by_id(uint8_t prop_id) {
> > + size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]);
> > + for (i = 0; i < num_properties; ++i) {
> > + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
> > + }
> > + return -1; // Property ID not found
> > +}
> > +
> > +// Returns the size of the properties section, without the
> > +// size of the content's length
> > +static size_t get_properties_length(struct mg_mqtt_prop *props, size_t 
> count) {
> > + size_t i, size = 0;
> > + for (i = 0; i < count; i++) {
> > + size++; // identifier
> > + switch (mqtt_prop_type_by_id(props[i].id)) {
> > + case MQTT_PROP_TYPE_STRING_PAIR:
> > + size += (uint32_t) (props[i].val.len + props[i].key.len +
> > + 2 * sizeof(uint16_t));
> > + break;
> > + case MQTT_PROP_TYPE_STRING:
> > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> > + break;
> > + case MQTT_PROP_TYPE_BINARY_DATA:
> > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> > + break;
> > + case MQTT_PROP_TYPE_VARIABLE_INT:
> > + size += varint_size((uint32_t) props[i].iv);
> > + break;
> > + case MQTT_PROP_TYPE_INT:
> > + size += (uint32_t) sizeof(uint32_t);
> > + break;
> > + case MQTT_PROP_TYPE_SHORT:
> > + size += (uint32_t) sizeof(uint16_t);
> > + break;
> > + case MQTT_PROP_TYPE_BYTE:
> > + size += (uint32_t) sizeof(uint8_t);
> > + break;
> > + default:
> > + return size; // cannot parse further down
> > + }
> > + }
> > +
> > + return size;
> > +}
> > +
> > +// returns the entire size of the properties section, including the
> > +// size of the variable length of the content
> > +static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) {
> > + size_t size = get_properties_length(props, count);
> > + size += varint_size(size);
> > + return size;
> > +}
> > +
> > +static void mg_send_mqtt_properties(struct mg_connection *c,
> > + struct mg_mqtt_prop *props, size_t nprops) {
> > + size_t total_size = get_properties_length(props, nprops);
> > + uint8_t buf_v[4] = {0, 0, 0, 0};
> > + uint8_t buf[4] = {0, 0, 0, 0};
> > + size_t i, len = encode_varint(buf, total_size);
> > +
> > + mg_send(c, buf, (size_t) len);
> > + for (i = 0; i < nprops; i++) {
> > + mg_send(c, &props[i].id, sizeof(props[i].id));
> > + switch (mqtt_prop_type_by_id(props[i].id)) {
> > + case MQTT_PROP_TYPE_STRING_PAIR:
> > + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
> > + mg_send(c, props[i].key.buf, props[i].key.len);
> > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> > + mg_send(c, props[i].val.buf, props[i].val.len);
> > + break;
> > + case MQTT_PROP_TYPE_BYTE:
> > + mg_send(c, &props[i].iv, sizeof(uint8_t));
> > + break;
> > + case MQTT_PROP_TYPE_SHORT:
> > + mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
> > + break;
> > + case MQTT_PROP_TYPE_INT:
> > + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
> > + break;
> > + case MQTT_PROP_TYPE_STRING:
> > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> > + mg_send(c, props[i].val.buf, props[i].val.len);
> > + break;
> > + case MQTT_PROP_TYPE_BINARY_DATA:
> > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> > + mg_send(c, props[i].val.buf, props[i].val.len);
> > + break;
> > + case MQTT_PROP_TYPE_VARIABLE_INT:
> > + len = encode_varint(buf_v, props[i].iv);
> > + mg_send(c, buf_v, (size_t) len);
> > + break;
> > + }
> > + }
> > +}
> > +
> > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct 
> mg_mqtt_prop *prop,
> > + size_t ofs) {
> > + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
> > + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
> > + size_t new_pos = ofs, len;
> > + prop->id = i[0];
> > +
> > + if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size)
> > + return 0;
> > + i++, new_pos++;
> > +
> > + switch (mqtt_prop_type_by_id(prop->id)) {
> > + case MQTT_PROP_TYPE_STRING_PAIR:
> > + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> > + prop->key.buf = (char *) i + 2;
> > + i += 2 + prop->key.len;
> > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> > + prop->val.buf = (char *) i + 2;
> > + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
> > + break;
> > + case MQTT_PROP_TYPE_BYTE:
> > + prop->iv = (uint8_t) i[0];
> > + new_pos++;
> > + break;
> > + case MQTT_PROP_TYPE_SHORT:
> > + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> > + new_pos += sizeof(uint16_t);
> > + break;
> > + case MQTT_PROP_TYPE_INT:
> > + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
> > + ((uint32_t) i[2] << 8) | i[3];
> > + new_pos += sizeof(uint32_t);
> > + break;
> > + case MQTT_PROP_TYPE_STRING:
> > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> > + prop->val.buf = (char *) i + 2;
> > + new_pos += 2 + prop->val.len;
> > + break;
> > + case MQTT_PROP_TYPE_BINARY_DATA:
> > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> > + prop->val.buf = (char *) i + 2;
> > + new_pos += 2 + prop->val.len;
> > + break;
> > + case MQTT_PROP_TYPE_VARIABLE_INT:
> > + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
> > + new_pos = (!len) ? 0 : new_pos + len;
> > + break;
> > + default:
> > + new_pos = 0;
> > + }
> > +
> > + return new_pos;
> > +}
> > +
> > +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts) {
> > + char client_id[21];
> > + struct mg_str cid = opts->client_id;
> > + size_t total_len = 7 + 1 + 2 + 2;
> > + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> > +
> > + if (cid.len == 0) {
> > + mg_random_str(client_id, sizeof(client_id) - 1);
> > + client_id[sizeof(client_id) - 1] = '\0';
> > + cid = mg_str(client_id);
> > + }
> > +
> > + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1)
> > + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
> > + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags
> > + if (opts->user.len > 0) {
> > + total_len += 2 + (uint32_t) opts->user.len;
> > + hdr[7] |= MQTT_HAS_USER_NAME;
> > + }
> > + if (opts->pass.len > 0) {
> > + total_len += 2 + (uint32_t) opts->pass.len;
> > + hdr[7] |= MQTT_HAS_PASSWORD;
> > + }
> > + if (opts->topic.len > 0) { // allow zero-length msgs, message.len is 
> size_t
> > + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) 
> opts->message.len;
> > + hdr[7] |= MQTT_HAS_WILL;
> > + }
> > + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> > + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
> > + total_len += (uint32_t) cid.len;
> > + if (c->is_mqtt5) {
> > + total_len += get_props_size(opts->props, opts->num_props);
> > + if (hdr[7] & MQTT_HAS_WILL)
> > + total_len += get_props_size(opts->will_props, opts->num_will_props);
> > + }
> > +
> > + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
> > + mg_send(c, hdr, sizeof(hdr));
> > + // keepalive == 0 means "do not disconnect us!"
> > + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> > +
> > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, 
> opts->num_props);
> > +
> > + mg_send_u16(c, mg_htons((uint16_t) cid.len));
> > + mg_send(c, cid.buf, cid.len);
> > +
> > + if (hdr[7] & MQTT_HAS_WILL) {
> > + if (c->is_mqtt5)
> > + mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props);
> > +
> > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> > + mg_send(c, opts->topic.buf, opts->topic.len);
> > + mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
> > + mg_send(c, opts->message.buf, opts->message.len);
> > + }
> > + if (opts->user.len > 0) {
> > + mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> > + mg_send(c, opts->user.buf, opts->user.len);
> > + }
> > + if (opts->pass.len > 0) {
> > + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> > + mg_send(c, opts->pass.buf, opts->pass.len);
> > + }
> > +}
> > +
> > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts) {
> > + uint16_t id = opts->retransmit_id;
> > + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 
> : 0));
> > + size_t len = 2 + opts->topic.len + opts->message.len;
> > + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
> > + (char *) opts->topic.buf, (int) opts->message.len,
> > + (char *) opts->message.buf));
> > + if (opts->qos > 0) len += 2;
> > + if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props);
> > +
> > + if (opts->qos > 0 && id != 0) flags |= 1 << 3;
> > + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
> > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> > + mg_send(c, opts->topic.buf, opts->topic.len);
> > + if (opts->qos > 0) { // need to send 'id' field
> > + if (id == 0) { // generate new one if not resending
> > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> > + id = c->mgr->mqtt_id;
> > + }
> > + mg_send_u16(c, mg_htons(id));
> > + }
> > +
> > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, 
> opts->num_props);
> > +
> > + if (opts->message.len > 0) mg_send(c, opts->message.buf, 
> opts->message.len);
> > + return id;
> > +}
> > +
> > +void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts) {
> > + uint8_t qos_ = opts->qos & 3;
> > + size_t plen = c->is_mqtt5 ? get_props_size(opts->props, 
> opts->num_props) : 0;
> > + size_t len = 2 + opts->topic.len + 2 + 1 + plen;
> > +
> > + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
> > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> > + mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, 
> opts->num_props);
> > +
> > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> > + mg_send(c, opts->topic.buf, opts->topic.len);
> > + mg_send(c, &qos_, sizeof(qos_));
> > +}
> > +
> > +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> > + struct mg_mqtt_message *m) {
> > + uint8_t lc = 0, *p, *end;
> > + uint32_t n = 0, len_len = 0;
> > +
> > + memset(m, 0, sizeof(*m));
> > + m->dgram.buf = (char *) buf;
> > + if (len < 2) return MQTT_INCOMPLETE;
> > + m->cmd = (uint8_t) (buf[0] >> 4);
> > + m->qos = (buf[0] >> 1) & 3;
> > +
> > + n = len_len = 0;
> > + p = (uint8_t *) buf + 1;
> > + while ((size_t) (p - buf) < len) {
> > + lc = *((uint8_t *) p++);
> > + n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> > + len_len++;
> > + if (!(lc & 0x80)) break;
> > + if (len_len >= 4) return MQTT_MALFORMED;
> > + }
> > + end = p + n;
> > + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> > + m->dgram.len = (size_t) (end - buf);
> > +
> > + switch (m->cmd) {
> > + case MQTT_CMD_CONNACK:
> > + if (end - p < 2) return MQTT_MALFORMED;
> > + m->ack = p[1];
> > + break;
> > + case MQTT_CMD_PUBACK:
> > + case MQTT_CMD_PUBREC:
> > + case MQTT_CMD_PUBREL:
> > + case MQTT_CMD_PUBCOMP:
> > + case MQTT_CMD_SUBSCRIBE:
> > + case MQTT_CMD_SUBACK:
> > + case MQTT_CMD_UNSUBSCRIBE:
> > + case MQTT_CMD_UNSUBACK:
> > + if (p + 2 > end) return MQTT_MALFORMED;
> > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > + p += 2;
> > + break;
> > + case MQTT_CMD_PUBLISH: {
> > + if (p + 2 > end) return MQTT_MALFORMED;
> > + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > + m->topic.buf = (char *) p + 2;
> > + p += 2 + m->topic.len;
> > + if (p > end) return MQTT_MALFORMED;
> > + if (m->qos > 0) {
> > + if (p + 2 > end) return MQTT_MALFORMED;
> > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > + p += 2;
> > + }
> > + if (p > end) return MQTT_MALFORMED;
> > + if (version == 5 && p + 2 < end) {
> > + len_len =
> > + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
> > + if (!len_len) return MQTT_MALFORMED;
> > + m->props_start = (size_t) (p + len_len - buf);
> > + p += len_len + m->props_size;
> > + }
> > + if (p > end) return MQTT_MALFORMED;
> > + m->data.buf = (char *) p;
> > + m->data.len = (size_t) (end - p);
> > + break;
> > + }
> > + default:
> > + break;
> > + }
> > + return MQTT_OK;
> > +}
> > +
> > +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_READ) {
> > + for (;;) {
> > + uint8_t version = c->is_mqtt5 ? 5 : 4;
> > + struct mg_mqtt_message mm;
> > + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> > + if (rc == MQTT_MALFORMED) {
> > + MG_ERROR(("%lu MQTT malformed message", c->id));
> > + c->is_closing = 1;
> > + break;
> > + } else if (rc == MQTT_OK) {
> > + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> > + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
> > + switch (mm.cmd) {
> > + case MQTT_CMD_CONNACK:
> > + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> > + if (mm.ack == 0) {
> > + MG_DEBUG(("%lu Connected", c->id));
> > + } else {
> > + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> > + c->is_closing = 1;
> > + }
> > + break;
> > + case MQTT_CMD_PUBLISH: {
> > + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> > + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
> > + if (mm.qos > 0) {
> > + uint16_t id = mg_ntohs(mm.id);
> > + uint32_t remaining_len = sizeof(id);
> > + if (c->is_mqtt5) remaining_len += 2; // 3.4.2
> > +
> > + mg_mqtt_send_header(
> > + c,
> > + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
> > + 0, remaining_len);
> > + mg_send(c, &id, sizeof(id));
> > +
> > + if (c->is_mqtt5) {
> > + uint16_t zero = 0;
> > + mg_send(c, &zero, sizeof(zero));
> > + }
> > + }
> > + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff
> > + break;
> > + }
> > + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc
> > + uint16_t id = mg_ntohs(mm.id);
> > + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1
> > + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
> > + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2
> > + break;
> > + }
> > + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc
> > + uint16_t id = mg_ntohs(mm.id);
> > + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1
> > + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
> > + mg_send(c, &id, sizeof(id));
> > + break;
> > + }
> > + }
> > + mg_call(c, MG_EV_MQTT_CMD, &mm);
> > + mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> > + } else {
> > + break;
> > + }
> > + }
> > + }
> > + (void) ev_data;
> > +}
> > +
> > +void mg_mqtt_ping(struct mg_connection *nc) {
> > + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> > +}
> > +
> > +void mg_mqtt_pong(struct mg_connection *nc) {
> > + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> > +}
> > +
> > +void mg_mqtt_disconnect(struct mg_connection *c,
> > + const struct mg_mqtt_opts *opts) {
> > + size_t len = 0;
> > + if (c->is_mqtt5) len = 1 + get_props_size(opts->props, 
> opts->num_props);
> > + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
> > +
> > + if (c->is_mqtt5) {
> > + uint8_t zero = 0;
> > + mg_send(c, &zero, sizeof(zero)); // reason code
> > + mg_send_mqtt_properties(c, opts->props, opts->num_props);
> > + }
> > +}
> > +
> > +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char 
> *url,
> > + const struct mg_mqtt_opts *opts,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> > + if (c != NULL) {
> > + struct mg_mqtt_opts empty;
> > + memset(&empty, 0, sizeof(empty));
> > + mg_mqtt_login(c, opts == NULL ? &empty : opts);
> > + c->pfn = mqtt_cb;
> > + }
> > + return c;
> > +}
> > +
> > +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char 
> *url,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> > + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> > + return c;
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/net.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list 
> *ap) {
> > + size_t old = c->send.len;
> > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> > + return c->send.len - old;
> > +}
> > +
> > +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> > + size_t len = 0;
> > + va_list ap;
> > + va_start(ap, fmt);
> > + len = mg_vprintf(c, fmt, &ap);
> > + va_end(ap);
> > + return len;
> > +}
> > +
> > +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> > + uint32_t localhost = mg_htonl(0x7f000001);
> > + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
> > + memcpy(addr->ip, &localhost, sizeof(uint32_t));
> > + addr->is_ip6 = false;
> > + return true;
> > +}
> > +
> > +static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> > + if (str.len > 0) return false;
> > + memset(addr->ip, 0, sizeof(addr->ip));
> > + addr->is_ip6 = false;
> > + return true;
> > +}
> > +
> > +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> > + uint8_t data[4] = {0, 0, 0, 0};
> > + size_t i, num_dots = 0;
> > + for (i = 0; i < str.len; i++) {
> > + if (str.buf[i] >= '0' && str.buf[i] <= '9') {
> > + int octet = data[num_dots] * 10 + (str.buf[i] - '0');
> > + if (octet > 255) return false;
> > + data[num_dots] = (uint8_t) octet;
> > + } else if (str.buf[i] == '.') {
> > + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false;
> > + num_dots++;
> > + } else {
> > + return false;
> > + }
> > + }
> > + if (num_dots != 3 || str.buf[i - 1] == '.') return false;
> > + memcpy(&addr->ip, data, sizeof(data));
> > + addr->is_ip6 = false;
> > + return true;
> > +}
> > +
> > +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> > + int i;
> > + uint32_t ipv4;
> > + if (str.len < 14) return false;
> > + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') 
> return false;
> > + for (i = 2; i < 6; i++) {
> > + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
> > + }
> > + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
> > + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false;
> > + memcpy(&ipv4, addr->ip, sizeof(ipv4));
> > + memset(addr->ip, 0, sizeof(addr->ip));
> > + addr->ip[10] = addr->ip[11] = 255;
> > + memcpy(&addr->ip[12], &ipv4, 4);
> > + addr->is_ip6 = true;
> > + return true;
> > +}
> > +
> > +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> > + size_t i, j = 0, n = 0, dc = 42;
> > + addr->scope_id = 0;
> > + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
> > + if (mg_v4mapped(str, addr)) return true;
> > + for (i = 0; i < str.len; i++) {
> > + if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
> > + (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
> > + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
> > + unsigned long val; // TODO(): This loops on chars, refactor
> > + if (i > j + 3) return false;
> > + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j]));
> > + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val));
> > + addr->ip[n] = (uint8_t) ((val >> 8) & 255);
> > + addr->ip[n + 1] = (uint8_t) (val & 255);
> > + } else if (str.buf[i] == ':') {
> > + j = i + 1;
> > + if (i > 0 && str.buf[i - 1] == ':') {
> > + dc = n; // Double colon
> > + if (i > 1 && str.buf[i - 2] == ':') return false;
> > + } else if (i > 0) {
> > + n += 2;
> > + }
> > + if (n > 14) return false;
> > + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing ::
> > + } else if (str.buf[i] == '%') { // Scope ID, last in string
> > + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10,
> > + &addr->scope_id, sizeof(uint8_t));
> > + } else {
> > + return false;
> > + }
> > + }
> > + if (n < 14 && dc == 42) return false;
> > + if (n < 14) {
> > + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
> > + memset(&addr->ip[dc], 0, 14 - n);
> > + }
> > +
> > + addr->is_ip6 = true;
> > + return true;
> > +}
> > +
> > +bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> > + // MG_INFO(("[%.*s]", (int) str.len, str.buf));
> > + return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, 
> addr) ||
> > + mg_aton6(str, addr);
> > +}
> > +
> > +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> > + struct mg_connection *c =
> > + (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
> > + if (c != NULL) {
> > + c->mgr = mgr;
> > + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
> > + c->id = ++mgr->nextid;
> > + MG_PROF_INIT(c);
> > + }
> > + return c;
> > +}
> > +
> > +void mg_close_conn(struct mg_connection *c) {
> > + mg_resolve_cancel(c); // Close any pending DNS query
> > + LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> > + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> > + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> > + // Order of operations is important. `MG_EV_CLOSE` event must be fired
> > + // before we deallocate received data, see #1331
> > + mg_call(c, MG_EV_CLOSE, NULL);
> > + MG_DEBUG(("%lu %ld closed", c->id, c->fd));
> > + MG_PROF_DUMP(c);
> > + MG_PROF_FREE(c);
> > +
> > + mg_tls_free(c);
> > + mg_iobuf_free(&c->recv);
> > + mg_iobuf_free(&c->send);
> > + mg_iobuf_free(&c->rtls);
> > + mg_bzero((unsigned char *) c, sizeof(*c));
> > + free(c);
> > +}
> > +
> > +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = NULL;
> > + if (url == NULL || url[0] == '\0') {
> > + MG_ERROR(("null url"));
> > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> > + MG_ERROR(("OOM"));
> > + } else {
> > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > + c->is_udp = (strncmp(url, "udp:", 4) == 0);
> > + c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> > + c->fn = fn;
> > + c->is_client = true;
> > + c->fn_data = fn_data;
> > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> > + mg_call(c, MG_EV_OPEN, (void *) url);
> > + mg_resolve(c, url);
> > + }
> > + return c;
> > +}
> > +
> > +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = NULL;
> > + if ((c = mg_alloc_conn(mgr)) == NULL) {
> > + MG_ERROR(("OOM %s", url));
> > + } else if (!mg_open_listener(c, url)) {
> > + MG_ERROR(("Failed: %s, errno %d", url, errno));
> > + MG_PROF_FREE(c);
> > + free(c);
> > + c = NULL;
> > + } else {
> > + c->is_listening = 1;
> > + c->is_udp = strncmp(url, "udp:", 4) == 0;
> > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > + c->fn = fn;
> > + c->fn_data = fn_data;
> > + mg_call(c, MG_EV_OPEN, NULL);
> > + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must
> > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> > + }
> > + return c;
> > +}
> > +
> > +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> > + mg_event_handler_t fn, void *fn_data) {
> > + struct mg_connection *c = mg_alloc_conn(mgr);
> > + if (c != NULL) {
> > + c->fd = (void *) (size_t) fd;
> > + c->fn = fn;
> > + c->fn_data = fn_data;
> > + MG_EPOLL_ADD(c);
> > + mg_call(c, MG_EV_OPEN, NULL);
> > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > + }
> > + return c;
> > +}
> > +
> > +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
> > + unsigned flags, void (*fn)(void *), void *arg) {
> > + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> > + if (t != NULL) {
> > + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> > + t->id = mgr->timerid++;
> > + }
> > + return t;
> > +}
> > +
> > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> > + if (c->rtls.len == 0) return MG_IO_WAIT;
> > + if (len > c->rtls.len) len = c->rtls.len;
> > + memcpy(buf, c->rtls.buf, len);
> > + mg_iobuf_del(&c->rtls, 0, len);
> > + return (long) len;
> > +}
> > +
> > +void mg_mgr_free(struct mg_mgr *mgr) {
> > + struct mg_connection *c;
> > + struct mg_timer *tmp, *t = mgr->timers;
> > + while (t != NULL) tmp = t->next, free(t), t = tmp;
> > + mgr->timers = NULL; // Important. Next call to poll won't touch timers
> > + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> > + mg_mgr_poll(mgr, 0);
> > +#if MG_ENABLE_FREERTOS_TCP
> > + FreeRTOS_DeleteSocketSet(mgr->ss);
> > +#endif
> > + MG_DEBUG(("All connections closed"));
> > +#if MG_ENABLE_EPOLL
> > + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> > +#endif
> > + mg_tls_ctx_free(mgr);
> > +}
> > +
> > +void mg_mgr_init(struct mg_mgr *mgr) {
> > + memset(mgr, 0, sizeof(*mgr));
> > +#if MG_ENABLE_EPOLL
> > + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
> > + MG_ERROR(("epoll_create1 errno %d", errno));
> > +#else
> > + mgr->epoll_fd = -1;
> > +#endif
> > +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > + // clang-format off
> > + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> > + // clang-format on
> > +#elif MG_ENABLE_FREERTOS_TCP
> > + mgr->ss = FreeRTOS_CreateSocketSet();
> > +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> > + // Ignore SIGPIPE signal, so if client cancels the request, it
> > + // won't kill the whole process.
> > + signal(SIGPIPE, SIG_IGN);
> > +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
> > + MG_TCPIP_DRIVER_INIT(mgr);
> > +#endif
> > + mgr->pipe = MG_INVALID_SOCKET;
> > + mgr->dnstimeout = 3000;
> > + mgr->dns4.url = "udp://8.8.8.8:53";
> > + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> > + mg_tls_ctx_init(mgr);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/net_builtin.c"
> > +#endif
> > +
> > +
> > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> > +#define MG_EPHEMERAL_PORT_BASE 32768
> > +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> > +
> > +#ifndef MIP_TCP_KEEPALIVE_MS
> > +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
> > +#endif
> > +
> > +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
> > +#define MIP_TCP_ARP_MS 100 // Timeout for ARP response
> > +#define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment
> > +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection
> > +#define MIP_TCP_WIN 6000 // TCP window size
> > +
> > +struct connstate {
> > + uint32_t seq, ack; // TCP seq/ack counters
> > + uint64_t timer; // TCP keep-alive / ACK timer
> > + uint32_t acked; // Last ACK-ed number
> > + size_t unacked; // Not acked bytes
> > + uint8_t mac[6]; // Peer MAC address
> > + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
> > +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send 
> keepalive
> > +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon
> > +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response
> > +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
> > +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the 
> connection
> > + uint8_t tmiss; // Number of keep-alive misses
> > + struct mg_iobuf raw; // For TLS only. Incoming raw data
> > +};
> > +
> > +#pragma pack(push, 1)
> > +
> > +struct lcp {
> > + uint8_t addr, ctrl, proto[2], code, id, len[2];
> > +};
> > +
> > +struct eth {
> > + uint8_t dst[6]; // Destination MAC address
> > + uint8_t src[6]; // Source MAC address
> > + uint16_t type; // Ethernet type
> > +};
> > +
> > +struct ip {
> > + uint8_t ver; // Version
> > + uint8_t tos; // Unused
> > + uint16_t len; // Length
> > + uint16_t id; // Unused
> > + uint16_t frag; // Fragmentation
> > +#define IP_FRAG_OFFSET_MSK 0xFF1F
> > +#define IP_MORE_FRAGS_MSK 0x20
> > + uint8_t ttl; // Time to live
> > + uint8_t proto; // Upper level protocol
> > + uint16_t csum; // Checksum
> > + uint32_t src; // Source IP
> > + uint32_t dst; // Destination IP
> > +};
> > +
> > +struct ip6 {
> > + uint8_t ver; // Version
> > + uint8_t opts[3]; // Options
> > + uint16_t len; // Length
> > + uint8_t proto; // Upper level protocol
> > + uint8_t ttl; // Time to live
> > + uint8_t src[16]; // Source IP
> > + uint8_t dst[16]; // Destination IP
> > +};
> > +
> > +struct icmp {
> > + uint8_t type;
> > + uint8_t code;
> > + uint16_t csum;
> > +};
> > +
> > +struct arp {
> > + uint16_t fmt; // Format of hardware address
> > + uint16_t pro; // Format of protocol address
> > + uint8_t hlen; // Length of hardware address
> > + uint8_t plen; // Length of protocol address
> > + uint16_t op; // Operation
> > + uint8_t sha[6]; // Sender hardware address
> > + uint32_t spa; // Sender protocol address
> > + uint8_t tha[6]; // Target hardware address
> > + uint32_t tpa; // Target protocol address
> > +};
> > +
> > +struct tcp {
> > + uint16_t sport; // Source port
> > + uint16_t dport; // Destination port
> > + uint32_t seq; // Sequence number
> > + uint32_t ack; // Acknowledgement number
> > + uint8_t off; // Data offset
> > + uint8_t flags; // TCP flags
> > +#define TH_FIN 0x01
> > +#define TH_SYN 0x02
> > +#define TH_RST 0x04
> > +#define TH_PUSH 0x08
> > +#define TH_ACK 0x10
> > +#define TH_URG 0x20
> > +#define TH_ECE 0x40
> > +#define TH_CWR 0x80
> > + uint16_t win; // Window
> > + uint16_t csum; // Checksum
> > + uint16_t urp; // Urgent pointer
> > +};
> > +
> > +struct udp {
> > + uint16_t sport; // Source port
> > + uint16_t dport; // Destination port
> > + uint16_t len; // UDP length
> > + uint16_t csum; // UDP checksum
> > +};
> > +
> > +struct dhcp {
> > + uint8_t op, htype, hlen, hops;
> > + uint32_t xid;
> > + uint16_t secs, flags;
> > + uint32_t ciaddr, yiaddr, siaddr, giaddr;
> > + uint8_t hwaddr[208];
> > + uint32_t magic;
> > + uint8_t options[32];
> > +};
> > +
> > +#pragma pack(pop)
> > +
> > +struct pkt {
> > + struct mg_str raw; // Raw packet data
> > + struct mg_str pay; // Payload data
> > + struct eth *eth;
> > + struct llc *llc;
> > + struct arp *arp;
> > + struct ip *ip;
> > + struct ip6 *ip6;
> > + struct icmp *icmp;
> > + struct tcp *tcp;
> > + struct udp *udp;
> > + struct dhcp *dhcp;
> > +};
> > +
> > +static void send_syn(struct mg_connection *c);
> > +
> > +static void mkpay(struct pkt *pkt, void *p) {
> > + pkt->pay =
> > + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) 
> p));
> > +}
> > +
> > +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> > + size_t i;
> > + const uint8_t *p = (const uint8_t *) buf;
> > + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
> > + return sum;
> > +}
> > +
> > +static uint16_t csumfin(uint32_t sum) {
> > + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> > + return mg_htons(~sum & 0xffff);
> > +}
> > +
> > +static uint16_t ipcsum(const void *buf, size_t len) {
> > + uint32_t sum = csumup(0, buf, len);
> > + return csumfin(sum);
> > +}
> > +
> > +static void settmout(struct mg_connection *c, uint8_t type) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS
> > + : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
> > + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
> > + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
> > + : MIP_TCP_KEEPALIVE_MS;
> > + s->timer = ifp->now + n;
> > + s->ttype = type;
> > + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> > +}
> > +
> > +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
> > + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
> > + if (n == len) ifp->nsent++;
> > + return n;
> > +}
> > +
> > +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
> > + struct eth *eth = (struct eth *) ifp->tx.buf;
> > + struct arp *arp = (struct arp *) (eth + 1);
> > + memset(eth->dst, 255, sizeof(eth->dst));
> > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
> > + eth->type = mg_htons(0x806);
> > + memset(arp, 0, sizeof(*arp));
> > + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> > + arp->plen = 4;
> > + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> > + memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> > + ether_output(ifp, PDIFF(eth, arp + 1));
> > +}
> > +
> > +static void onstatechange(struct mg_tcpip_if *ifp) {
> > + if (ifp->state == MG_TCPIP_STATE_READY) {
> > + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
> > + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
> > + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
> > + arp_ask(ifp, ifp->gw);
> > + } else if (ifp->state == MG_TCPIP_STATE_UP) {
> > + MG_ERROR(("Link up"));
> > + srand((unsigned int) mg_millis());
> > + } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
> > + MG_ERROR(("Link down"));
> > + }
> > +}
> > +
> > +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> > + uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
> > + size_t plen) {
> > + struct eth *eth = (struct eth *) ifp->tx.buf;
> > + struct ip *ip = (struct ip *) (eth + 1);
> > + memcpy(eth->dst, mac_dst, sizeof(eth->dst));
> > + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC
> > + eth->type = mg_htons(0x800);
> > + memset(ip, 0, sizeof(*ip));
> > + ip->ver = 0x45; // Version 4, header length 5 words
> > + ip->frag = 0x40; // Don't fragment
> > + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> > + ip->ttl = 64;
> > + ip->proto = proto;
> > + ip->src = ip_src;
> > + ip->dst = ip_dst;
> > + ip->csum = ipcsum(ip, sizeof(*ip));
> > + return ip;
> > +}
> > +
> > +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t 
> ip_src,
> > + uint16_t sport, uint32_t ip_dst, uint16_t dport,
> > + const void *buf, size_t len) {
> > + struct ip *ip =
> > + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
> > + struct udp *udp = (struct udp *) (ip + 1);
> > + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> > + udp->sport = sport;
> > + udp->dport = dport;
> > + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> > + udp->csum = 0;
> > + uint32_t cs = csumup(0, udp, sizeof(*udp));
> > + cs = csumup(cs, buf, len);
> > + cs = csumup(cs, &ip->src, sizeof(ip->src));
> > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> > + cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> > + udp->csum = csumfin(cs);
> > + memmove(udp + 1, buf, len);
> > + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> > + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + 
> len);
> > +}
> > +
> > +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t 
> ip_src,
> > + uint32_t ip_dst, uint8_t *opts, size_t optslen,
> > + bool ciaddr) {
> > + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
> > + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> > + dhcp.magic = mg_htonl(0x63825363);
> > + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> > + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> > + memcpy(&dhcp.options, opts, optslen);
> > + if (ciaddr) dhcp.ciaddr = ip_src;
> > + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
> > + sizeof(dhcp));
> > +}
> > +
> > +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> > +
> > +// RFC-2131 #4.3.6, #4.4.1
> > +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t 
> ip_req,
> > + uint32_t ip_srv) {
> > + uint8_t opts[] = {
> > + 53, 1, 3, // Type: DHCP request
> > + 55, 2, 1, 3, // GW and mask
> > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
> > + 54, 4, 0, 0, 0, 0, // DHCP server ID
> > + 50, 4, 0, 0, 0, 0, // Requested IP
> > + 255 // End of options
> > + };
> > + memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
> > + memcpy(opts + 20, &ip_req, sizeof(ip_req));
> > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), 
> false);
> > + MG_DEBUG(("DHCP req sent"));
> > +}
> > +
> > +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
> > +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t 
> *mac_dst,
> > + uint32_t ip_src, uint32_t ip_dst) {
> > + uint8_t opts[] = {
> > + 53, 1, 3, // Type: DHCP request
> > + 255 // End of options
> > + };
> > + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
> > + MG_DEBUG(("DHCP req sent"));
> > +}
> > +
> > +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
> > + uint8_t opts[] = {
> > + 53, 1, 1, // Type: DHCP discover
> > + 55, 2, 1, 3, // Parameters: ip, mask
> > + 255 // End of options
> > + };
> > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), 
> false);
> > + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac));
> > +}
> > +
> > +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt 
> *pkt,
> > + bool lsn) {
> > + struct mg_connection *c = NULL;
> > + for (c = mgr->conns; c != NULL; c = c->next) {
> > + if (c->is_arplooking && pkt->arp &&
> > + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
> > + break;
> > + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
> > + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> > + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> > + break;
> > + }
> > + return c;
> > +}
> > +
> > +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> > + // ARP request. Make a response, then send
> > + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
> > + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
> > + struct eth *eth = (struct eth *) ifp->tx.buf;
> > + struct arp *arp = (struct arp *) (eth + 1);
> > + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
> > + eth->type = mg_htons(0x806);
> > + *arp = *pkt->arp;
> > + arp->op = mg_htons(2);
> > + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> > + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> > + arp->tpa = pkt->arp->spa;
> > + arp->spa = ifp->ip;
> > + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, 
> mg_print_mac,
> > + &ifp->mac));
> > + ether_output(ifp, PDIFF(eth, arp + 1));
> > + } else if (pkt->arp->op == mg_htons(2)) {
> > + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) 
> return;
> > + if (pkt->arp->spa == ifp->gw) {
> > + // Got response for the GW ARP request. Set ifp->gwmac
> > + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
> > + } else {
> > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> > + if (c != NULL && c->is_arplooking) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
> > + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip,
> > + mg_print_mac, s->mac));
> > + c->is_arplooking = 0;
> > + send_syn(c);
> > + settmout(c, MIP_TTYPE_SYN);
> > + }
> > + }
> > + }
> > +}
> > +
> > +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + // MG_DEBUG(("ICMP %d", (int) len));
> > + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == 
> ifp->ip) {
> > + size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct 
> icmp);
> > + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> > + if (plen > space) plen = space;
> > + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
> > + sizeof(struct icmp) + plen);
> > + struct icmp *icmp = (struct icmp *) (ip + 1);
> > + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
> > + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX
> > + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> > + ether_output(ifp, hlen + plen);
> > + }
> > +}
> > +
> > +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
> > + uint8_t msgtype = 0, state = ifp->state;
> > + // perform size check first, then access fields
> > + uint8_t *p = pkt->dhcp->options,
> > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> > + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) 
> return;
> > + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9
> > + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask
> > + memcpy(&mask, p + 2, sizeof(mask));
> > + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW
> > + memcpy(&gw, p + 2, sizeof(gw));
> > + ip = pkt->dhcp->yiaddr;
> > + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
> > + memcpy(&lease, p + 2, sizeof(lease));
> > + lease = mg_ntohl(lease);
> > + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type
> > + msgtype = p[2];
> > + }
> > + p += p[1] + 2;
> > + }
> > + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
> > + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP
> > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
> > + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw 
> &&
> > + lease) { // DHCPOFFER
> > + // select IP, (4.4.1) (fallback to IP source addr on foul play)
> > + tx_dhcp_request_sel(ifp, ip,
> > + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
> > + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
> > + } else if (msgtype == 5) { // DHCPACK
> > + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an 
> IP
> > + ifp->lease_expire = ifp->now + lease * 1000;
> > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
> > + // assume DHCP server = router until ARP resolves
> > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> > + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> > + ifp->state = MG_TCPIP_STATE_READY; // BOUND state
> > + uint64_t rand;
> > + mg_random(&rand, sizeof(rand));
> > + srand((unsigned int) (rand + mg_millis()));
> > + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // 
> renew
> > + ifp->lease_expire = ifp->now + lease * 1000;
> > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
> > + } // TODO(): accept provided T1/T2 and store server IP for renewal 
> (4.4)
> > + }
> > + if (ifp->state != state) onstatechange(ifp);
> > +}
> > +
> > +// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> > +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + uint8_t op = 0, *p = pkt->dhcp->options,
> > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> > + // struct dhcp *req = pkt->dhcp;
> > + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> > + res.yiaddr = ifp->ip;
> > + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
> > + while (p + 1 < end && p[0] != 255) { // Parse options
> > + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
> > + op = p[2];
> > + }
> > + p += p[1] + 2;
> > + }
> > + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
> > + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK
> > + uint8_t opts[] = {
> > + 53, 1, msg, // Message type
> > + 1, 4, 0, 0, 0, 0, // Subnet mask
> > + 54, 4, 0, 0, 0, 0, // Server ID
> > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
> > + 51, 4, 255, 255, 255, 255, // Lease time
> > + 255 // End of options
> > + };
> > + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> > + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> > + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> > + memcpy(&res.options, opts, sizeof(opts));
> > + res.magic = pkt->dhcp->magic;
> > + res.xid = pkt->dhcp->xid;
> > + if (ifp->enable_get_gateway) {
> > + ifp->gw = res.yiaddr;
> > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> > + }
> > + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
> > + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
> > + }
> > +}
> > +
> > +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> > + if (c == NULL) {
> > + // No UDP listener on this port. Should send ICMP, but keep silent.
> > + } else {
> > + c->rem.port = pkt->udp->sport;
> > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> > + if (c->recv.len >= MG_MAX_RECV_SIZE) {
> > + mg_error(c, "max_recv_buf_size reached");
> > + } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> > + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> > + mg_error(c, "oom");
> > + } else {
> > + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
> > + c->recv.len += pkt->pay.len;
> > + mg_call(c, MG_EV_READ, &pkt->pay.len);
> > + }
> > + }
> > +}
> > +
> > +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, 
> uint32_t dst_ip,
> > + uint8_t flags, uint16_t sport, uint16_t dport,
> > + uint32_t seq, uint32_t ack, const void *buf, size_t len) {
> > +#if 0
> > + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460, SACK 
> permitted
> > + if (flags & TH_SYN) {
> > + // Handshake? Set MSS
> > + buf = opts;
> > + len = sizeof(opts);
> > + }
> > +#endif
> > + struct ip *ip =
> > + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
> > + struct tcp *tcp = (struct tcp *) (ip + 1);
> > + memset(tcp, 0, sizeof(*tcp));
> > + if (buf != NULL && len) memmove(tcp + 1, buf, len);
> > + tcp->sport = sport;
> > + tcp->dport = dport;
> > + tcp->seq = seq;
> > + tcp->ack = ack;
> > + tcp->flags = flags;
> > + tcp->win = mg_htons(MIP_TCP_WIN);
> > + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> > + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header size 28 
> bytes
> > +
> > + uint32_t cs = 0;
> > + uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> > + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 
> 255)};
> > + cs = csumup(cs, tcp, n);
> > + cs = csumup(cs, &ip->src, sizeof(ip->src));
> > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> > + cs = csumup(cs, pseudo, sizeof(pseudo));
> > + tcp->csum = csumfin(cs);
> > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, 
> &ip->src,
> > + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
> > + mg_ntohs(tcp->dport), tcp->flags, len));
> > + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
> > + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
> > +}
> > +
> > +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
> > + uint8_t flags, uint32_t seq, const void *buf,
> > + size_t len) {
> > + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> > + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport,
> > + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
> > + buf, len);
> > +}
> > +
> > +static struct mg_connection *accept_conn(struct mg_connection *lsn,
> > + struct pkt *pkt) {
> > + struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> > + if (c == NULL) {
> > + MG_ERROR(("OOM"));
> > + return NULL;
> > + }
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
> > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> > + settmout(c, MIP_TTYPE_KEEPALIVE);
> > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> > + c->rem.port = pkt->tcp->sport;
> > + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
> > + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> > + c->is_accepted = 1;
> > + c->is_hexdumping = lsn->is_hexdumping;
> > + c->pfn = lsn->pfn;
> > + c->loc = lsn->loc;
> > + c->pfn_data = lsn->pfn_data;
> > + c->fn = lsn->fn;
> > + c->fn_data = lsn->fn_data;
> > + mg_call(c, MG_EV_OPEN, NULL);
> > + mg_call(c, MG_EV_ACCEPT, NULL);
> > + return c;
> > +}
> > +
> > +static size_t trim_len(struct mg_connection *c, size_t len) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, 
> udp_h_len = 8;
> > + size_t max_headers_len =
> > + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
> > + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - 
> eth_h_len;
> > +
> > + // If the frame exceeds the available buffer, trim the length
> > + if (len + max_headers_len > ifp->tx.len) {
> > + len = ifp->tx.len - max_headers_len;
> > + }
> > + // Ensure the MTU isn't lower than the minimum allowed value
> > + if (ifp->mtu < min_mtu) {
> > + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
> > + ifp->mtu = (uint16_t) min_mtu;
> > + }
> > + // If the total packet size exceeds the MTU, trim the length
> > + if (len + max_headers_len - eth_h_len > ifp->mtu) {
> > + len = ifp->mtu - max_headers_len + eth_h_len;
> > + if (c->is_udp) {
> > + MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
> > + }
> > + }
> > +
> > + return len;
> > +}
> > +
> > +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + uint32_t dst_ip = *(uint32_t *) c->rem.ip;
> > + len = trim_len(c, len);
> > + if (c->is_udp) {
> > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, 
> len);
> > + } else {
> > + size_t sent =
> > + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
> > + mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
> > + if (sent == 0) {
> > + return MG_IO_WAIT;
> > + } else if (sent == (size_t) -1) {
> > + return MG_IO_ERR;
> > + } else {
> > + s->seq += (uint32_t) len;
> > + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> > + }
> > + }
> > + return (long) len;
> > +}
> > +
> > +static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf 
> *io) {
> > + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> > + if (n == MG_IO_ERR) {
> > + mg_error(c, "TLS recv error");
> > + } else if (n > 0) {
> > + // Decrypted successfully - trigger MG_EV_READ
> > + io->len += (size_t) n;
> > + mg_call(c, MG_EV_READ, &n);
> > + }
> > +}
> > +
> > +static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
> > + uint32_t seq = mg_ntohl(pkt->tcp->seq);
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + if (pkt->tcp->flags & TH_FIN) {
> > + // If we initiated the closure, we reply with ACK upon receiving FIN
> > + // If we didn't initiate it, we reply with FIN as part of the normal 
> TCP
> > + // closure process
> > + uint8_t flags = TH_ACK;
> > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
> > + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
> > + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
> > + s->seq++; // Yes. Increment our SEQ
> > + } else { // Otherwise,
> > + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
> > + }
> > + } else {
> > + flags |= TH_FIN;
> > + c->is_draining = 1;
> > + settmout(c, MIP_TTYPE_FIN);
> > + }
> > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
> > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> > + } else if (pkt->pay.len == 0) {
> > + // TODO(cpq): handle this peer's ACK
> > + } else if (seq != s->ack) {
> > + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> > + if (s->ack == ack) {
> > + MG_VERBOSE(("ignoring duplicate pkt"));
> > + } else {
> > + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
> > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
> > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
> > + 0);
> > + }
> > + } else if (io->size - io->len < pkt->pay.len &&
> > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> > + mg_error(c, "oom");
> > + } else {
> > + // Copy TCP payload into the IO buffer. If the connection is plain 
> text,
> > + // we copy to c->recv. If the connection is TLS, this data is 
> encrypted,
> > + // therefore we copy that encrypted data to the c->rtls iobuffer 
> instead,
> > + // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
> > + // call back mg_io_recv() which grabs raw data from c->rtls
> > + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
> > + io->len += pkt->pay.len;
> > +
> > + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), 
> s->ack));
> > + // Advance ACK counter
> > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> > + s->unacked += pkt->pay.len;
> > + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
> > + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
> > + // Send ACK immediately
> > + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
> > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
> > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL,
> > + 0);
> > + s->unacked = 0;
> > + s->acked = s->ack;
> > + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
> > + } else {
> > + // if not already running, setup a timer to send an ACK later
> > + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> > + }
> > +
> > + if (c->is_tls && c->is_tls_hs) {
> > + mg_tls_handshake(c);
> > + } else if (c->is_tls) {
> > + // TLS connection. Make room for decrypted data in c->recv
> > + io = &c->recv;
> > + if (io->size - io->len < pkt->pay.len &&
> > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> > + mg_error(c, "oom");
> > + } else {
> > + // Decrypt data directly into c->recv
> > + handle_tls_recv(c, io);
> > + }
> > + } else {
> > + // Plain text connection, data is already in c->recv, trigger
> > + // MG_EV_READ
> > + mg_call(c, MG_EV_READ, &pkt->pay.len);
> > + }
> > + }
> > +}
> > +
> > +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> > + struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
> > +#if 0
> > + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) 
> pkt->pay.len));
> > +#endif
> > + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | 
> TH_ACK)) {
> > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
> > + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> > + c->is_connecting = 0; // Client connected
> > + settmout(c, MIP_TTYPE_KEEPALIVE);
> > + mg_call(c, MG_EV_CONNECT, NULL); // Let user know
> > + } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) 
> {
> > + // mg_hexdump(pkt->raw.buf, pkt->raw.len);
> > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > + } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> > + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> > + } else if (c != NULL) {
> > +#if 0
> > + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
> > + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> > + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> > + mg_hexdump(pkt->pay.buf, pkt->pay.len);
> > +#endif
> > + s->tmiss = 0; // Reset missed keep-alive counter
> > + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
> > + settmout(c,
> > + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
> > + read_conn(c, pkt); // Override timer with ACK timeout if needed
> > + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > + } else if (pkt->tcp->flags & TH_RST) {
> > + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> > + // ignore RST if not connected
> > + } else if (pkt->tcp->flags & TH_SYN) {
> > + // Use peer's source port as ISN, in order to recognise the handshake
> > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> > + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> > + } else if (pkt->tcp->flags & TH_FIN) {
> > + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> > + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
> > + accept_conn(c, pkt);
> > + } else if (!c->is_accepted) { // no peer
> > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > + } else {
> > + // MG_VERBOSE(("dropped silently.."));
> > + }
> > +}
> > +
> > +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & 
> IP_FRAG_OFFSET_MSK) {
> > + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
> > + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
> > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> > + if (c) mg_error(c, "Received fragmented packet");
> > + } else if (pkt->ip->proto == 1) {
> > + pkt->icmp = (struct icmp *) (pkt->ip + 1);
> > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> > + mkpay(pkt, pkt->icmp + 1);
> > + rx_icmp(ifp, pkt);
> > + } else if (pkt->ip->proto == 17) {
> > + pkt->udp = (struct udp *) (pkt->ip + 1);
> > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
> > + mkpay(pkt, pkt->udp + 1);
> > + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
> > + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
> > + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
> > + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
> > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> > + mkpay(pkt, pkt->dhcp + 1);
> > + rx_dhcp_client(ifp, pkt);
> > + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) 
> {
> > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> > + mkpay(pkt, pkt->dhcp + 1);
> > + rx_dhcp_server(ifp, pkt);
> > + } else {
> > + rx_udp(ifp, pkt);
> > + }
> > + } else if (pkt->ip->proto == 6) {
> > + pkt->tcp = (struct tcp *) (pkt->ip + 1);
> > + if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> > + mkpay(pkt, pkt->tcp + 1);
> > + uint16_t iplen = mg_ntohs(pkt->ip->len);
> > + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 
> 4U));
> > + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
> > + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
> > + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
> > + rx_tcp(ifp, pkt);
> > + }
> > +}
> > +
> > +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> > + // MG_DEBUG(("IP %d", (int) len));
> > + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> > + pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> > + mkpay(pkt, pkt->icmp + 1);
> > + rx_icmp(ifp, pkt);
> > + } else if (pkt->ip6->proto == 17) {
> > + pkt->udp = (struct udp *) (pkt->ip6 + 1);
> > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
> > + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
> > + // mg_htons(udp->dport)));
> > + mkpay(pkt, pkt->udp + 1);
> > + }
> > +}
> > +
> > +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) 
> {
> > + struct pkt pkt;
> > + memset(&pkt, 0, sizeof(pkt));
> > + pkt.raw.buf = (char *) buf;
> > + pkt.raw.len = len;
> > + pkt.eth = (struct eth *) buf;
> > + // mg_hexdump(buf, len > 16 ? 16: len);
> > + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
> > + if (ifp->enable_mac_check &&
> > + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> > + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
> > + return;
> > + if (ifp->enable_crc32_check && len > 4) {
> > + len -= 4; // TODO(scaprile): check on bigendian
> > + uint32_t crc = mg_crc32(0, (const char *) buf, len);
> > + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return;
> > + }
> > + if (pkt.eth->type == mg_htons(0x806)) {
> > + pkt.arp = (struct arp *) (pkt.eth + 1);
> > + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // 
> Truncated
> > + rx_arp(ifp, &pkt);
> > + } else if (pkt.eth->type == mg_htons(0x86dd)) {
> > + pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // 
> Truncated
> > + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
> > + mkpay(&pkt, pkt.ip6 + 1);
> > + rx_ip6(ifp, &pkt);
> > + } else if (pkt.eth->type == mg_htons(0x800)) {
> > + pkt.ip = (struct ip *) (pkt.eth + 1);
> > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // 
> Truncated
> > + // Truncate frame to what IP header tells us
> > + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) 
> {
> > + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> > + }
> > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // 
> Truncated
> > + if ((pkt.ip->ver >> 4) != 4) return; // Not IP
> > + mkpay(&pkt, pkt.ip + 1);
> > + rx_ip(ifp, &pkt);
> > + } else {
> > + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
> > + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : 
> len);
> > + }
> > +}
> > +
> > +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
> > + struct mg_connection *c;
> > + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now);
> > + ifp->now = now;
> > +
> > +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> > + if (expired_1000ms) {
> > + const char *names[] = {"down", "up", "req", "ready"};
> > + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
> > + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
> > + ifp->ndrop, ifp->nerr));
> > + }
> > +#endif
> > + // Handle physical interface up/down status
> > + if (expired_1000ms && ifp->driver->up) {
> > + bool up = ifp->driver->up(ifp);
> > + bool current = ifp->state != MG_TCPIP_STATE_DOWN;
> > + if (up != current) {
> > + ifp->state = up == false ? MG_TCPIP_STATE_DOWN
> > + : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
> > + : MG_TCPIP_STATE_READY;
> > + if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> > + onstatechange(ifp);
> > + }
> > + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
> > + }
> > + if (ifp->state == MG_TCPIP_STATE_DOWN) return;
> > +
> > + // DHCP RFC-2131 (4.4)
> > + if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
> > + tx_dhcp_discover(ifp); // INIT (4.4.1)
> > + } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
> > + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
> > + if (ifp->now >= ifp->lease_expire) {
> > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP
> > + onstatechange(ifp);
> > + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
> > + ((ifp->now / 1000) % 60) == 0) {
> > + // hack: 30 min before deadline, try to rebind (4.3.6) every min
> > + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
> > + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
> > + }
> > +
> > + // Read data from the network
> > + if (ifp->driver->rx != NULL) { // Polling driver. We must call it
> > + size_t len =
> > + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
> > + if (len > 0) {
> > + ifp->nrecv++;
> > + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
> > + }
> > + } else { // Interrupt-based driver. Fills recv queue itself
> > + char *buf;
> > + size_t len = mg_queue_next(&ifp->recv_queue, &buf);
> > + if (len > 0) {
> > + mg_tcpip_rx(ifp, buf, len);
> > + mg_queue_del(&ifp->recv_queue, len);
> > + }
> > + }
> > +
> > + // Process timeouts
> > + for (c = ifp->mgr->conns; c != NULL; c = c->next) {
> > + if (c->is_udp || c->is_listening || c->is_resolving) continue;
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + if (now > s->timer) {
> > + if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
> > + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
> > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> > + s->acked = s->ack;
> > + } else if (s->ttype == MIP_TTYPE_ARP) {
> > + mg_error(c, "ARP timeout");
> > + } else if (s->ttype == MIP_TTYPE_SYN) {
> > + mg_error(c, "Connection timeout");
> > + } else if (s->ttype == MIP_TTYPE_FIN) {
> > + c->is_closing = 1;
> > + continue;
> > + } else {
> > + if (s->tmiss++ > 2) {
> > + mg_error(c, "keepalive");
> > + } else {
> > + MG_VERBOSE(("%lu keepalive", c->id));
> > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> > + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
> > + }
> > + }
> > +
> > + settmout(c, MIP_TTYPE_KEEPALIVE);
> > + }
> > + }
> > +}
> > +
> > +// This function executes in interrupt context, thus it should copy data
> > +// somewhere fast. Note that newlib's malloc is not thread safe, thus 
> use
> > +// our lock-free queue with preallocated buffer to copy data and return 
> asap
> > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) {
> > + char *p;
> > + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
> > + memcpy(p, buf, len);
> > + mg_queue_add(&ifp->recv_queue, len);
> > + ifp->nrecv++;
> > + } else {
> > + ifp->ndrop++;
> > + }
> > +}
> > +
> > +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
> > + // If MAC address is not set, make a random one
> > + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
> > + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
> > + ifp->mac[0] = 0x02; // Locally administered, unicast
> > + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
> > + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac));
> > + }
> > +
> > + if (ifp->driver->init && !ifp->driver->init(ifp)) {
> > + MG_ERROR(("driver init failed"));
> > + } else {
> > + size_t framesize = 1540;
> > + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize;
> > + if (ifp->recv_queue.size == 0)
> > + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
> > + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
> > + ifp->timer_1000ms = mg_millis();
> > + mgr->priv = ifp;
> > + ifp->mgr = mgr;
> > + ifp->mtu = MG_TCPIP_MTU_DEFAULT;
> > + mgr->extraconnsize = sizeof(struct connstate);
> > + if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> > + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
> > + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535
> > + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
> > + // MG_EPHEMERAL_PORT_BASE to 65535
> > + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) 
> MG_ERROR(("OOM"));
> > + }
> > +}
> > +
> > +void mg_tcpip_free(struct mg_tcpip_if *ifp) {
> > + free(ifp->recv_queue.buf);
> > + free(ifp->tx.buf);
> > +}
> > +
> > +static void send_syn(struct mg_connection *c) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, 
> NULL,
> > + 0);
> > +}
> > +
> > +void mg_connect_resolved(struct mg_connection *c) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + c->is_resolving = 0;
> > + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = 
> MG_EPHEMERAL_PORT_BASE;
> > + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
> > + c->loc.port = mg_htons(ifp->eport++);
> > + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, 
> mg_print_ip_port,
> > + &c->rem));
> > + mg_call(c, MG_EV_RESOLVE, NULL);
> > + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | 
> ~ifp->mask))) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast
> > + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) 
> {
> > + // If we're in the same LAN, fire an ARP lookup.
> > + MG_DEBUG(("%lu ARP lookup...", c->id));
> > + arp_ask(ifp, rem_ip);
> > + settmout(c, MIP_TTYPE_ARP);
> > + c->is_arplooking = 1;
> > + c->is_connecting = 1;
> > + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
> > + struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 
> to EF
> > + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group
> > + memcpy(s->mac, mcastp, 3);
> > + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb
> > + s->mac[3] &= 0x7F;
> > + } else {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
> > + if (c->is_udp) {
> > + mg_call(c, MG_EV_CONNECT, NULL);
> > + } else {
> > + send_syn(c);
> > + settmout(c, MIP_TTYPE_SYN);
> > + c->is_connecting = 1;
> > + }
> > + }
> > +}
> > +
> > +bool mg_open_listener(struct mg_connection *c, const char *url) {
> > + c->loc.port = mg_htons(mg_url_port(url));
> > + return true;
> > +}
> > +
> > +static void write_conn(struct mg_connection *c) {
> > + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> > + : mg_io_send(c, c->send.buf, c->send.len);
> > + if (len == MG_IO_ERR) {
> > + mg_error(c, "tx err");
> > + } else if (len > 0) {
> > + mg_iobuf_del(&c->send, 0, (size_t) len);
> > + mg_call(c, MG_EV_WRITE, &len);
> > + }
> > +}
> > +
> > +static void init_closure(struct mg_connection *c) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + if (c->is_udp == false && c->is_listening == false &&
> > + c->is_connecting == false) { // For TCP conns,
> > + struct mg_tcpip_if *ifp =
> > + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
> > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> > + settmout(c, MIP_TTYPE_FIN);
> > + }
> > +}
> > +
> > +static void close_conn(struct mg_connection *c) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + mg_iobuf_free(&s->raw); // For TLS connections, release raw data
> > + mg_close_conn(c);
> > +}
> > +
> > +static bool can_write(struct mg_connection *c) {
> > + return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 
> 0 &&
> > + c->is_tls_hs == 0 && c->is_arplooking == 0;
> > +}
> > +
> > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
> > + struct mg_connection *c, *tmp;
> > + uint64_t now = mg_millis();
> > + mg_timer_poll(&mgr->timers, now);
> > + if (ifp == NULL || ifp->driver == NULL) return;
> > + mg_tcpip_poll(ifp, now);
> > + for (c = mgr->conns; c != NULL; c = tmp) {
> > + tmp = c->next;
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + mg_call(c, MG_EV_POLL, &now);
> > + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> > + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> > + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> > + if (c->is_tls && mg_tls_pending(c) > 0)
> > + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
> > + if (can_write(c)) write_conn(c);
> > + if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN)
> > + init_closure(c);
> > + if (c->is_closing) close_conn(c);
> > + }
> > + (void) ms;
> > +}
> > +
> > +bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> > + bool res = false;
> > + uint32_t rem_ip;
> > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> > + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
> > + mg_error(c, "net down");
> > + } else if (c->is_udp) {
> > + struct connstate *s = (struct connstate *) (c + 1);
> > + len = trim_len(c, len); // Trimming length if necessary
> > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, 
> len);
> > + res = true;
> > + } else {
> > + res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> > + }
> > + return res;
> > +}
> > +#endif // MG_ENABLE_TCPIP
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/ota_dummy.c"
> > +#endif
> > +
> > +
> > +
> > +#if MG_OTA == MG_OTA_NONE
> > +bool mg_ota_begin(size_t new_firmware_size) {
> > + (void) new_firmware_size;
> > + return true;
> > +}
> > +bool mg_ota_write(const void *buf, size_t len) {
> > + (void) buf, (void) len;
> > + return true;
> > +}
> > +bool mg_ota_end(void) {
> > + return true;
> > +}
> > +bool mg_ota_commit(void) {
> > + return true;
> > +}
> > +bool mg_ota_rollback(void) {
> > + return true;
> > +}
> > +int mg_ota_status(int fw) {
> > + (void) fw;
> > + return 0;
> > +}
> > +uint32_t mg_ota_crc32(int fw) {
> > + (void) fw;
> > + return 0;
> > +}
> > +uint32_t mg_ota_timestamp(int fw) {
> > + (void) fw;
> > + return 0;
> > +}
> > +size_t mg_ota_size(int fw) {
> > + (void) fw;
> > + return 0;
> > +}
> > +MG_IRAM void mg_ota_boot(void) {
> > +}
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/ota_esp32.c"
> > +#endif
> > +
> > +
> > +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
> > +
> > +static const esp_partition_t *s_ota_update_partition;
> > +static esp_ota_handle_t s_ota_update_handle;
> > +static bool s_ota_success;
> > +
> > +// Those empty macros do nothing, but mark places in the code which 
> could
> > +// potentially trigger a watchdog reboot due to the log flash erase 
> operation
> > +#define disable_wdt()
> > +#define enable_wdt()
> > +
> > +bool mg_ota_begin(size_t new_firmware_size) {
> > + if (s_ota_update_partition != NULL) {
> > + MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
> > + return false;
> > + } else {
> > + s_ota_success = false;
> > + disable_wdt();
> > + s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
> > + esp_err_t err = esp_ota_begin(s_ota_update_partition, 
> new_firmware_size,
> > + &s_ota_update_handle);
> > + enable_wdt();
> > + MG_DEBUG(("esp_ota_begin(): %d", err));
> > + s_ota_success = (err == ESP_OK);
> > + }
> > + return s_ota_success;
> > +}
> > +
> > +bool mg_ota_write(const void *buf, size_t len) {
> > + disable_wdt();
> > + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
> > + enable_wdt();
> > + MG_INFO(("esp_ota_write(): %d", err));
> > + s_ota_success = err == ESP_OK;
> > + return s_ota_success;
> > +}
> > +
> > +bool mg_ota_end(void) {
> > + esp_err_t err = esp_ota_end(s_ota_update_handle);
> > + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
> > + if (s_ota_success && err == ESP_OK) {
> > + err = esp_ota_set_boot_partition(s_ota_update_partition);
> > + s_ota_success = (err == ESP_OK);
> > + }
> > + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
> > + s_ota_update_partition = NULL;
> > + return s_ota_success;
> > +}
> > +
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/ota_flash.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +// This OTA implementation uses the internal flash API outlined in 
> device.h
> > +// It splits flash into 2 equal partitions, and stores OTA status in the
> > +// last sector of the partition.
> > +
> > +#if MG_OTA == MG_OTA_FLASH
> > +
> > +#define MG_OTADATA_KEY 0xb07afed0
> > +
> > +static char *s_addr; // Current address to write to
> > +static size_t s_size; // Firmware size to flash. In-progress indicator
> > +static uint32_t s_crc32; // Firmware checksum
> > +
> > +struct mg_otadata {
> > + uint32_t crc32, size, timestamp, status;
> > +};
> > +
> > +bool mg_ota_begin(size_t new_firmware_size) {
> > + bool ok = false;
> > + if (s_size) {
> > + MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
> > + } else {
> > + size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
> > + s_crc32 = 0;
> > + s_addr = (char *) mg_flash_start() + half;
> > + MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
> > + if (new_firmware_size < max) {
> > + ok = true;
> > + s_size = new_firmware_size;
> > + MG_INFO(("Starting OTA, firmware size %lu", s_size));
> > + } else {
> > + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, 
> max));
> > + }
> > + }
> > + return ok;
> > +}
> > +
> > +bool mg_ota_write(const void *buf, size_t len) {
> > + bool ok = false;
> > + if (s_size == 0) {
> > + MG_ERROR(("OTA is not started, call mg_ota_begin()"));
> > + } else {
> > + size_t align = mg_flash_write_align();
> > + size_t len_aligned_down = MG_ROUND_DOWN(len, align);
> > + if (len_aligned_down) ok = mg_flash_write(s_addr, buf, 
> len_aligned_down);
> > + if (len_aligned_down < len) {
> > + size_t left = len - len_aligned_down;
> > + char tmp[align];
> > + memset(tmp, 0xff, sizeof(tmp));
> > + memcpy(tmp, (char *) buf + len_aligned_down, left);
> > + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
> > + }
> > + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
> > + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
> > + s_addr += len;
> > + }
> > + return ok;
> > +}
> > +
> > +MG_IRAM static uint32_t mg_fwkey(int fw) {
> > + uint32_t key = MG_OTADATA_KEY + fw;
> > + int bank = mg_flash_bank();
> > + if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
> > + if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
> > + return key;
> > +}
> > +
> > +bool mg_ota_end(void) {
> > + char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
> > + bool ok = false;
> > + if (s_size) {
> > + size_t size = s_addr - base;
> > + uint32_t crc32 = mg_crc32(0, base, s_size);
> > + if (size == s_size && crc32 == s_crc32) {
> > + uint32_t now = (uint32_t) (mg_now() / 1000);
> > + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
> > + uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
> > + ok = mg_flash_save(NULL, key, &od, sizeof(od));
> > + }
> > + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, 
> s_size,
> > + size, ok ? "ok" : "fail"));
> > + s_size = 0;
> > + if (ok) ok = mg_flash_swap_bank();
> > + }
> > + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
> > + return ok;
> > +}
> > +
> > +MG_IRAM static struct mg_otadata mg_otadata(int fw) {
> > + uint32_t key = mg_fwkey(fw);
> > + struct mg_otadata od = {};
> > + MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : 
> "prev"));
> > + mg_flash_load(NULL, key, &od, sizeof(od));
> > + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, 
> key));
> > + // mg_hexdump(&od, sizeof(od));
> > + return od;
> > +}
> > +
> > +int mg_ota_status(int fw) {
> > + struct mg_otadata od = mg_otadata(fw);
> > + return od.status;
> > +}
> > +uint32_t mg_ota_crc32(int fw) {
> > + struct mg_otadata od = mg_otadata(fw);
> > + return od.crc32;
> > +}
> > +uint32_t mg_ota_timestamp(int fw) {
> > + struct mg_otadata od = mg_otadata(fw);
> > + return od.timestamp;
> > +}
> > +size_t mg_ota_size(int fw) {
> > + struct mg_otadata od = mg_otadata(fw);
> > + return od.size;
> > +}
> > +
> > +MG_IRAM bool mg_ota_commit(void) {
> > + bool ok = true;
> > + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
> > + if (od.status != MG_OTA_COMMITTED) {
> > + od.status = MG_OTA_COMMITTED;
> > + MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
> > + ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, 
> sizeof(od));
> > + }
> > + return ok;
> > +}
> > +
> > +bool mg_ota_rollback(void) {
> > + MG_DEBUG(("Rolling firmware back"));
> > + if (mg_flash_bank() == 0) {
> > + // No dual bank support. Mark previous firmware as FIRST_BOOT
> > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> > + prev.status = MG_OTA_FIRST_BOOT;
> > + return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, 
> &prev,
> > + sizeof(prev));
> > + } else {
> > + return mg_flash_swap_bank();
> > + }
> > +}
> > +
> > +MG_IRAM void mg_ota_boot(void) {
> > + MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
> > + struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
> > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> > +
> > + if (curr.status == MG_OTA_FIRST_BOOT) {
> > + if (prev.status == MG_OTA_UNAVAILABLE) {
> > + MG_INFO(("Setting previous firmware state to committed"));
> > + prev.status = MG_OTA_COMMITTED;
> > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, 
> sizeof(prev));
> > + }
> > + curr.status = MG_OTA_UNCOMMITTED;
> > + MG_INFO(("First boot, setting status to UNCOMMITTED"));
> > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, 
> sizeof(curr));
> > + } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
> > + // Swap paritions. Pray power does not disappear
> > + size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
> > + char *partition1 = mg_flash_start();
> > + char *partition2 = mg_flash_start() + fs / 2;
> > + size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition
> > +
> > + if (curr.status != MG_OTA_UNAVAILABLE &&
> > + prev.status != MG_OTA_UNAVAILABLE) {
> > + // We know exact sizes of both firmwares.
> > + // Shrink swap size to the MAX(firmware1, firmware2)
> > + size_t sz = curr.size > prev.size ? curr.size : prev.size;
> > + if (sz > 0 && sz < max) max = sz;
> > + }
> > +
> > + // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
> > + prev.status = MG_OTA_UNCOMMITTED;
> > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
> > + sizeof(prev));
> > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
> > + sizeof(curr));
> > +
> > + MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
> > + MG_INFO(("Do NOT power off..."));
> > + mg_log_level = MG_LL_NONE;
> > +
> > + // We use the last sector of partition2 for OTA data/config storage
> > + // Therefore we can use last sector of partition1 for swapping
> > + char *tmpsector = partition1 + fs / 2 - ss; // Last sector of 
> partition1
> > + (void) tmpsector;
> > + for (ofs = 0; ofs < max; ofs += ss) {
> > + // mg_flash_erase(tmpsector);
> > + mg_flash_write(tmpsector, partition1 + ofs, ss);
> > + // mg_flash_erase(partition1 + ofs);
> > + mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
> > + // mg_flash_erase(partition2 + ofs);
> > + mg_flash_write(partition2 + ofs, tmpsector, ss);
> > + }
> > + mg_device_reset();
> > + }
> > +}
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/printf.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list 
> *ap) {
> > + size_t len = mg_snprintf(NULL, 0, fmt, ap);
> > + char *buf;
> > + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
> > + len = 0; // Nah. Not enough space
> > + } else {
> > + len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
> > + mg_queue_add(q, len);
> > + }
> > + return len;
> > +}
> > +
> > +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
> > + va_list ap;
> > + size_t len;
> > + va_start(ap, fmt);
> > + len = mg_queue_vprintf(q, fmt, &ap);
> > + va_end(ap);
> > + return len;
> > +}
> > +
> > +static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
> > + struct mg_iobuf *io = (struct mg_iobuf *) param;
> > + if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
> > + if (io->len + 2 <= io->size) {
> > + io->buf[io->len++] = (uint8_t) ch;
> > + io->buf[io->len] = 0;
> > + } else if (io->len < io->size) {
> > + io->buf[io->len++] = 0; // Guarantee to 0-terminate
> > + }
> > +}
> > +
> > +static void mg_putchar_iobuf_static(char ch, void *param) {
> > + mg_pfn_iobuf_private(ch, param, false);
> > +}
> > +
> > +void mg_pfn_iobuf(char ch, void *param) {
> > + mg_pfn_iobuf_private(ch, param, true);
> > +}
> > +
> > +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list 
> *ap) {
> > + struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> > + size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> > + if (n < len) buf[n] = '\0';
> > + return n;
> > +}
> > +
> > +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> > + va_list ap;
> > + size_t n;
> > + va_start(ap, fmt);
> > + n = mg_vsnprintf(buf, len, fmt, &ap);
> > + va_end(ap);
> > + return n;
> > +}
> > +
> > +char *mg_vmprintf(const char *fmt, va_list *ap) {
> > + struct mg_iobuf io = {0, 0, 0, 256};
> > + mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> > + return (char *) io.buf;
> > +}
> > +
> > +char *mg_mprintf(const char *fmt, ...) {
> > + char *s;
> > + va_list ap;
> > + va_start(ap, fmt);
> > + s = mg_vmprintf(fmt, &ap);
> > + va_end(ap);
> > + return s;
> > +}
> > +
> > +void mg_pfn_stdout(char c, void *param) {
> > + putchar(c);
> > + (void) param;
> > +}
> > +
> > +static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t 
> *p) {
> > + return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
> > +}
> > +
> > +static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t 
> *p) {
> > + return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", 
> mg_ntohs(p[0]),
> > + mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
> > + mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
> > + mg_ntohs(p[7]));
> > +}
> > +
> > +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) {
> > + uint8_t *p = va_arg(*ap, uint8_t *);
> > + return print_ip4(out, arg, p);
> > +}
> > +
> > +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) {
> > + uint16_t *p = va_arg(*ap, uint16_t *);
> > + return print_ip6(out, arg, p);
> > +}
> > +
> > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) {
> > + struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
> > + if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip);
> > + return print_ip4(out, arg, (uint8_t *) &addr->ip);
> > +}
> > +
> > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list 
> *ap) {
> > + struct mg_addr *a = va_arg(*ap, struct mg_addr *);
> > + return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, 
> mg_ntohs(a->port));
> > +}
> > +
> > +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) {
> > + uint8_t *p = va_arg(*ap, uint8_t *);
> > + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], 
> p[1], p[2],
> > + p[3], p[4], p[5]);
> > +}
> > +
> > +static char mg_esc(int c, bool esc) {
> > + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> > + for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> > + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> > + }
> > + return 0;
> > +}
> > +
> > +static char mg_escape(int c) {
> > + return mg_esc(c, true);
> > +}
> > +
> > +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> > + size_t len) {
> > + size_t i = 0, extra = 0;
> > + for (i = 0; i < len && buf[i] != '\0'; i++) {
> > + char c = mg_escape(buf[i]);
> > + if (c) {
> > + out('\\', ptr), out(c, ptr), extra++;
> > + } else {
> > + out(buf[i], ptr);
> > + }
> > + }
> > + return i + extra;
> > +}
> > +
> > +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf,
> > + size_t len) {
> > + size_t i, j, n = 0;
> > + const char *t =
> > + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> > + for (i = 0; i < len; i += 3) {
> > + uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> > + c3 = i + 2 < len ? buf[i + 2] : 0;
> > + char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
> > + if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> > + if (i + 2 < len) tmp[3] = t[c3 & 63];
> > + for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg);
> > + n += j;
> > + }
> > + return n;
> > +}
> > +
> > +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) {
> > + size_t bl = (size_t) va_arg(*ap, int);
> > + uint8_t *p = va_arg(*ap, uint8_t *);
> > + const char *hex = "0123456789abcdef";
> > + size_t j;
> > + for (j = 0; j < bl; j++) {
> > + out(hex[(p[j] >> 4) & 0x0F], arg);
> > + out(hex[p[j] & 0x0F], arg);
> > + }
> > + return 2 * bl;
> > +}
> > +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list 
> *ap) {
> > + size_t len = (size_t) va_arg(*ap, int);
> > + uint8_t *buf = va_arg(*ap, uint8_t *);
> > + return bcpy(out, arg, buf, len);
> > +}
> > +
> > +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) {
> > + size_t len = (size_t) va_arg(*ap, int);
> > + char *p = va_arg(*ap, char *);
> > + if (len == 0) len = p == NULL ? 0 : strlen(p);
> > + return qcpy(out, arg, p, len);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/queue.c"
> > +#endif
> > +
> > +
> > +
> > +#if (defined(__GNUC__) && (__GNUC__ > 4) || \
> > + (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \
> > + defined(__clang__)
> > +#define MG_MEMORY_BARRIER() __sync_synchronize()
> > +#elif defined(_MSC_VER) && _MSC_VER >= 1700
> > +#define MG_MEMORY_BARRIER() MemoryBarrier()
> > +#elif !defined(MG_MEMORY_BARRIER)
> > +#define MG_MEMORY_BARRIER()
> > +#endif
> > +
> > +// Every message in a queue is prepended by a 32-bit message length 
> (ML).
> > +// If ML is 0, then it is the end, and reader must wrap to the 
> beginning.
> > +//
> > +// Queue when q->tail <= q->head:
> > +// |----- free -----| ML | message1 | ML | message2 | ----- free ------|
> > +// ^ ^ ^ ^
> > +// buf tail head len
> > +//
> > +// Queue when q->tail > q->head:
> > +// | ML | message2 |----- free ------| ML | message1 | 0 |---- free 
> ----|
> > +// ^ ^ ^ ^
> > +// buf head tail len
> > +
> > +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
> > + q->size = size;
> > + q->buf = buf;
> > + q->head = q->tail = 0;
> > +}
> > +
> > +static size_t mg_queue_read_len(struct mg_queue *q) {
> > + uint32_t n = 0;
> > + MG_MEMORY_BARRIER();
> > + memcpy(&n, q->buf + q->tail, sizeof(n));
> > + assert(q->tail + n + sizeof(n) <= q->size);
> > + return n;
> > +}
> > +
> > +static void mg_queue_write_len(struct mg_queue *q, size_t len) {
> > + uint32_t n = (uint32_t) len;
> > + memcpy(q->buf + q->head, &n, sizeof(n));
> > + MG_MEMORY_BARRIER();
> > +}
> > +
> > +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
> > + size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0 marker
> > + if (q->head >= q->tail && q->head + len + hs <= q->size) {
> > + space = q->size - q->head - hs; // There is enough space
> > + } else if (q->head >= q->tail && q->tail > hs) {
> > + mg_queue_write_len(q, 0); // Not enough space ahead
> > + q->head = 0; // Wrap head to the beginning
> > + }
> > + if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
> > + if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
> > + return space;
> > +}
> > +
> > +size_t mg_queue_next(struct mg_queue *q, char **buf) {
> > + size_t len = 0;
> > + if (q->tail != q->head) {
> > + len = mg_queue_read_len(q);
> > + if (len == 0) { // Zero (head wrapped) ?
> > + q->tail = 0; // Reset tail to the start
> > + if (q->head > q->tail) len = mg_queue_read_len(q); // Read again
> > + }
> > + }
> > + if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
> > + assert(q->tail + len <= q->size);
> > + return len;
> > +}
> > +
> > +void mg_queue_add(struct mg_queue *q, size_t len) {
> > + assert(len > 0);
> > + mg_queue_write_len(q, len);
> > + assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
> > + q->head += len + sizeof(uint32_t);
> > +}
> > +
> > +void mg_queue_del(struct mg_queue *q, size_t len) {
> > + q->tail += len + sizeof(uint32_t);
> > + assert(q->tail + sizeof(uint32_t) <= q->size);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/rpc.c"
> > +#endif
> > +
> > +
> > +
> > +void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> > + void (*fn)(struct mg_rpc_req *), void *fn_data) {
> > + struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> > + if (rpc != NULL) {
> > + rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
> > + rpc->method.len = method.len;
> > + rpc->fn = fn;
> > + rpc->fn_data = fn_data;
> > + rpc->next = *head, *head = rpc;
> > + }
> > +}
> > +
> > +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
> > + struct mg_rpc *r;
> > + while ((r = *head) != NULL) {
> > + if (r->fn == fn || fn == NULL) {
> > + *head = r->next;
> > + free((void *) r->method.buf);
> > + free(r);
> > + } else {
> > + head = &(*head)->next;
> > + }
> > + }
> > +}
> > +
> > +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
> > + struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> > + while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
> > + if (h != NULL) {
> > + r->rpc = h;
> > + h->fn(r);
> > + } else {
> > + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, 
> method.buf);
> > + }
> > +}
> > +
> > +void mg_rpc_process(struct mg_rpc_req *r) {
> > + int len, off = mg_json_get(r->frame, "$.method", &len);
> > + if (off > 0 && r->frame.buf[off] == '"') {
> > + struct mg_str method = mg_str_n(&r->frame.buf[off + 1], (size_t) len - 
> 2);
> > + mg_rpc_call(r, method);
> > + } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> > + (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> > + mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler
> > + } else {
> > + mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
> > + r->frame.buf); // Invalid
> > + }
> > +}
> > +
> > +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
> > + int len, off = mg_json_get(r->frame, "$.id", &len);
> > + if (off > 0) {
> > + mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", 
> len,
> > + &r->frame.buf[off], mg_print_esc, 0, "result");
> > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> > + mg_xprintf(r->pfn, r->pfn_data, "}");
> > + }
> > +}
> > +
> > +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> > + va_list ap;
> > + va_start(ap, fmt);
> > + mg_rpc_vok(r, fmt, &ap);
> > + va_end(ap);
> > +}
> > +
> > +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, 
> va_list *ap) {
> > + int len, off = mg_json_get(r->frame, "$.id", &len);
> > + mg_xprintf(r->pfn, r->pfn_data, "{");
> > + if (off > 0) {
> > + mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len,
> > + &r->frame.buf[off]);
> > + }
> > + mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, 
> "error",
> > + mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
> > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> > + mg_xprintf(r->pfn, r->pfn_data, "}}");
> > +}
> > +
> > +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
> > + va_list ap;
> > + va_start(ap, fmt);
> > + mg_rpc_verr(r, code, fmt, &ap);
> > + va_end(ap);
> > +}
> > +
> > +static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
> > + struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
> > + size_t len = 0;
> > + for (h = *head; h != NULL; h = h->next) {
> > + if (h->method.len == 0) continue; // Ignore response handler
> > + len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
> > + mg_print_esc, (int) h->method.len, h->method.buf);
> > + }
> > + return len;
> > +}
> > +
> > +void mg_rpc_list(struct mg_rpc_req *r) {
> > + mg_rpc_ok(r, "[%M]", print_methods, r->head);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/sha1.c"
> > +#endif
> > +/* Copyright(c) By Steve Reid <st...@edmweb.com> */
> > +/* 100% Public Domain */
> > +
> > +
> > +
> > +union char64long16 {
> > + unsigned char c[64];
> > + uint32_t l[16];
> > +};
> > +
> > +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - 
> (bits))))
> > +
> > +static uint32_t blk0(union char64long16 *block, int i) {
> > + if (MG_BIG_ENDIAN) {
> > + } else {
> > + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> > + (rol(block->l[i], 8) & 0x00FF00FF);
> > + }
> > + return block->l[i];
> > +}
> > +
> > +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define 
> R0~R4) */
> > +#undef blk
> > +#undef R0
> > +#undef R1
> > +#undef R2
> > +#undef R3
> > +#undef R4
> > +
> > +#define blk(i) \
> > + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 
> 15] ^ \
> > + block->l[(i + 2) & 15] ^ block->l[i & 15], \
> > + 1))
> > +#define R0(v, w, x, y, z, i) \
> > + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
> > + w = rol(w, 30);
> > +#define R1(v, w, x, y, z, i) \
> > + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> > + w = rol(w, 30);
> > +#define R2(v, w, x, y, z, i) \
> > + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> > + w = rol(w, 30);
> > +#define R3(v, w, x, y, z, i) \
> > + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
> > + w = rol(w, 30);
> > +#define R4(v, w, x, y, z, i) \
> > + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> > + w = rol(w, 30);
> > +
> > +static void mg_sha1_transform(uint32_t state[5],
> > + const unsigned char *buffer) {
> > + uint32_t a, b, c, d, e;
> > + union char64long16 block[1];
> > +
> > + memcpy(block, buffer, 64);
> > + a = state[0];
> > + b = state[1];
> > + c = state[2];
> > + d = state[3];
> > + e = state[4];
> > + R0(a, b, c, d, e, 0);
> > + R0(e, a, b, c, d, 1);
> > + R0(d, e, a, b, c, 2);
> > + R0(c, d, e, a, b, 3);
> > + R0(b, c, d, e, a, 4);
> > + R0(a, b, c, d, e, 5);
> > + R0(e, a, b, c, d, 6);
> > + R0(d, e, a, b, c, 7);
> > + R0(c, d, e, a, b, 8);
> > + R0(b, c, d, e, a, 9);
> > + R0(a, b, c, d, e, 10);
> > + R0(e, a, b, c, d, 11);
> > + R0(d, e, a, b, c, 12);
> > + R0(c, d, e, a, b, 13);
> > + R0(b, c, d, e, a, 14);
> > + R0(a, b, c, d, e, 15);
> > + R1(e, a, b, c, d, 16);
> > + R1(d, e, a, b, c, 17);
> > + R1(c, d, e, a, b, 18);
> > + R1(b, c, d, e, a, 19);
> > + R2(a, b, c, d, e, 20);
> > + R2(e, a, b, c, d, 21);
> > + R2(d, e, a, b, c, 22);
> > + R2(c, d, e, a, b, 23);
> > + R2(b, c, d, e, a, 24);
> > + R2(a, b, c, d, e, 25);
> > + R2(e, a, b, c, d, 26);
> > + R2(d, e, a, b, c, 27);
> > + R2(c, d, e, a, b, 28);
> > + R2(b, c, d, e, a, 29);
> > + R2(a, b, c, d, e, 30);
> > + R2(e, a, b, c, d, 31);
> > + R2(d, e, a, b, c, 32);
> > + R2(c, d, e, a, b, 33);
> > + R2(b, c, d, e, a, 34);
> > + R2(a, b, c, d, e, 35);
> > + R2(e, a, b, c, d, 36);
> > + R2(d, e, a, b, c, 37);
> > + R2(c, d, e, a, b, 38);
> > + R2(b, c, d, e, a, 39);
> > + R3(a, b, c, d, e, 40);
> > + R3(e, a, b, c, d, 41);
> > + R3(d, e, a, b, c, 42);
> > + R3(c, d, e, a, b, 43);
> > + R3(b, c, d, e, a, 44);
> > + R3(a, b, c, d, e, 45);
> > + R3(e, a, b, c, d, 46);
> > + R3(d, e, a, b, c, 47);
> > + R3(c, d, e, a, b, 48);
> > + R3(b, c, d, e, a, 49);
> > + R3(a, b, c, d, e, 50);
> > + R3(e, a, b, c, d, 51);
> > + R3(d, e, a, b, c, 52);
> > + R3(c, d, e, a, b, 53);
> > + R3(b, c, d, e, a, 54);
> > + R3(a, b, c, d, e, 55);
> > + R3(e, a, b, c, d, 56);
> > + R3(d, e, a, b, c, 57);
> > + R3(c, d, e, a, b, 58);
> > + R3(b, c, d, e, a, 59);
> > + R4(a, b, c, d, e, 60);
> > + R4(e, a, b, c, d, 61);
> > + R4(d, e, a, b, c, 62);
> > + R4(c, d, e, a, b, 63);
> > + R4(b, c, d, e, a, 64);
> > + R4(a, b, c, d, e, 65);
> > + R4(e, a, b, c, d, 66);
> > + R4(d, e, a, b, c, 67);
> > + R4(c, d, e, a, b, 68);
> > + R4(b, c, d, e, a, 69);
> > + R4(a, b, c, d, e, 70);
> > + R4(e, a, b, c, d, 71);
> > + R4(d, e, a, b, c, 72);
> > + R4(c, d, e, a, b, 73);
> > + R4(b, c, d, e, a, 74);
> > + R4(a, b, c, d, e, 75);
> > + R4(e, a, b, c, d, 76);
> > + R4(d, e, a, b, c, 77);
> > + R4(c, d, e, a, b, 78);
> > + R4(b, c, d, e, a, 79);
> > + state[0] += a;
> > + state[1] += b;
> > + state[2] += c;
> > + state[3] += d;
> > + state[4] += e;
> > + /* Erase working structures. The order of operations is important,
> > + * used to ensure that compiler doesn't optimize those out. */
> > + memset(block, 0, sizeof(block));
> > + a = b = c = d = e = 0;
> > + (void) a;
> > + (void) b;
> > + (void) c;
> > + (void) d;
> > + (void) e;
> > +}
> > +
> > +void mg_sha1_init(mg_sha1_ctx *context) {
> > + context->state[0] = 0x67452301;
> > + context->state[1] = 0xEFCDAB89;
> > + context->state[2] = 0x98BADCFE;
> > + context->state[3] = 0x10325476;
> > + context->state[4] = 0xC3D2E1F0;
> > + context->count[0] = context->count[1] = 0;
> > +}
> > +
> > +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
> > + size_t len) {
> > + size_t i, j;
> > +
> > + j = context->count[0];
> > + if ((context->count[0] += (uint32_t) len << 3) < j) 
> context->count[1]++;
> > + context->count[1] += (uint32_t) (len >> 29);
> > + j = (j >> 3) & 63;
> > + if ((j + len) > 63) {
> > + memcpy(&context->buffer[j], data, (i = 64 - j));
> > + mg_sha1_transform(context->state, context->buffer);
> > + for (; i + 63 < len; i += 64) {
> > + mg_sha1_transform(context->state, &data[i]);
> > + }
> > + j = 0;
> > + } else
> > + i = 0;
> > + memcpy(&context->buffer[j], &data[i], len - i);
> > +}
> > +
> > +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
> > + unsigned i;
> > + unsigned char finalcount[8], c;
> > +
> > + for (i = 0; i < 8; i++) {
> > + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
> > + ((3 - (i & 3)) * 8)) &
> > + 255);
> > + }
> > + c = 0200;
> > + mg_sha1_update(context, &c, 1);
> > + while ((context->count[0] & 504) != 448) {
> > + c = 0000;
> > + mg_sha1_update(context, &c, 1);
> > + }
> > + mg_sha1_update(context, finalcount, 8);
> > + for (i = 0; i < 20; i++) {
> > + digest[i] =
> > + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 
> 255);
> > + }
> > + memset(context, '\0', sizeof(*context));
> > + memset(&finalcount, '\0', sizeof(finalcount));
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/sha256.c"
> > +#endif
> > +// https://github.com/B-Con/crypto-algorithms
> > +// Author: Brad Conte (brad AT bradconte.com)
> > +// Disclaimer: This code is presented "as is" without any guarantees.
> > +// Details: Defines the API for the corresponding SHA1 implementation.
> > +// Copyright: public domain
> > +
> > +
> > +
> > +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
> > +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
> > +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
> > +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
> > +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
> > +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
> > +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
> > +
> > +static const uint32_t mg_sha256_k[64] = {
> > + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
> > + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
> > + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
> > + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
> > + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
> > + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
> > + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
> > + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
> > + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
> > + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
> > + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
> > +
> > +void mg_sha256_init(mg_sha256_ctx *ctx) {
> > + ctx->len = 0;
> > + ctx->bits = 0;
> > + ctx->state[0] = 0x6a09e667;
> > + ctx->state[1] = 0xbb67ae85;
> > + ctx->state[2] = 0x3c6ef372;
> > + ctx->state[3] = 0xa54ff53a;
> > + ctx->state[4] = 0x510e527f;
> > + ctx->state[5] = 0x9b05688c;
> > + ctx->state[6] = 0x1f83d9ab;
> > + ctx->state[7] = 0x5be0cd19;
> > +}
> > +
> > +static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
> > + int i, j;
> > + uint32_t a, b, c, d, e, f, g, h;
> > + uint32_t m[64];
> > + for (i = 0, j = 0; i < 16; ++i, j += 4)
> > + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
> > + ((uint32_t) ctx->buffer[j + 1] << 16) |
> > + ((uint32_t) ctx->buffer[j + 2] << 8) |
> > + ((uint32_t) ctx->buffer[j + 3]));
> > + for (; i < 64; ++i)
> > + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
> > +
> > + a = ctx->state[0];
> > + b = ctx->state[1];
> > + c = ctx->state[2];
> > + d = ctx->state[3];
> > + e = ctx->state[4];
> > + f = ctx->state[5];
> > + g = ctx->state[6];
> > + h = ctx->state[7];
> > +
> > + for (i = 0; i < 64; ++i) {
> > + uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
> > + uint32_t t2 = ep0(a) + maj(a, b, c);
> > + h = g;
> > + g = f;
> > + f = e;
> > + e = d + t1;
> > + d = c;
> > + c = b;
> > + b = a;
> > + a = t1 + t2;
> > + }
> > +
> > + ctx->state[0] += a;
> > + ctx->state[1] += b;
> > + ctx->state[2] += c;
> > + ctx->state[3] += d;
> > + ctx->state[4] += e;
> > + ctx->state[5] += f;
> > + ctx->state[6] += g;
> > + ctx->state[7] += h;
> > +}
> > +
> > +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char *data,
> > + size_t len) {
> > + size_t i;
> > + for (i = 0; i < len; i++) {
> > + ctx->buffer[ctx->len] = data[i];
> > + if ((++ctx->len) == 64) {
> > + mg_sha256_chunk(ctx);
> > + ctx->bits += 512;
> > + ctx->len = 0;
> > + }
> > + }
> > +}
> > +
> > +// TODO: make final reusable (remove side effects)
> > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *ctx) {
> > + uint32_t i = ctx->len;
> > + if (i < 56) {
> > + ctx->buffer[i++] = 0x80;
> > + while (i < 56) {
> > + ctx->buffer[i++] = 0x00;
> > + }
> > + } else {
> > + ctx->buffer[i++] = 0x80;
> > + while (i < 64) {
> > + ctx->buffer[i++] = 0x00;
> > + }
> > + mg_sha256_chunk(ctx);
> > + memset(ctx->buffer, 0, 56);
> > + }
> > +
> > + ctx->bits += ctx->len * 8;
> > + ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
> > + ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
> > + ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
> > + ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
> > + ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
> > + ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
> > + ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
> > + ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
> > + mg_sha256_chunk(ctx);
> > +
> > + for (i = 0; i < 4; ++i) {
> > + digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
> > + digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) & 0xff);
> > + digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) & 0xff);
> > + digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) & 0xff);
> > + digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) & 0xff);
> > + digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) & 0xff);
> > + digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) & 0xff);
> > + digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) & 0xff);
> > + }
> > +}
> > +
> > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, 
> uint8_t *data,
> > + size_t datasz) {
> > + mg_sha256_ctx ctx;
> > + uint8_t k[64] = {0};
> > + uint8_t o_pad[64], i_pad[64];
> > + unsigned int i;
> > + memset(i_pad, 0x36, sizeof(i_pad));
> > + memset(o_pad, 0x5c, sizeof(o_pad));
> > + if (keysz < 64) {
> > + if (keysz > 0) memmove(k, key, keysz);
> > + } else {
> > + mg_sha256_init(&ctx);
> > + mg_sha256_update(&ctx, key, keysz);
> > + mg_sha256_final(k, &ctx);
> > + }
> > + for (i = 0; i < sizeof(k); i++) {
> > + i_pad[i] ^= k[i];
> > + o_pad[i] ^= k[i];
> > + }
> > + mg_sha256_init(&ctx);
> > + mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
> > + mg_sha256_update(&ctx, data, datasz);
> > + mg_sha256_final(dst, &ctx);
> > + mg_sha256_init(&ctx);
> > + mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
> > + mg_sha256_update(&ctx, dst, 32);
> > + mg_sha256_final(dst, &ctx);
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/sntp.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +
> > +#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
> > +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
> > +
> > +static int64_t gettimestamp(const uint32_t *data) {
> > + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> > + if (sec) sec -= SNTP_TIME_OFFSET;
> > + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 
> 1000.0);
> > +}
> > +
> > +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> > + int64_t res = -1;
> > + int mode = len > 0 ? buf[0] & 7 : 0;
> > + int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> > + if (len < 48) {
> > + MG_ERROR(("%s", "corrupt packet"));
> > + } else if (mode != 4 && mode != 5) {
> > + MG_ERROR(("%s", "not a server reply"));
> > + } else if (buf[1] == 0) {
> > + MG_ERROR(("%s", "server sent a kiss of death"));
> > + } else if (version == 4 || version == 3) {
> > + // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> > + int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> > + int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> > + int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> > + int64_t t3 = (int64_t) mg_millis();
> > + int64_t delta = (t3 - t0) - (t2 - t1);
> > + MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
> > + res = t2 + delta / 2;
> > + } else {
> > + MG_ERROR(("unexpected version: %d", version));
> > + }
> > + return res;
> > +}
> > +
> > +static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_READ) {
> > + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> > + if (milliseconds > 0) {
> > + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds));
> > + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> > + MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> > + (unsigned) (milliseconds % 1000)));
> > + }
> > + mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
> > + } else if (ev == MG_EV_CONNECT) {
> > + mg_sntp_request(c);
> > + } else if (ev == MG_EV_CLOSE) {
> > + }
> > + (void) ev_data;
> > +}
> > +
> > +void mg_sntp_request(struct mg_connection *c) {
> > + if (c->is_resolving) {
> > + MG_ERROR(("%lu wait until resolved", c->id));
> > + } else {
> > + int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
> > + uint8_t buf[48] = {0};
> > + uint32_t *t = (uint32_t *) &buf[40];
> > + double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> > + buf[0] = (0 << 6) | (4 << 3) | 3;
> > + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> > + t[1] = mg_htonl((uint32_t) frac);
> > + mg_send(c, buf, sizeof(buf));
> > + }
> > +}
> > +
> > +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char 
> *url,
> > + mg_event_handler_t fn, void *fnd) {
> > + struct mg_connection *c = NULL;
> > + if (url == NULL) url = "udp://time.google.com:123";
> > + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> > + return c;
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/sock.c"
> > +#endif
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +
> > +#if MG_ENABLE_SOCKET
> > +
> > +#ifndef closesocket
> > +#define closesocket(x) close(x)
> > +#endif
> > +
> > +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> > +#define S2PTR(s_) ((void *) (size_t) (s_))
> > +
> > +#ifndef MSG_NONBLOCKING
> > +#define MSG_NONBLOCKING 0
> > +#endif
> > +
> > +#ifndef AF_INET6
> > +#define AF_INET6 10
> > +#endif
> > +
> > +#ifndef MG_SOCK_ERR
> > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
> > +#endif
> > +
> > +#ifndef MG_SOCK_INTR
> > +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == 
> EINTR)
> > +#endif
> > +
> > +#ifndef MG_SOCK_PENDING
> > +#define MG_SOCK_PENDING(errcode) \
> > + (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK))
> > +#endif
> > +
> > +#ifndef MG_SOCK_RESET
> > +#define MG_SOCK_RESET(errcode) \
> > + (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
> > +#endif
> > +
> > +union usa {
> > + struct sockaddr sa;
> > + struct sockaddr_in sin;
> > +#if MG_ENABLE_IPV6
> > + struct sockaddr_in6 sin6;
> > +#endif
> > +};
> > +
> > +static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> > + socklen_t len = sizeof(usa->sin);
> > + memset(usa, 0, sizeof(*usa));
> > + usa->sin.sin_family = AF_INET;
> > + usa->sin.sin_port = a->port;
> > + memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
> > +#if MG_ENABLE_IPV6
> > + if (a->is_ip6) {
> > + usa->sin.sin_family = AF_INET6;
> > + usa->sin6.sin6_port = a->port;
> > + usa->sin6.sin6_scope_id = a->scope_id;
> > + memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
> > + len = sizeof(usa->sin6);
> > + }
> > +#endif
> > + return len;
> > +}
> > +
> > +static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
> > + a->is_ip6 = is_ip6;
> > + a->port = usa->sin.sin_port;
> > + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
> > +#if MG_ENABLE_IPV6
> > + if (is_ip6) {
> > + memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
> > + a->port = usa->sin6.sin6_port;
> > + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
> > + }
> > +#endif
> > +}
> > +
> > +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> > + union usa usa;
> > + socklen_t n = sizeof(usa);
> > + if (getsockname(fd, &usa.sa, &n) == 0) {
> > + tomgaddr(&usa, addr, n != sizeof(usa.sin));
> > + }
> > +}
> > +
> > +static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
> > + if (n == MG_IO_WAIT) {
> > + // Do nothing
> > + } else if (n <= 0) {
> > + c->is_closing = 1; // Termination. Don't call mg_error(): #1529
> > + } else if (n > 0) {
> > + if (c->is_hexdumping) {
> > + MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc,
> > + r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
> > + mg_hexdump(buf, (size_t) n);
> > + }
> > + if (r) {
> > + c->recv.len += (size_t) n;
> > + mg_call(c, MG_EV_READ, &n);
> > + } else {
> > + mg_iobuf_del(&c->send, 0, (size_t) n);
> > + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> > + if (c->send.len == 0) {
> > + MG_EPOLL_MOD(c, 0);
> > + }
> > + mg_call(c, MG_EV_WRITE, &n);
> > }
> > }
> > - return flags;
> > }
> >
> > -static int uri_to_path(struct mg_connection *c, struct mg_http_message 
> *hm,
> > - const struct mg_http_serve_opts *opts, char *path,
> > - size_t path_size) {
> > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > - struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0};
> > - while (mg_commalist(&s, &k, &v)) {
> > - if (v.len == 0) v = k, k = mg_str("/");
> > - if (hm->uri.len < k.len) continue;
> > - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
> > - u = k, p = v;
> > +long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> > + long n;
> > + if (c->is_udp) {
> > + union usa usa;
> > + socklen_t slen = tousa(&c->rem, &usa);
> > + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
> > + if (n > 0) setlocaddr(FD(c), &c->loc);
> > + } else {
> > + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> > }
> > - return uri_to_path2(c, hm, fs, u, p, path, path_size);
> > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > }
> >
> > -void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message 
> *hm,
> > - const struct mg_http_serve_opts *opts) {
> > - char path[MG_PATH_MAX];
> > - const char *sp = opts->ssi_pattern;
> > - int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> > - if (flags < 0) {
> > - // Do nothing: the response has already been sent by uri_to_path()
> > - } else if (flags & MG_FS_DIR) {
> > - listdir(c, hm, opts, path);
> > - } else if (flags && sp != NULL &&
> > - mg_globmatch(sp, strlen(sp), path, strlen(path))) {
> > - mg_http_serve_ssi(c, opts->root_dir, path);
> > +bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> > + if (c->is_udp) {
> > + long n = mg_io_send(c, buf, len);
> > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
> > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> > + iolog(c, (char *) buf, n, false);
> > + return n > 0;
> > } else {
> > - mg_http_serve_file(c, hm, path, opts);
> > + return mg_iobuf_add(&c->send, c->send.len, buf, len);
> > }
> > }
> >
> > -static bool mg_is_url_safe(int c) {
> > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> > - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
> > +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> > +#if defined(MG_CUSTOM_NONBLOCK)
> > + MG_CUSTOM_NONBLOCK(fd);
> > +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > + unsigned long on = 1;
> > + ioctlsocket(fd, FIONBIO, &on);
> > +#elif MG_ENABLE_RL
> > + unsigned long on = 1;
> > + ioctlsocket(fd, FIONBIO, &on);
> > +#elif MG_ENABLE_FREERTOS_TCP
> > + const BaseType_t off = 0;
> > + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) 
> (void) 0;
> > + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) 
> (void) 0;
> > +#elif MG_ENABLE_LWIP
> > + lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> > +#elif MG_ARCH == MG_ARCH_AZURERTOS
> > + fcntl(fd, F_SETFL, O_NONBLOCK);
> > +#elif MG_ARCH == MG_ARCH_TIRTOS
> > + int val = 0;
> > + setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> > + // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> > + int sz = sizeof(val);
> > + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> > + val /= 2; // set send low-water mark at half send buffer size
> > + setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> > +#else
> > + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // 
> Non-blocking mode
> > + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
> > +#endif
> > }
> >
> > -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
> > - size_t i, n = 0;
> > - for (i = 0; i < sl; i++) {
> > - int c = *(unsigned char *) &s[i];
> > - if (n + 4 >= len) return 0;
> > - if (mg_is_url_safe(c)) {
> > - buf[n++] = s[i];
> > +bool mg_open_listener(struct mg_connection *c, const char *url) {
> > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> > + bool success = false;
> > + c->loc.port = mg_htons(mg_url_port(url));
> > + if (!mg_aton(mg_url_host(url), &c->loc)) {
> > + MG_ERROR(("invalid listening URL: %s", url));
> > + } else {
> > + union usa usa;
> > + socklen_t slen = tousa(&c->loc, &usa);
> > + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> > + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
> > + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> > + (void) on;
> > +
> > + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> > + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
> > +#if defined(SO_EXCLUSIVEADDRUSE)
> > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
> > + (char *) &on, sizeof(on))) != 0) {
> > + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> > + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, 
> MG_SOCK_ERR(rc)));
> > +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
> > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
> > + sizeof(on))) != 0) {
> > + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On
> > + // Windows, SO_REUSEADDR allows to bind a socket to a port without 
> error
> > + // even if the port is already open by another program. This is not the
> > + // behavior SO_REUSEADDR was designed for, and leads to hard-to-track
> > + // failure scenarios.
> > + //
> > + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining
> > + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile 
> but
> > + // won't work! (setsockopt will return EINVAL)
> > + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
> > +#endif
> > +#if MG_IPV6_V6ONLY
> > + // Bind only to the V6 address, not V4 address on this port
> > + } else if (c->loc.is_ip6 &&
> > + (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
> > + sizeof(on))) != 0) {
> > + // See #2089. Allow to bind v4 and v6 sockets on the same port
> > + MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
> > +#endif
> > + } else if ((rc = bind(fd, &usa.sa, slen)) != 0) {
> > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> > + } else if ((type == SOCK_STREAM &&
> > + (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
> > + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> > + // In case port was set to 0, get the real port number
> > + MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
> > } else {
> > - buf[n++] = '%';
> > - mg_hex(&s[i], 1, &buf[n]);
> > - n += 2;
> > + setlocaddr(fd, &c->loc);
> > + mg_set_non_blocking_mode(fd);
> > + c->fd = S2PTR(fd);
> > + MG_EPOLL_ADD(c);
> > + success = true;
> > }
> > }
> > - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate the 
> destination
> > - if (len > 0) buf[len - 1] = '\0'; // Always.
> > - return n;
> > + if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> > + return success;
> > }
> >
> > -void mg_http_creds(struct mg_http_message *hm, char *user, size_t 
> userlen,
> > - char *pass, size_t passlen) {
> > - struct mg_str *v = mg_http_get_header(hm, "Authorization");
> > - user[0] = pass[0] = '\0';
> > - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
> > - char buf[256];
> > - int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
> > - const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 
> 0);
> > - if (p != NULL) {
> > - mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
> > - mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
> > - }
> > - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 
> 0) {
> > - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
> > - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> > - struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 
> 12));
> > - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
> > +static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
> > + long n = 0;
> > + if (c->is_udp) {
> > + union usa usa;
> > + socklen_t slen = tousa(&c->rem, &usa);
> > + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
> > + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
> > } else {
> > - mg_http_get_var(&hm->query, "access_token", pass, passlen);
> > + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> > }
> > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > }
> >
> > -static struct mg_str stripquotes(struct mg_str s) {
> > - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
> > - ? mg_str_n(s.ptr + 1, s.len - 2)
> > - : s;
> > +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
> > + bool res = false;
> > + if (io->len >= MG_MAX_RECV_SIZE) {
> > + mg_error(c, "MG_MAX_RECV_SIZE");
> > + } else if (io->size <= io->len &&
> > + !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
> > + mg_error(c, "OOM");
> > + } else {
> > + res = true;
> > + }
> > + return res;
> > }
> >
> > -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
> > - size_t i;
> > - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> > - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) {
> > - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
> > - int q = p < x && *p == '"' ? 1 : 0;
> > - while (p < x &&
> > - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> > - p++;
> > - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len,
> > - // v.ptr, (int) (p - b), b));
> > - return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> > +// NOTE(lsm): do only one iteration of reads, cause some systems
> > +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
> > +static void read_conn(struct mg_connection *c) {
> > + if (ioalloc(c, &c->recv)) {
> > + char *buf = (char *) &c->recv.buf[c->recv.len];
> > + size_t len = c->recv.size - c->recv.len;
> > + long n = -1;
> > + if (c->is_tls) {
> > + if (!ioalloc(c, &c->rtls)) return;
> > + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
> > + c->rtls.size - c->rtls.len);
> > + if (n == MG_IO_ERR && c->rtls.len == 0) {
> > + // Close only if we have fully drained both raw (rtls) and TLS buffers
> > + c->is_closing = 1;
> > + } else {
> > + if (n > 0) c->rtls.len += (size_t) n;
> > + if (c->is_tls_hs) mg_tls_handshake(c);
> > + n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
> > + }
> > + } else {
> > + n = recv_raw(c, buf, len);
> > }
> > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
> > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> > + iolog(c, buf, n, true);
> > }
> > - return mg_str_n(NULL, 0);
> > }
> >
> > -bool mg_http_match_uri(const struct mg_http_message *hm, const char 
> *glob) {
> > - return mg_match(hm->uri, mg_str(glob), NULL);
> > +static void write_conn(struct mg_connection *c) {
> > + char *buf = (char *) c->send.buf;
> > + size_t len = c->send.len;
> > + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, 
> len);
> > + MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> > + (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> > + (long) c->recv.size, n, MG_SOCK_ERR(n)));
> > + iolog(c, buf, n, false);
> > }
> >
> > -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> > - struct mg_fs *fs, const char *path, size_t max_size) {
> > - char buf[20] = "0";
> > - long res = 0, offset;
> > - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> > - offset = strtol(buf, NULL, 0);
> > - if (hm->body.len == 0) {
> > - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
> > +static void close_conn(struct mg_connection *c) {
> > + if (FD(c) != MG_INVALID_SOCKET) {
> > +#if MG_ENABLE_EPOLL
> > + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> > +#endif
> > + closesocket(FD(c));
> > +#if MG_ENABLE_FREERTOS_TCP
> > + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> > +#endif
> > + }
> > + mg_close_conn(c);
> > +}
> > +
> > +static void connect_conn(struct mg_connection *c) {
> > + union usa usa;
> > + socklen_t n = sizeof(usa);
> > + // Use getpeername() to test whether we have connected
> > + if (getpeername(FD(c), &usa.sa, &n) == 0) {
> > + c->is_connecting = 0;
> > + setlocaddr(FD(c), &c->loc);
> > + mg_call(c, MG_EV_CONNECT, NULL);
> > + MG_EPOLL_MOD(c, 0);
> > + if (c->is_tls_hs) mg_tls_handshake(c);
> > } else {
> > - struct mg_fd *fd;
> > - size_t current_size = 0;
> > - MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset));
> > - if (offset == 0) fs->rm(path); // If offset if 0, truncate file
> > - fs->st(path, &current_size, NULL);
> > - if (offset < 0) {
> > - mg_http_reply(c, 400, "", "offset required");
> > - res = -1;
> > - } else if (offset > 0 && current_size != (size_t) offset) {
> > - mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> > - res = -2;
> > - } else if ((size_t) offset + hm->body.len > max_size) {
> > - mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> > - (unsigned long) max_size);
> > - res = -3;
> > - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> > - mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> > - res = -4;
> > + mg_error(c, "socket error");
> > + }
> > +}
> > +
> > +static void setsockopts(struct mg_connection *c) {
> > +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> > + MG_ARCH == MG_ARCH_TIRTOS
> > + (void) c;
> > +#else
> > + int on = 1;
> > +#if !defined(SOL_TCP)
> > +#define SOL_TCP IPPROTO_TCP
> > +#endif
> > + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) 
> != 0)
> > + (void) 0;
> > + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, 
> sizeof(on)) !=
> > + 0)
> > + (void) 0;
> > +#endif
> > +}
> > +
> > +void mg_connect_resolved(struct mg_connection *c) {
> > + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> > + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has 
> resolved IP
> > + c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
> > + c->is_resolving = 0; // Clear resolving flag
> > + if (FD(c) == MG_INVALID_SOCKET) {
> > + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
> > + } else if (c->is_udp) {
> > + MG_EPOLL_ADD(c);
> > +#if MG_ARCH == MG_ARCH_TIRTOS
> > + union usa usa; // TI-RTOS NDK requires binding to receive on UDP 
> sockets
> > + socklen_t slen = tousa(&c->loc, &usa);
> > + if ((rc = bind(c->fd, &usa.sa, slen)) != 0)
> > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> > +#endif
> > + setlocaddr(FD(c), &c->loc);
> > + mg_call(c, MG_EV_RESOLVE, NULL);
> > + mg_call(c, MG_EV_CONNECT, NULL);
> > + } else {
> > + union usa usa;
> > + socklen_t slen = tousa(&c->rem, &usa);
> > + mg_set_non_blocking_mode(FD(c));
> > + setsockopts(c);
> > + MG_EPOLL_ADD(c);
> > + mg_call(c, MG_EV_RESOLVE, NULL);
> > + rc = connect(FD(c), &usa.sa, slen); // Attempt to connect
> > + if (rc == 0) { // Success
> > + setlocaddr(FD(c), &c->loc);
> > + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user
> > + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake
> > + MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, 
> &c->rem));
> > + c->is_connecting = 1;
> > } else {
> > - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
> > - mg_fs_close(fd);
> > - mg_http_reply(c, 200, "", "%ld", res);
> > + mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
> > }
> > }
> > - return res;
> > -}
> > -
> > -int mg_http_status(const struct mg_http_message *hm) {
> > - return atoi(hm->uri.ptr);
> > -}
> > -
> > -// If a server sends data to the client using chunked encoding, 
> Mongoose strips
> > -// off the chunking prefix (hex length and \r\n) and suffix (\r\n), 
> appends the
> > -// stripped data to the body, and fires the MG_EV_HTTP_CHUNK event. 
> When zero
> > -// chunk is received, we fire MG_EV_HTTP_MSG, and the body already has 
> all
> > -// chunking prefixes/suffixes stripped.
> > -//
> > -// If a server sends data without chunked encoding, we also fire a 
> series of
> > -// MG_EV_HTTP_CHUNK events for every received piece of data, and then 
> we fire
> > -// MG_EV_HTTP_MSG event in the end.
> > -//
> > -// We track total processed length in the c->pfn_data, which is a void *
> > -// pointer: we store a size_t value there.
> > -static bool getchunk(struct mg_str s, size_t *prefixlen, size_t 
> *datalen) {
> > - size_t i = 0, n;
> > - while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
> > - n = mg_unhexn(s.ptr, i);
> > - // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
> > - if (s.len < i + n + 4) return false; // Chunk not yet fully buffered
> > - if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
> > - if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n') return false;
> > - *prefixlen = i + 2;
> > - *datalen = n;
> > - return true;
> > }
> >
> > -static bool mg_is_chunked(struct mg_http_message *hm) {
> > - const char *needle = "chunked";
> > - struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
> > - return te != NULL && mg_vcasecmp(te, needle) == 0;
> > -}
> > -
> > -void mg_http_delete_chunk(struct mg_connection *c, struct 
> mg_http_message *hm) {
> > - size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
> > - mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
> > - c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
> > -}
> > -
> > -static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
> > - struct mg_http_message *hm, bool *next) {
> > - // | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
> > - // +------------------+--------------------------+----
> > - // | hlen | chunk1 | ......
> > - char *buf = (char *) &c->recv.buf[hlen], *p = buf;
> > - size_t len = c->recv.len - hlen;
> > - size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> > - size_t mark, pl, dl, del = 0, ofs = 0;
> > - bool last = false;
> > - if (processed <= len) len -= processed, buf += processed;
> > - while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
> > - size_t saved = c->recv.len;
> > - memmove(p + processed, buf + ofs + pl, dl);
> > - // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
> > - hm->chunk = mg_str_n(p + processed, dl);
> > - mg_call(c, MG_EV_HTTP_CHUNK, hm);
> > - ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
> > - processed += dl;
> > - if (c->recv.len != saved) processed -= dl, buf -= dl;
> > - // mg_hexdump(c->recv.buf, hlen + processed);
> > - last = (dl == 0);
> > - }
> > - mg_iobuf_del(&c->recv, hlen + processed, del);
> > - mark = ((size_t) c->pfn_data) & MG_DMARK;
> > - c->pfn_data = (void *) (processed | mark);
> > - if (last) {
> > - hm->body.len = processed;
> > - hm->message.len = hlen + processed;
> > - c->pfn_data = NULL;
> > - if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
> > - // MG_INFO(("LAST, mark: %lx", mark));
> > - // mg_hexdump(c->recv.buf, c->recv.len);
> > - }
> > +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> > + socklen_t *len) {
> > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> > + do {
> > + memset(usa, 0, sizeof(*usa));
> > + fd = accept(sock, &usa->sa, len);
> > + } while (MG_SOCK_INTR(fd));
> > + return fd;
> > }
> >
> > -static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
> > - struct mg_http_message *hm, bool *next) {
> > - size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> > - size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
> > - hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
> > - if (processed <= hm->chunk.len && !deleted) {
> > - hm->chunk.len -= processed;
> > - hm->chunk.ptr += processed;
> > - }
> > - left = hm->body.len < processed ? 0 : hm->body.len - processed;
> > - if (hm->chunk.len > left) hm->chunk.len = left;
> > - if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
> > - processed += hm->chunk.len;
> > - deleted = ((size_t) c->pfn_data) & MG_DMARK; // Re-evaluate after user 
> call
> > - if (processed >= hm->body.len) { // Last, 0-len chunk
> > - hm->chunk.len = 0; // Reset length
> > - mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
> > - c->pfn_data = NULL; // Reset processed counter
> > - if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = 
> true;
> > +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
> > + struct mg_connection *c = NULL;
> > + union usa usa;
> > + socklen_t sa_len = sizeof(usa);
> > + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> > + if (fd == MG_INVALID_SOCKET) {
> > +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
> > + // AzureRTOS, in non-block socket mode can mark listening socket 
> readable
> > + // even it is not. See comment for 'select' func implementation in
> > + // nx_bsd.c That's not an error, just should try later
> > + if (errno != EAGAIN)
> > +#endif
> > + MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1)));
> > +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> > + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
> > + } else if ((long) fd >= FD_SETSIZE) {
> > + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> > + closesocket(fd);
> > +#endif
> > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> > + MG_ERROR(("%lu OOM", lsn->id));
> > + closesocket(fd);
> > } else {
> > - c->pfn_data = (void *) (processed | deleted); // if it is set
> > + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > + c->fd = S2PTR(fd);
> > + MG_EPOLL_ADD(c);
> > + mg_set_non_blocking_mode(FD(c));
> > + setsockopts(c);
> > + c->is_accepted = 1;
> > + c->is_hexdumping = lsn->is_hexdumping;
> > + c->loc = lsn->loc;
> > + c->pfn = lsn->pfn;
> > + c->pfn_data = lsn->pfn_data;
> > + c->fn = lsn->fn;
> > + c->fn_data = lsn->fn_data;
> > + MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port,
> > + &c->rem, mg_print_ip_port, &c->loc));
> > + mg_call(c, MG_EV_OPEN, NULL);
> > + mg_call(c, MG_EV_ACCEPT, NULL);
> > }
> > }
> >
> > -static void http_cb(struct mg_connection *c, int ev, void *evd, void 
> *fnd) {
> > - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> > - struct mg_http_message hm;
> > - // mg_hexdump(c->recv.buf, c->recv.len);
> > - while (c->recv.buf != NULL && c->recv.len > 0) {
> > - bool next = false;
> > - int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> > - if (hlen < 0) {
> > - mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
> > - break;
> > - }
> > - if (c->is_resp) break; // Response is still generated
> > - if (hlen == 0) break; // Request is not buffered yet
> > - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
> > - hm.message.len = c->recv.len; // and closes now, deliver a MSG
> > - hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
> > - }
> > - if (mg_is_chunked(&hm)) {
> > - deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
> > - } else {
> > - deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
> > - }
> > - if (next) continue; // Chunks & request were deleted
> > - // Chunk events are delivered. If we have full body, deliver MSG
> > - if (c->recv.len < hm.message.len) break;
> > - if (c->is_accepted) c->is_resp = 1; // Start generating response
> > - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
> > - mg_iobuf_del(&c->recv, 0, hm.message.len);
> > - }
> > - }
> > - (void) evd, (void) fnd;
> > +static bool can_read(const struct mg_connection *c) {
> > + return c->is_full == false;
> > }
> >
> > -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char 
> *url,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> > - if (c != NULL) c->pfn = http_cb;
> > - return c;
> > +static bool can_write(const struct mg_connection *c) {
> > + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
> > }
> >
> > -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char 
> *url,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> > - if (c != NULL) c->pfn = http_cb;
> > - return c;
> > +static bool skip_iotest(const struct mg_connection *c) {
> > + return (c->is_closing || c->is_resolving || FD(c) == 
> MG_INVALID_SOCKET) ||
> > + (can_read(c) == false && can_write(c) == false);
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/iobuf.c"
> > -#endif
> > -
> > -
> > -
> > -
> > -// Not using memset for zeroing memory, cause it can be dropped by 
> compiler
> > -// See https://github.com/cesanta/mongoose/pull/1265
> > -static void zeromem(volatile unsigned char *buf, size_t len) {
> > - if (buf != NULL) {
> > - while (len--) *buf++ = 0;
> > +static void mg_iotest(struct mg_mgr *mgr, int ms) {
> > +#if MG_ENABLE_FREERTOS_TCP
> > + struct mg_connection *c;
> > + for (c = mgr->conns; c != NULL; c = c->next) {
> > + c->is_readable = c->is_writable = 0;
> > + if (skip_iotest(c)) continue;
> > + if (can_read(c))
> > + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> > + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> > + if (c->is_closing) ms = 1;
> > }
> > -}
> > -
> > -static size_t roundup(size_t size, size_t align) {
> > - return align == 0 ? size : (size + align - 1) / align * align;
> > -}
> > -
> > -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> > - int ok = 1;
> > - new_size = roundup(new_size, io->align);
> > - if (new_size == 0) {
> > - zeromem(io->buf, io->size);
> > - free(io->buf);
> > - io->buf = NULL;
> > - io->len = io->size = 0;
> > - } else if (new_size != io->size) {
> > - // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease 
> the
> > - // porting to some obscure platforms like FreeRTOS
> > - void *p = calloc(1, new_size);
> > - if (p != NULL) {
> > - size_t len = new_size < io->len ? new_size : io->len;
> > - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> > - zeromem(io->buf, io->size);
> > - free(io->buf);
> > - io->buf = (unsigned char *) p;
> > - io->size = new_size;
> > + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> > + for (c = mgr->conns; c != NULL; c = c->next) {
> > + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> > + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> > + c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> > + if (c->fd != MG_INVALID_SOCKET)
> > + FreeRTOS_FD_CLR(c->fd, mgr->ss,
> > + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> > + }
> > +#elif MG_ENABLE_EPOLL
> > + size_t max = 1;
> > + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > + c->is_readable = c->is_writable = 0;
> > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1, c->is_readable = 
> 1;
> > + if (can_write(c)) MG_EPOLL_MOD(c, 1);
> > + if (c->is_closing) ms = 1;
> > + max++;
> > + }
> > + struct epoll_event *evs = (struct epoll_event *) alloca(max * 
> sizeof(evs[0]));
> > + int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> > + for (int i = 0; i < n; i++) {
> > + struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
> > + if (evs[i].events & EPOLLERR) {
> > + mg_error(c, "socket error");
> > + } else if (c->is_readable == 0) {
> > + bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> > + bool wr = evs[i].events & EPOLLOUT;
> > + c->is_readable = can_read(c) && rd ? 1U : 0;
> > + c->is_writable = can_write(c) && wr ? 1U : 0;
> > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> > + }
> > + }
> > + (void) skip_iotest;
> > +#elif MG_ENABLE_POLL
> > + nfds_t n = 0;
> > + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
> > + struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> > + memset(fds, 0, n * sizeof(fds[0]));
> > + n = 0;
> > + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > + c->is_readable = c->is_writable = 0;
> > + if (skip_iotest(c)) {
> > + // Socket not valid, ignore
> > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> > + ms = 1; // Don't wait if TLS is ready
> > } else {
> > - ok = 0;
> > - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
> > + fds[n].fd = FD(c);
> > + if (can_read(c)) fds[n].events |= POLLIN;
> > + if (can_write(c)) fds[n].events |= POLLOUT;
> > + if (c->is_closing) ms = 1;
> > + n++;
> > }
> > }
> > - return ok;
> > -}
> > -
> > -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> > - io->buf = NULL;
> > - io->align = align;
> > - io->size = io->len = 0;
> > - return mg_iobuf_resize(io, size);
> > -}
> >
> > -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
> > - size_t len) {
> > - size_t new_size = roundup(io->len + len, io->align);
> > - mg_iobuf_resize(io, new_size); // Attempt to resize
> > - if (new_size != io->size) len = 0; // Resize failure, append nothing
> > - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len 
> - ofs);
> > - if (buf != NULL) memmove(io->buf + ofs, buf, len);
> > - if (ofs > io->len) io->len += ofs - io->len;
> > - io->len += len;
> > - return len;
> > -}
> > + // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> > + if (poll(fds, n, ms) < 0) {
> > +#if MG_ARCH == MG_ARCH_WIN32
> > + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
> > +#endif
> > + memset(fds, 0, n * sizeof(fds[0]));
> > + }
> > + n = 0;
> > + for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > + if (skip_iotest(c)) {
> > + // Socket not valid, ignore
> > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> > + c->is_readable = 1;
> > + } else {
> > + if (fds[n].revents & POLLERR) {
> > + mg_error(c, "socket error");
> > + } else {
> > + c->is_readable =
> > + (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> > + c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> > + }
> > + n++;
> > + }
> > + }
> > +#else
> > + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, 
> *tvp;
> > + struct mg_connection *c;
> > + fd_set rset, wset, eset;
> > + MG_SOCKET_TYPE maxfd = 0;
> > + int rc;
> >
> > -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> > - if (ofs > io->len) ofs = io->len;
> > - if (ofs + len > io->len) len = io->len - ofs;
> > - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs 
> - len);
> > - if (io->buf) zeromem(io->buf + io->len - len, len);
> > - io->len -= len;
> > - return len;
> > -}
> > + FD_ZERO(&rset);
> > + FD_ZERO(&wset);
> > + FD_ZERO(&eset);
> > + tvp = ms < 0 ? NULL : &tv;
> > + for (c = mgr->conns; c != NULL; c = c->next) {
> > + c->is_readable = c->is_writable = 0;
> > + if (skip_iotest(c)) continue;
> > + FD_SET(FD(c), &eset);
> > + if (can_read(c)) FD_SET(FD(c), &rset);
> > + if (can_write(c)) FD_SET(FD(c), &wset);
> > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
> > + if (FD(c) > maxfd) maxfd = FD(c);
> > + if (c->is_closing) ms = 1;
> > + }
> >
> > -void mg_iobuf_free(struct mg_iobuf *io) {
> > - mg_iobuf_resize(io, 0);
> > -}
> > + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) {
> > +#if MG_ARCH == MG_ARCH_WIN32
> > + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets
> > +#else
> > + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
> > +#endif
> > + FD_ZERO(&rset);
> > + FD_ZERO(&wset);
> > + FD_ZERO(&eset);
> > + }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/json.c"
> > + for (c = mgr->conns; c != NULL; c = c->next) {
> > + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> > + mg_error(c, "socket error");
> > + } else {
> > + c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
> > + c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
> > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> > + }
> > + }
> > #endif
> > +}
> >
> > +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
> > + socklen_t n = sizeof(usa[0].sin);
> > + bool success = false;
> >
> > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> > + (void) memset(&usa[0], 0, sizeof(usa[0]));
> > + usa[0].sin.sin_family = AF_INET;
> > + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1
> > + usa[1] = usa[0];
> >
> > -
> > -static const char *escapeseq(int esc) {
> > - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> > + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> > + (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> > + bind(sp[0], &usa[0].sa, n) == 0 && //
> > + bind(sp[1], &usa[1].sa, n) == 0 && //
> > + getsockname(sp[0], &usa[0].sa, &n) == 0 && //
> > + getsockname(sp[1], &usa[1].sa, &n) == 0 && //
> > + connect(sp[0], &usa[1].sa, n) == 0 && //
> > + connect(sp[1], &usa[0].sa, n) == 0) { //
> > + success = true;
> > + }
> > + if (!success) {
> > + if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> > + if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> > + }
> > + return success;
> > }
> >
> > -static char json_esc(int c, int esc) {
> > - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> > - for (p = esc1; *p != '\0'; p++) {
> > - if (*p == c) return esc2[p - esc1];
> > +// mg_wakeup() event handler
> > +static void wufn(struct mg_connection *c, int ev, void *ev_data) {
> > + if (ev == MG_EV_READ) {
> > + unsigned long *id = (unsigned long *) c->recv.buf;
> > + // MG_INFO(("Got data"));
> > + // mg_hexdump(c->recv.buf, c->recv.len);
> > + if (c->recv.len >= sizeof(*id)) {
> > + struct mg_connection *t;
> > + for (t = c->mgr->conns; t != NULL; t = t->next) {
> > + if (t->id == *id) {
> > + struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
> > + c->recv.len - sizeof(*id));
> > + mg_call(t, MG_EV_WAKEUP, &data);
> > + }
> > + }
> > + }
> > + c->recv.len = 0; // Consume received data
> > + } else if (ev == MG_EV_CLOSE) {
> > + closesocket(c->mgr->pipe); // When we're closing, close the other
> > + c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too
> > }
> > - return 0;
> > + (void) ev_data;
> > }
> >
> > -static int mg_pass_string(const char *s, int len) {
> > - int i;
> > - for (i = 0; i < len; i++) {
> > - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> > - i++;
> > - } else if (s[i] == '\0') {
> > - return MG_JSON_INVALID;
> > - } else if (s[i] == '"') {
> > - return i;
> > +bool mg_wakeup_init(struct mg_mgr *mgr) {
> > + bool ok = false;
> > + if (mgr->pipe == MG_INVALID_SOCKET) {
> > + union usa usa[2];
> > + MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> > + struct mg_connection *c = NULL;
> > + if (!mg_socketpair(sp, usa)) {
> > + MG_ERROR(("Cannot create socket pair"));
> > + } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) == NULL) {
> > + closesocket(sp[0]);
> > + closesocket(sp[1]);
> > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> > + } else {
> > + tomgaddr(&usa[0], &c->rem, false);
> > + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
> > + mgr->pipe = sp[0];
> > + ok = true;
> > }
> > }
> > - return MG_JSON_INVALID;
> > + return ok;
> > }
> >
> > -static double mg_atod(const char *p, int len, int *numlen) {
> > - double d = 0.0;
> > - int i = 0, sign = 1;
> > -
> > - // Sign
> > - if (i < len && *p == '-') {
> > - sign = -1, i++;
> > - } else if (i < len && *p == '+') {
> > - i++;
> > +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void 
> *buf,
> > + size_t len) {
> > + if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
> > + char *extended_buf = (char *) alloca(len + sizeof(conn_id));
> > + memcpy(extended_buf, &conn_id, sizeof(conn_id));
> > + memcpy(extended_buf + sizeof(conn_id), buf, len);
> > + send(mgr->pipe, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING);
> > + return true;
> > }
> > + return false;
> > +}
> >
> > - // Decimal
> > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> > - d *= 10.0;
> > - d += p[i] - '0';
> > - }
> > - d *= sign;
> > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> > + struct mg_connection *c, *tmp;
> > + uint64_t now;
> >
> > - // Fractional
> > - if (i < len && p[i] == '.') {
> > - double frac = 0.0, base = 0.1;
> > - i++;
> > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> > - frac += base * (p[i] - '0');
> > - base /= 10.0;
> > + mg_iotest(mgr, ms);
> > + now = mg_millis();
> > + mg_timer_poll(&mgr->timers, now);
> > +
> > + for (c = mgr->conns; c != NULL; c = tmp) {
> > + bool is_resp = c->is_resp;
> > + tmp = c->next;
> > + mg_call(c, MG_EV_POLL, &now);
> > + if (is_resp && !c->is_resp) {
> > + long n = 0;
> > + mg_call(c, MG_EV_READ, &n);
> > + }
> > + MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
> > + c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
> > + c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
> > + c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
> > + c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
> > + if (c->is_resolving || c->is_closing) {
> > + // Do nothing
> > + } else if (c->is_listening && c->is_udp == 0) {
> > + if (c->is_readable) accept_conn(mgr, c);
> > + } else if (c->is_connecting) {
> > + if (c->is_readable || c->is_writable) connect_conn(c);
> > + //} else if (c->is_tls_hs) {
> > + // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> > + } else {
> > + if (c->is_readable) read_conn(c);
> > + if (c->is_writable) write_conn(c);
> > }
> > - d += frac * sign;
> > - }
> >
> > - // Exponential
> > - if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> > - int j, exp = 0, minus = 0;
> > - i++;
> > - if (i < len && p[i] == '-') minus = 1, i++;
> > - if (i < len && p[i] == '+') i++;
> > - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> > - exp = exp * 10 + (p[i++] - '0');
> > - if (minus) exp = -exp;
> > - for (j = 0; j < exp; j++) d *= 10.0;
> > - for (j = 0; j < -exp; j++) d /= 10.0;
> > + if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> > + if (c->is_closing) close_conn(c);
> > }
> > -
> > - if (numlen != NULL) *numlen = i;
> > - return d;
> > }
> > +#endif
> >
> > -int mg_json_get(struct mg_str json, const char *path, int *toklen) {
> > - const char *s = json.ptr;
> > - int len = (int) json.len;
> > - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
> > - unsigned char nesting[MG_JSON_MAX_DEPTH];
> > - int i = 0; // Current offset in `s`
> > - int j = 0; // Offset in `s` we're looking for (return value)
> > - int depth = 0; // Current depth (nesting level)
> > - int ed = 0; // Expected depth
> > - int pos = 1; // Current position in `path`
> > - int ci = -1, ei = -1; // Current and expected index in array
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/ssi.c"
> > +#endif
> >
> > - if (toklen) *toklen = 0;
> > - if (path[0] != '$') return MG_JSON_INVALID;
> >
> > -#define MG_CHECKRET(x) \
> > - do { \
> > - if (depth == ed && path[pos] == '\0' && ci == ei) { \
> > - if (toklen) *toklen = i - j + 1; \
> > - return j; \
> > - } \
> > - } while (0)
> >
> > -// In the ascii table, the distance between `[` and `]` is 2.
> > -// Ditto for `{` and `}`. Hence +2 in the code below.
> > -#define MG_EOO(x) \
> > - do { \
> > - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
> > - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> > - depth--; \
> > - MG_CHECKRET(x); \
> > - } while (0)
> >
> > - for (i = 0; i < len; i++) {
> > - unsigned char c = ((unsigned char *) s)[i];
> > - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> > - switch (expecting) {
> > - case S_VALUE:
> > - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
> > - if (depth == ed) j = i;
> > - if (c == '{') {
> > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> > - if (depth == ed && path[pos] == '.' && ci == ei) {
> > - // If we start the object, reset array indices
> > - ed++, pos++, ci = ei = -1;
> > - }
> > - nesting[depth++] = c;
> > - expecting = S_KEY;
> > - break;
> > - } else if (c == '[') {
> > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> > - if (depth == ed && path[pos] == '[' && ei == ci) {
> > - ed++, pos++, ci = 0;
> > - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> > - ei *= 10;
> > - ei += path[pos] - '0';
> > - }
> > - if (path[pos] != 0) pos++;
> > - }
> > - nesting[depth++] = c;
> > - break;
> > - } else if (c == ']' && depth > 0) { // Empty array
> > - MG_EOO(']');
> > - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
> > - i += 3;
> > - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
> > - i += 3;
> > - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
> > - i += 4;
> > - } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> > - int numlen = 0;
> > - mg_atod(&s[i], len - i, &numlen);
> > - i += numlen - 1;
> > - } else if (c == '"') {
> > - int n = mg_pass_string(&s[i + 1], len - i - 1);
> > - if (n < 0) return n;
> > - i += n + 1;
> > - } else {
> > - return MG_JSON_INVALID;
> > - }
> > - MG_CHECKRET('V');
> > - if (depth == ed && ei >= 0) ci++;
> > - expecting = S_COMMA_OR_EOO;
> > - break;
> > +#ifndef MG_MAX_SSI_DEPTH
> > +#define MG_MAX_SSI_DEPTH 5
> > +#endif
> >
> > - case S_KEY:
> > - if (c == '"') {
> > - int n = mg_pass_string(&s[i + 1], len - i - 1);
> > - if (n < 0) return n;
> > - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> > - if (depth < ed) return MG_JSON_NOT_FOUND;
> > - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> > - // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
> > - // &s[i + 1], n, depth, ed);
> > - // NOTE(cpq): in the check sequence below is important.
> > - // strncmp() must go first: it fails fast if the remaining length of
> > - // the path is smaller than `n`.
> > - if (depth == ed && path[pos - 1] == '.' &&
> > - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> > - (path[pos + n] == '\0' || path[pos + n] == '.' ||
> > - path[pos + n] == '[')) {
> > - pos += n;
> > - }
> > - i += n + 1;
> > - expecting = S_COLON;
> > - } else if (c == '}') { // Empty object
> > - MG_EOO('}');
> > - expecting = S_COMMA_OR_EOO;
> > - } else {
> > - return MG_JSON_INVALID;
> > - }
> > - break;
> > +#ifndef MG_SSI_BUFSIZ
> > +#define MG_SSI_BUFSIZ 1024
> > +#endif
> >
> > - case S_COLON:
> > - if (c == ':') {
> > - expecting = S_VALUE;
> > +#if MG_ENABLE_SSI
> > +static char *mg_ssi(const char *path, const char *root, int depth) {
> > + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> > + FILE *fp = fopen(path, "rb");
> > + if (fp != NULL) {
> > + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> > + int ch, intag = 0;
> > + size_t len = 0;
> > + buf[0] = arg[0] = '\0';
> > + while ((ch = fgetc(fp)) != EOF) {
> > + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
> > + buf[len++] = (char) (ch & 0xff);
> > + buf[len] = '\0';
> > + if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> > + *p = (char *) path + strlen(path), *data;
> > + while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> > + mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
> > + if (depth < MG_MAX_SSI_DEPTH &&
> > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> > + mg_iobuf_add(&b, b.len, data, strlen(data));
> > + free(data);
> > + } else {
> > + MG_ERROR(("%s: file=%s error or too deep", path, arg));
> > + }
> > + } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> > + mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> > + if (depth < MG_MAX_SSI_DEPTH &&
> > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> > + mg_iobuf_add(&b, b.len, data, strlen(data));
> > + free(data);
> > + } else {
> > + MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> > + }
> > } else {
> > - return MG_JSON_INVALID;
> > + // Unknown SSI tag
> > + MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> > + mg_iobuf_add(&b, b.len, buf, len);
> > }
> > - break;
> > -
> > - case S_COMMA_OR_EOO:
> > - if (depth <= 0) {
> > - return MG_JSON_INVALID;
> > - } else if (c == ',') {
> > - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> > - } else if (c == ']' || c == '}') {
> > - MG_EOO('O');
> > - if (depth == ed && ei >= 0) ci++;
> > - } else {
> > - return MG_JSON_INVALID;
> > + intag = 0;
> > + len = 0;
> > + } else if (ch == '<') {
> > + intag = 1;
> > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> > + len = 0;
> > + buf[len++] = (char) (ch & 0xff);
> > + } else if (intag) {
> > + if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> > + intag = 0;
> > + } else if (len >= sizeof(buf) - 2) {
> > + MG_ERROR(("%s: SSI tag is too large", path));
> > + len = 0;
> > }
> > - break;
> > + buf[len++] = (char) (ch & 0xff);
> > + } else {
> > + buf[len++] = (char) (ch & 0xff);
> > + if (len >= sizeof(buf)) {
> > + mg_iobuf_add(&b, b.len, buf, len);
> > + len = 0;
> > + }
> > + }
> > }
> > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> > + if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
> > + fclose(fp);
> > }
> > - return MG_JSON_NOT_FOUND;
> > + (void) depth;
> > + (void) root;
> > + return (char *) b.buf;
> > }
> >
> > -bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
> > - int n, toklen, found = 0;
> > - if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> > - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) {
> > - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
> > - found = 1;
> > - }
> > - return found;
> > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> > + const char *fullpath) {
> > + const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
> > + char *data = mg_ssi(fullpath, root, 0);
> > + mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> > + free(data);
> > +}
> > +#else
> > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> > + const char *fullpath) {
> > + mg_http_reply(c, 501, NULL, "SSI not enabled");
> > + (void) root, (void) fullpath;
> > }
> > +#endif
> >
> > -bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
> > - int found = 0, off = mg_json_get(json, path, NULL);
> > - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
> > - if (v != NULL) *v = json.ptr[off] == 't';
> > - found = 1;
> > - }
> > - return found;
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/str.c"
> > +#endif
> > +
> > +
> > +struct mg_str mg_str_s(const char *s) {
> > + struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
> > + return str;
> > }
> >
> > -static bool json_unescape(const char *s, size_t len, char *to, size_t 
> n) {
> > - size_t i, j;
> > - for (i = 0, j = 0; i < len && j < n; i++, j++) {
> > - if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
> > - // \uXXXX escape. We could process a simple one-byte chars
> > - // \u00xx from the ASCII range. More complex chars would require
> > - // dragging in a UTF8 library, which is too much for us
> > - if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up
> > - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2);
> > +struct mg_str mg_str_n(const char *s, size_t n) {
> > + struct mg_str str = {(char *) s, n};
> > + return str;
> > +}
> >
> > - i += 5;
> > - } else if (s[i] == '\\' && i + 1 < len) {
> > - char c = json_esc(s[i + 1], 0);
> > - if (c == 0) return false;
> > - to[j] = c;
> > - i++;
> > - } else {
> > - to[j] = s[i];
> > - }
> > +static int mg_tolc(char c) {
> > + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
> > +}
> > +
> > +int mg_casecmp(const char *s1, const char *s2) {
> > + int diff = 0;
> > + do {
> > + int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
> > + diff = c - d;
> > + } while (diff == 0 && s1[-1] != '\0');
> > + return diff;
> > +}
> > +
> > +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> > + size_t i = 0;
> > + while (i < str1.len && i < str2.len) {
> > + int c1 = str1.buf[i];
> > + int c2 = str2.buf[i];
> > + if (c1 < c2) return -1;
> > + if (c1 > c2) return 1;
> > + i++;
> > }
> > - if (j >= n) return false;
> > - if (n > 0) to[j] = '\0';
> > - return true;
> > + if (i < str1.len) return 1;
> > + if (i < str2.len) return -1;
> > + return 0;
> > }
> >
> > -char *mg_json_get_str(struct mg_str json, const char *path) {
> > - char *result = NULL;
> > - int len = 0, off = mg_json_get(json, path, &len);
> > - if (off >= 0 && len > 1 && json.ptr[off] == '"') {
> > - if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> > - !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
> > - (size_t) len)) {
> > - free(result);
> > - result = NULL;
> > - }
> > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) {
> > + size_t i = 0;
> > + while (i < str1.len && i < str2.len) {
> > + int c1 = mg_tolc(str1.buf[i]);
> > + int c2 = mg_tolc(str2.buf[i]);
> > + if (c1 < c2) return -1;
> > + if (c1 > c2) return 1;
> > + i++;
> > }
> > - return result;
> > + if (i < str1.len) return 1;
> > + if (i < str2.len) return -1;
> > + return 0;
> > }
> >
> > -char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
> > - char *result = NULL;
> > - int len = 0, off = mg_json_get(json, path, &len);
> > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> > - (result = (char *) calloc(1, (size_t) len)) != NULL) {
> > - int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
> > - if (slen != NULL) *slen = k;
> > +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
> > + size_t i = 0, j = 0, ni = 0, nj = 0;
> > + if (caps) caps->buf = NULL, caps->len = 0;
> > + while (i < p.len || j < s.len) {
> > + if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] == 
> p.buf[i])) {
> > + if (caps == NULL) {
> > + } else if (p.buf[i] == '?') {
> > + caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap
> > + caps++, caps->buf = NULL, caps->len = 0; // Init next cap
> > + } else if (caps->buf != NULL && caps->len == 0) {
> > + caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize current cap
> > + caps++, caps->len = 0, caps->buf = NULL; // Init next cap
> > + }
> > + i++, j++;
> > + } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
> > + if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j]; // Init 
> cap
> > + ni = i++, nj = j + 1;
> > + } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' || s.buf[j] != 
> '/')) {
> > + i = ni, j = nj;
> > + if (caps && caps->buf == NULL && caps->len == 0) {
> > + caps--, caps->len = 0; // Restart previous cap
> > + }
> > + } else {
> > + return false;
> > + }
> > }
> > - return result;
> > + if (caps && caps->buf && caps->len == 0) {
> > + caps->len = (size_t) (&s.buf[j] - caps->buf);
> > + }
> > + return true;
> > }
> >
> > -char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
> > - char *result = NULL;
> > - int len = 0, off = mg_json_get(json, path, &len);
> > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> > - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> > - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result);
> > - result[len / 2 - 1] = '\0';
> > - if (slen != NULL) *slen = len / 2 - 1;
> > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char 
> sep) {
> > + if (s.len == 0 || s.buf == NULL) {
> > + return false; // Empty string, nothing to span - fail
> > + } else {
> > + size_t len = 0;
> > + while (len < s.len && s.buf[len] != sep) len++; // Find separator
> > + if (a) *a = mg_str_n(s.buf, len); // Init a
> > + if (b) *b = mg_str_n(s.buf + len, s.len - len); // Init b
> > + if (b && len < s.len) b->buf++, b->len--; // Skip separator
> > + return true;
> > }
> > - return result;
> > }
> >
> > -long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
> > - double dv;
> > - long result = dflt;
> > - if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> > - return result;
> > +bool mg_str_to_num(struct mg_str str, int base, void *val, size_t 
> val_len) {
> > + size_t i = 0, ndigits = 0;
> > + uint64_t max = val_len == sizeof(uint8_t) ? 0xFF
> > + : val_len == sizeof(uint16_t) ? 0xFFFF
> > + : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
> > + : (uint64_t) ~0;
> > + uint64_t result = 0;
> > + if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return false;
> > + if (base == 0 && str.len >= 2) {
> > + if (str.buf[i] == '0') {
> > + i++;
> > + base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
> > + if (base != 10) ++i;
> > + } else {
> > + base = 10;
> > + }
> > + }
> > + switch (base) {
> > + case 2:
> > + while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
> > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
> > + if (result > max/2) return false; // Overflow
> > + result *= 2;
> > + if (result > max - digit) return false; // Overflow
> > + result += digit;
> > + i++, ndigits++;
> > + }
> > + break;
> > + case 10:
> > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
> > + if (result > max/10) return false; // Overflow
> > + result *= 10;
> > + if (result > max - digit) return false; // Overflow
> > + result += digit;
> > + i++, ndigits++;
> > + }
> > + break;
> > + case 16:
> > + while (i < str.len) {
> > + char c = str.buf[i];
> > + uint64_t digit = (c >= '0' && c <= '9') ? (uint64_t) (c - '0')
> > + : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
> > + : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
> > + : (uint64_t) ~0;
> > + if (digit == (uint64_t) ~0) break;
> > + if (result > max/16) return false; // Overflow
> > + result *= 16;
> > + if (result > max - digit) return false; // Overflow
> > + result += digit;
> > + i++, ndigits++;
> > + }
> > + break;
> > + default:
> > + return false;
> > + }
> > + if (ndigits == 0) return false;
> > + if (i != str.len) return false;
> > + if (val_len == 1) {
> > + *((uint8_t *) val) = (uint8_t) result;
> > + } else if (val_len == 2) {
> > + *((uint16_t *) val) = (uint16_t) result;
> > + } else if (val_len == 4) {
> > + *((uint32_t *) val) = (uint32_t) result;
> > + } else {
> > + *((uint64_t *) val) = (uint64_t) result;
> > + }
> > + return true;
> > }
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/log.c"
> > +#line 1 "src/timer.c"
> > #endif
> >
> >
> >
> > +#define MG_TIMER_CALLED 4
> >
> > -
> > -static void default_logger(char c, void *param) {
> > - putchar(c);
> > - (void) c, (void) param;
> > -}
> > -
> > -static int s_level = MG_LL_INFO;
> > -static mg_pfn_t s_log_func = default_logger;
> > -static void *s_log_func_param = NULL;
> > -
> > -void mg_log_set_fn(mg_pfn_t fn, void *param) {
> > - s_log_func = fn;
> > - s_log_func_param = param;
> > -}
> > -
> > -static void logc(unsigned char c) {
> > - s_log_func((char) c, s_log_func_param);
> > -}
> > -
> > -static void logs(const char *buf, size_t len) {
> > - size_t i;
> > - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> > -}
> > -
> > -void mg_log_set(int log_level) {
> > - MG_DEBUG(("Setting log level to %d", log_level));
> > - s_level = log_level;
> > -}
> > -
> > -bool mg_log_prefix(int level, const char *file, int line, const char 
> *fname) {
> > - if (level <= s_level) {
> > - const char *p = strrchr(file, '/');
> > - char buf[41];
> > - size_t n;
> > - if (p == NULL) p = strrchr(file, '\\');
> > - n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(), 
> level,
> > - p == NULL ? file : p + 1, line, fname);
> > - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> > - while (n < sizeof(buf)) buf[n++] = ' ';
> > - logs(buf, n - 1);
> > - return true;
> > - } else {
> > - return false;
> > - }
> > +void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t 
> ms,
> > + unsigned flags, void (*fn)(void *), void *arg) {
> > + t->id = 0, t->period_ms = ms, t->expire = 0;
> > + t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> > + *head = t;
> > }
> >
> > -void mg_log(const char *fmt, ...) {
> > - va_list ap;
> > - va_start(ap, fmt);
> > - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> > - va_end(ap);
> > - logc((unsigned char) '\n');
> > +void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> > + while (*head && *head != t) head = &(*head)->next;
> > + if (*head) *head = t->next;
> > }
> >
> > -static unsigned char nibble(unsigned c) {
> > - return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> > +// t: expiration time, prd: period, now: current time. Return true if 
> expired
> > +bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> > + if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
> > + if (*t == 0) *t = now + prd; // Firt poll? Set expiration
> > + if (*t > now) return false; // Not expired yet, return
> > + *t = (now - *t) > prd ? now + prd : *t + prd; // Next expiration time
> > + return true; // Expired, return true
> > }
> >
> > -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> > -void mg_hexdump(const void *buf, size_t len) {
> > - const unsigned char *p = (const unsigned char *) buf;
> > - unsigned char ascii[16], alen = 0;
> > - size_t i;
> > - for (i = 0; i < len; i++) {
> > - if ((i % 16) == 0) {
> > - // Print buffered ascii chars
> > - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 
> 0;
> > - // Print hex address, then \t
> > - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> > - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
> > - }
> > - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. 
> c5
> > - logc(' '); // Space after hex number
> > - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf
> > +void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> > + struct mg_timer *t, *tmp;
> > + for (t = *head; t != NULL; t = tmp) {
> > + bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> > + !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
> > + bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> > + tmp = t->next;
> > + if (!once && !expired) continue;
> > + if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
> > + t->fn(t->arg);
> > + }
> > + t->flags |= MG_TIMER_CALLED;
> > }
> > - while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
> > - logs(" ", 2), logs((char *) ascii, 16), logc('\n');
> > }
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/md5.c"
> > +#line 1 "src/tls_aes128.c"
> > #endif
> > 
> +/******************************************************************************
> > + *
> > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE 
> GOOD OF ALL
> > + *
> > + * This is a simple and straightforward implementation of the AES 
> Rijndael
> > + * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The 
> focus
> > + * of this work was correctness & accuracy. It is written in 'C' 
> without any
> > + * particular focus upon optimization or speed. It should be endian 
> (memory
> > + * byte order) neutral since the few places that care are handled 
> explicitly.
> > + *
> > + * This implementation of Rijndael was created by Steven M. Gibson of 
> GRC.com.
> > + *
> > + * It is intended for general purpose use, but was written in support 
> of GRC's
> > + * reference implementation of the SQRL (Secure Quick Reliable Login) 
> client.
> > + *
> > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> > + *
> > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY 
> WARRANTY MADE
> > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN 
> RISK.
> > + *
> > + 
> *******************************************************************************/
> > +
> > 
> +/******************************************************************************/
> > +#define AES_DECRYPTION 1 // whether AES decryption is supported
> > 
> +/******************************************************************************/
> > +
> > +#define MG_ENCRYPT 1 // specify whether we're encrypting
> > +#define MG_DECRYPT 0 // or decrypting
> > +
> > +
> > +
> > +
> > +
> > +#if MG_TLS == MG_TLS_BUILTIN
> > 
> +/******************************************************************************
> > + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
> > + 
> ******************************************************************************/
> > +static void aes_init_keygen_tables(void);
> > +
> > 
> +/******************************************************************************
> > + * AES_SETKEY : called to expand the key for encryption or decryption
> > + 
> ******************************************************************************/
> > +static int aes_setkey(aes_context *ctx, // pointer to context
> > + int mode, // 1 or 0 for Encrypt/Decrypt
> > + const uchar *key, // AES input key
> > + uint keysize); // size in bytes (must be 16, 24, 32 for
> > + // 128, 192 or 256-bit keys respectively)
> > + // returns 0 for success
> > +
> > 
> +/******************************************************************************
> > + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data
> > + 
> ******************************************************************************/
> > +static int aes_cipher(aes_context *ctx, // pointer to context
> > + const uchar input[16], // 128-bit block to en/decipher
> > + uchar output[16]); // 128-bit output result block
> > + // returns 0 for success
> > +
> > 
> +/******************************************************************************
> > + * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES 
> ctx
> > + 
> ******************************************************************************/
> > +typedef struct {
> > + int mode; // cipher direction: encrypt/decrypt
> > + uint64_t len; // cipher data length processed so far
> > + uint64_t add_len; // total add data length
> > + uint64_t HL[16]; // precalculated lo-half HTable
> > + uint64_t HH[16]; // precalculated hi-half HTable
> > + uchar base_ectr[16]; // first counter-mode cipher output for tag
> > + uchar y[16]; // the current cipher-input IV|Counter value
> > + uchar buf[16]; // buf working value
> > + aes_context aes_ctx; // cipher context used
> > +} gcm_context;
> > +
> > 
> +/******************************************************************************
> > + * GCM_SETKEY : sets the GCM (and AES) keying material for use
> > + 
> ******************************************************************************/
> > +static int gcm_setkey(
> > + gcm_context *ctx, // caller-provided context ptr
> > + const uchar *key, // pointer to cipher key
> > + const uint keysize // size in bytes (must be 16, 24, 32 for
> > + // 128, 192 or 256-bit keys respectively)
> > +); // returns 0 for success
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_CRYPT_AND_TAG
> > + *
> > + * This either encrypts or decrypts the user-provided data and, either
> > + * way, generates an authentication tag of the requested length. It 
> must be
> > + * called with a GCM context whose key has already been set with 
> GCM_SETKEY.
> > + *
> > + * The user would typically call this explicitly to ENCRYPT a buffer of 
> data
> > + * and optional associated data, and produce its an authentication tag.
> > + *
> > + * To reverse the process the user would typically call the companion
> > + * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
> > + * authentication tag. The GCM_AUTH_DECRYPT function calls this function
> > + * to perform its decryption and tag generation, which it then compares.
> > + *
> > + 
> ******************************************************************************/
> > +static int gcm_crypt_and_tag(
> > + gcm_context *ctx, // gcm context with key already setup
> > + int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
> > + const uchar *iv, // pointer to the 12-byte initialization vector
> > + size_t iv_len, // byte length if the IV. should always be 12
> > + const uchar *add, // pointer to the non-ciphered additional data
> > + size_t add_len, // byte length of the additional AEAD data
> > + const uchar *input, // pointer to the cipher data source
> > + uchar *output, // pointer to the cipher data destination
> > + size_t length, // byte length of the cipher data
> > + uchar *tag, // pointer to the tag to be generated
> > + size_t tag_len); // byte length of the tag to be generated
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_START
> > + *
> > + * Given a user-provided GCM context, this initializes it, sets the 
> encryption
> > + * mode, and preprocesses the initialization vector and additional AEAD 
> data.
> > + *
> > + 
> ******************************************************************************/
> > +static int gcm_start(
> > + gcm_context *ctx, // pointer to user-provided GCM context
> > + int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0)
> > + const uchar *iv, // pointer to initialization vector
> > + size_t iv_len, // IV length in bytes (should == 12)
> > + const uchar *add, // pointer to additional AEAD data (NULL if none)
> > + size_t add_len); // length of additional AEAD data (bytes)
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_UPDATE
> > + *
> > + * This is called once or more to process bulk plaintext or ciphertext 
> data.
> > + * We give this some number of bytes of input and it returns the same 
> number
> > + * of output bytes. If called multiple times (which is fine) all but 
> the final
> > + * invocation MUST be called with length mod 16 == 0. (Only the final 
> call can
> > + * have a partial block length of < 128 bits.)
> > + *
> > + 
> ******************************************************************************/
> > +static int gcm_update(gcm_context *ctx, // pointer to user-provided GCM 
> context
> > + size_t length, // length, in bytes, of data to process
> > + const uchar *input, // pointer to source data
> > + uchar *output); // pointer to destination data
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_FINISH
> > + *
> > + * This is called once after all calls to GCM_UPDATE to finalize the 
> GCM.
> > + * It performs the final GHASH to produce the resulting authentication 
> TAG.
> > + *
> > + 
> ******************************************************************************/
> > +static int gcm_finish(
> > + gcm_context *ctx, // pointer to user-provided GCM context
> > + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0
> > + size_t tag_len); // length, in bytes, of the tag-receiving buf
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_ZERO_CTX
> > + *
> > + * The GCM context contains both the GCM context and the AES context.
> > + * This includes keying and key-related material which is security-
> > + * sensitive, so it MUST be zeroed after use. This function does that.
> > + *
> > + 
> ******************************************************************************/
> > +static void gcm_zero_ctx(gcm_context *ctx);
> > +
> > 
> +/******************************************************************************
> > + *
> > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE 
> GOOD OF ALL
> > + *
> > + * This is a simple and straightforward implementation of the AES 
> Rijndael
> > + * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The 
> focus
> > + * of this work was correctness & accuracy. It is written in 'C' 
> without any
> > + * particular focus upon optimization or speed. It should be endian 
> (memory
> > + * byte order) neutral since the few places that care are handled 
> explicitly.
> > + *
> > + * This implementation of Rijndael was created by Steven M. Gibson of 
> GRC.com.
> > + *
> > + * It is intended for general purpose use, but was written in support 
> of GRC's
> > + * reference implementation of the SQRL (Secure Quick Reliable Login) 
> client.
> > + *
> > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> > + *
> > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY 
> WARRANTY MADE
> > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN 
> RISK.
> > + *
> > + 
> *******************************************************************************/
> > +
> > +
> > +
> > +
> > +static int aes_tables_inited = 0; // run-once flag for performing key
> > + // expasion table generation (see below)
> > +/*
> > + * The following static local tables must be filled-in before the first 
> use of
> > + * the GCM or AES ciphers. They are used for the AES key 
> expansion/scheduling
> > + * and once built are read-only and thread safe. The "gcm_initialize" 
> function
> > + * must be called once during system initialization to populate these 
> arrays
> > + * for subsequent use by the AES key scheduler. If they have not been 
> built
> > + * before attempted use, an error will be returned to the caller.
> > + *
> > + * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. 
> Since
> > + * GCM uses AES in counter-mode, where the AES cipher output is XORed 
> with
> > + * the GCM input, we ONLY NEED AES encryption. Thus, to save space AES
> > + * decryption is typically disabled by setting AES_DECRYPTION to 0 in 
> aes.h.
> > + */
> > +// We always need our forward tables
> > +static uchar FSb[256]; // Forward substitution box (FSb)
> > +static uint32_t FT0[256]; // Forward key schedule assembly tables
> > +static uint32_t FT1[256];
> > +static uint32_t FT2[256];
> > +static uint32_t FT3[256];
> > +
> > +#if AES_DECRYPTION // We ONLY need reverse for decryption
> > +static uchar RSb[256]; // Reverse substitution box (RSb)
> > +static uint32_t RT0[256]; // Reverse key schedule assembly tables
> > +static uint32_t RT1[256];
> > +static uint32_t RT2[256];
> > +static uint32_t RT3[256];
> > +#endif /* AES_DECRYPTION */
> > +
> > +static uint32_t RCON[10]; // AES round constants
> >
> > +/*
> > + * Platform Endianness Neutralizing Load and Store Macro definitions
> > + * AES wants platform-neutral Little Endian (LE) byte ordering
> > + */
> > +#define GET_UINT32_LE(n, b, i) \
> > + { \
> > + (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) | \
> > + ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] << 24); \
> > + }
> >
> > -
> > -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> > -
> > -static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> > - if (MG_BIG_ENDIAN) {
> > - do {
> > - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> > - ((unsigned) buf[1] << 8 | buf[0]);
> > - *(uint32_t *) buf = t;
> > - buf += 4;
> > - } while (--longs);
> > - } else {
> > - (void) buf, (void) longs; // Little endian. Do nothing
> > +#define PUT_UINT32_LE(n, b, i) \
> > + { \
> > + (b)[(i)] = (uchar) ((n)); \
> > + (b)[(i) + 1] = (uchar) ((n) >> 8); \
> > + (b)[(i) + 2] = (uchar) ((n) >> 16); \
> > + (b)[(i) + 3] = (uchar) ((n) >> 24); \
> > }
> > -}
> >
> > -#define F1(x, y, z) (z ^ (x & (y ^ z)))
> > -#define F2(x, y, z) F1(z, x, y)
> > -#define F3(x, y, z) (x ^ y ^ z)
> > -#define F4(x, y, z) (y ^ (x | ~z))
> > +/*
> > + * AES forward and reverse encryption round processing macros
> > + */
> > +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
> > + { \
> > + X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
> > + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \
> > + \
> > + X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
> > + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \
> > + \
> > + X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
> > + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \
> > + \
> > + X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
> > + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \
> > + }
> >
> > -#define MD5STEP(f, w, x, y, z, data, s) \
> > - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> > +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
> > + { \
> > + X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
> > + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \
> > + \
> > + X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
> > + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \
> > + \
> > + X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
> > + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \
> > + \
> > + X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
> > + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \
> > + }
> >
> > /*
> > - * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
> > - * initialization constants.
> > + * These macros improve the readability of the key
> > + * generation initialization code by collapsing
> > + * repetitive common operations into logical pieces.
> > */
> > -void mg_md5_init(mg_md5_ctx *ctx) {
> > - ctx->buf[0] = 0x67452301;
> > - ctx->buf[1] = 0xefcdab89;
> > - ctx->buf[2] = 0x98badcfe;
> > - ctx->buf[3] = 0x10325476;
> > +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
> > +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
> > +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
> > +#define MIX(x, y) \
> > + { \
> > + y = ((y << 1) | (y >> 7)) & 0xFF; \
> > + x ^= y; \
> > + }
> > +#define CPY128 \
> > + { \
> > + *RK++ = *SK++; \
> > + *RK++ = *SK++; \
> > + *RK++ = *SK++; \
> > + *RK++ = *SK++; \
> > + }
> >
> > - ctx->bits[0] = 0;
> > - ctx->bits[1] = 0;
> > -}
> > 
> +/******************************************************************************
> > + *
> > + * AES_INIT_KEYGEN_TABLES
> > + *
> > + * Fills the AES key expansion tables allocated above with their static
> > + * data. This is not "per key" data, but static system-wide read-only
> > + * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once
> > + * at system initialization to setup the tables for all subsequent use.
> > + *
> > + 
> ******************************************************************************/
> > +void aes_init_keygen_tables(void) {
> > + int i, x, y, z; // general purpose iteration and computation locals
> > + int pow[256];
> > + int log[256];
> > +
> > + if (aes_tables_inited) return;
> > +
> > + // fill the 'pow' and 'log' tables over GF(2^8)
> > + for (i = 0, x = 1; i < 256; i++) {
> > + pow[i] = x;
> > + log[x] = i;
> > + x = (x ^ XTIME(x)) & 0xFF;
> > + }
> > + // compute the round constants
> > + for (i = 0, x = 1; i < 10; i++) {
> > + RCON[i] = (uint32_t) x;
> > + x = XTIME(x) & 0xFF;
> > + }
> > + // fill the forward and reverse substitution boxes
> > + FSb[0x00] = 0x63;
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > + RSb[0x63] = 0x00;
> > +#endif /* AES_DECRYPTION */
> > +
> > + for (i = 1; i < 256; i++) {
> > + x = y = pow[255 - log[i]];
> > + MIX(x, y);
> > + MIX(x, y);
> > + MIX(x, y);
> > + MIX(x, y);
> > + FSb[i] = (uchar) (x ^= 0x63);
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > + RSb[x] = (uchar) i;
> > +#endif /* AES_DECRYPTION */
> > + }
> > + // generate the forward and reverse key expansion tables
> > + for (i = 0; i < 256; i++) {
> > + x = FSb[i];
> > + y = XTIME(x) & 0xFF;
> > + z = (y ^ x) & 0xFF;
> >
> > -static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
> > - uint32_t a, b, c, d;
> > + FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x << 16) ^
> > + ((uint32_t) z << 24);
> >
> > - a = buf[0];
> > - b = buf[1];
> > - c = buf[2];
> > - d = buf[3];
> > + FT1[i] = ROTL8(FT0[i]);
> > + FT2[i] = ROTL8(FT1[i]);
> > + FT3[i] = ROTL8(FT2[i]);
> >
> > - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> > - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> > - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> > - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> > - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> > - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> > - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> > - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> > - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> > - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> > - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> > - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> > - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> > - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> > - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> > - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > + x = RSb[i];
> >
> > - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> > - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> > - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> > - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> > - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> > - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> > - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> > - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> > - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> > - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> > - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> > - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> > - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> > - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> > - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> > - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> > + RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x) << 8) ^
> > + ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
> >
> > - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> > - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> > - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> > - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> > - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> > - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> > - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> > - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> > - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> > - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> > - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> > - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> > - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> > - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> > - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> > - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> > + RT1[i] = ROTL8(RT0[i]);
> > + RT2[i] = ROTL8(RT1[i]);
> > + RT3[i] = ROTL8(RT2[i]);
> > +#endif /* AES_DECRYPTION */
> > + }
> > + aes_tables_inited = 1; // flag that the tables have been generated
> > +} // to permit subsequent use of the AES cipher
> > +
> > 
> +/******************************************************************************
> > + *
> > + * AES_SET_ENCRYPTION_KEY
> > + *
> > + * This is called by 'aes_setkey' when we're establishing a key for
> > + * subsequent encryption. We give it a pointer to the encryption
> > + * context, a pointer to the key, and the key's length in bytes.
> > + * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
> > + *
> > + 
> ******************************************************************************/
> > +static int aes_set_encryption_key(aes_context *ctx, const uchar *key,
> > + uint keysize) {
> > + uint i; // general purpose iteration local
> > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
> > +
> > + for (i = 0; i < (keysize >> 2); i++) {
> > + GET_UINT32_LE(RK[i], key, i << 2);
> > + }
> >
> > - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> > - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> > - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> > - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> > - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> > - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> > - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> > - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> > - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> > - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> > - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> > - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> > - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> > - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> > - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> > - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> > + switch (ctx->rounds) {
> > + case 10:
> > + for (i = 0; i < 10; i++, RK += 4) {
> > + RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
> > + ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
> > +
> > + RK[5] = RK[1] ^ RK[4];
> > + RK[6] = RK[2] ^ RK[5];
> > + RK[7] = RK[3] ^ RK[6];
> > + }
> > + break;
> >
> > - buf[0] += a;
> > - buf[1] += b;
> > - buf[2] += c;
> > - buf[3] += d;
> > -}
> > + case 12:
> > + for (i = 0; i < 8; i++, RK += 6) {
> > + RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
> > + ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
> > +
> > + RK[7] = RK[1] ^ RK[6];
> > + RK[8] = RK[2] ^ RK[7];
> > + RK[9] = RK[3] ^ RK[8];
> > + RK[10] = RK[4] ^ RK[9];
> > + RK[11] = RK[5] ^ RK[10];
> > + }
> > + break;
> >
> > -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t 
> len) {
> > - uint32_t t;
> > + case 14:
> > + for (i = 0; i < 7; i++, RK += 8) {
> > + RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
> > + ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
> > +
> > + RK[9] = RK[1] ^ RK[8];
> > + RK[10] = RK[2] ^ RK[9];
> > + RK[11] = RK[3] ^ RK[10];
> > +
> > + RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
> > + ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
> > +
> > + RK[13] = RK[5] ^ RK[12];
> > + RK[14] = RK[6] ^ RK[13];
> > + RK[15] = RK[7] ^ RK[14];
> > + }
> > + break;
> >
> > - t = ctx->bits[0];
> > - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
> > - ctx->bits[1] += (uint32_t) len >> 29;
> > + default:
> > + return -1;
> > + }
> > + return (0);
> > +}
> > +
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > +
> > 
> +/******************************************************************************
> > + *
> > + * AES_SET_DECRYPTION_KEY
> > + *
> > + * This is called by 'aes_setkey' when we're establishing a
> > + * key for subsequent decryption. We give it a pointer to
> > + * the encryption context, a pointer to the key, and the key's
> > + * length in bits. Valid lengths are: 128, 192, or 256 bits.
> > + *
> > + 
> ******************************************************************************/
> > +static int aes_set_decryption_key(aes_context *ctx, const uchar *key,
> > + uint keysize) {
> > + int i, j;
> > + aes_context cty; // a calling aes context for set_encryption_key
> > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
> > + uint32_t *SK;
> > + int ret;
> >
> > - t = (t >> 3) & 0x3f;
> > + cty.rounds = ctx->rounds; // initialize our local aes context
> > + cty.rk = cty.buf; // round count and key buf pointer
> >
> > - if (t) {
> > - unsigned char *p = (unsigned char *) ctx->in + t;
> > + if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0) return 
> (ret);
> >
> > - t = 64 - t;
> > - if (len < t) {
> > - memcpy(p, buf, len);
> > - return;
> > + SK = cty.rk + cty.rounds * 4;
> > +
> > + CPY128 // copy a 128-bit block from *SK to *RK
> > +
> > + for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
> > + for (j = 0; j < 4; j++, SK++) {
> > + *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
> > + RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
> > }
> > - memcpy(p, buf, t);
> > - mg_byte_reverse(ctx->in, 16);
> > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > - buf += t;
> > - len -= t;
> > + }
> > + CPY128 // copy a 128-bit block from *SK to *RK
> > + memset(&cty, 0, sizeof(aes_context)); // clear local aes context
> > + return (0);
> > +}
> > +
> > +#endif /* AES_DECRYPTION */
> > +
> > 
> +/******************************************************************************
> > + *
> > + * AES_SETKEY
> > + *
> > + * Invoked to establish the key schedule for subsequent 
> encryption/decryption
> > + *
> > + 
> ******************************************************************************/
> > +static int aes_setkey(aes_context *ctx, // AES context provided by our 
> caller
> > + int mode, // ENCRYPT or DECRYPT flag
> > + const uchar *key, // pointer to the key
> > + uint keysize) // key length in bytes
> > +{
> > + // since table initialization is not thread safe, we could either add
> > + // system-specific mutexes and init the AES key generation tables on
> > + // demand, or ask the developer to simply call "gcm_initialize" once 
> during
> > + // application startup before threading begins. That's what we choose.
> > + if (!aes_tables_inited) return (-1); // fail the call when not inited.
> > +
> > + ctx->mode = mode; // capture the key type we're creating
> > + ctx->rk = ctx->buf; // initialize our round key pointer
> > +
> > + switch (keysize) // set the rounds count based upon the keysize
> > + {
> > + case 16:
> > + ctx->rounds = 10;
> > + break; // 16-byte, 128-bit key
> > + case 24:
> > + ctx->rounds = 12;
> > + break; // 24-byte, 192-bit key
> > + case 32:
> > + ctx->rounds = 14;
> > + break; // 32-byte, 256-bit key
> > + default:
> > + return (-1);
> > }
> >
> > - while (len >= 64) {
> > - memcpy(ctx->in, buf, 64);
> > - mg_byte_reverse(ctx->in, 16);
> > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > - buf += 64;
> > - len -= 64;
> > +#if AES_DECRYPTION
> > + if (mode == MG_DECRYPT) // expand our key for encryption or decryption
> > + return (aes_set_decryption_key(ctx, key, keysize));
> > + else /* MG_ENCRYPT */
> > +#endif /* AES_DECRYPTION */
> > + return (aes_set_encryption_key(ctx, key, keysize));
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * AES_CIPHER
> > + *
> > + * Perform AES encryption and decryption.
> > + * The AES context will have been setup with the encryption mode
> > + * and all keying information appropriate for the task.
> > + *
> > + 
> ******************************************************************************/
> > +static int aes_cipher(aes_context *ctx, const uchar input[16],
> > + uchar output[16]) {
> > + int i;
> > + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals
> > +
> > + RK = ctx->rk;
> > +
> > + GET_UINT32_LE(X0, input, 0);
> > + X0 ^= *RK++; // load our 128-bit
> > + GET_UINT32_LE(X1, input, 4);
> > + X1 ^= *RK++; // input buffer in a storage
> > + GET_UINT32_LE(X2, input, 8);
> > + X2 ^= *RK++; // memory endian-neutral way
> > + GET_UINT32_LE(X3, input, 12);
> > + X3 ^= *RK++;
> > +
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > +
> > + if (ctx->mode == MG_DECRYPT) {
> > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> > + AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> > + }
> > +
> > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> > +
> > + X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
> > + ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
> > +
> > + X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
> > + ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
> > +
> > + X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
> > + ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
> > +
> > + X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
> > + ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
> > + } else /* MG_ENCRYPT */
> > + {
> > +#endif /* AES_DECRYPTION */
> > +
> > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> > + AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> > + }
> > +
> > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> > +
> > + X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
> > + ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
> > +
> > + X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
> > + ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
> > +
> > + X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
> > + ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
> > +
> > + X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
> > + ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
> > + ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
> > + ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
> > +
> > +#if AES_DECRYPTION // whether AES decryption is supported
> > }
> > +#endif /* AES_DECRYPTION */
> > +
> > + PUT_UINT32_LE(X0, output, 0);
> > + PUT_UINT32_LE(X1, output, 4);
> > + PUT_UINT32_LE(X2, output, 8);
> > + PUT_UINT32_LE(X3, output, 12);
> > +
> > + return (0);
> > +}
> > +/* end of aes.c */
> > 
> +/******************************************************************************
> > + *
> > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE 
> GOOD OF ALL
> > + *
> > + * This is a simple and straightforward implementation of AES-GCM 
> authenticated
> > + * encryption. The focus of this work was correctness & accuracy. It is 
> written
> > + * in straight 'C' without any particular focus upon optimization or 
> speed. It
> > + * should be endian (memory byte order) neutral since the few places 
> that care
> > + * are handled explicitly.
> > + *
> > + * This implementation of AES-GCM was created by Steven M. Gibson of 
> GRC.com.
> > + *
> > + * It is intended for general purpose use, but was written in support 
> of GRC's
> > + * reference implementation of the SQRL (Secure Quick Reliable Login) 
> client.
> > + *
> > + * See: 
> http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> > + * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
> > + * gcm/gcm-revised-spec.pdf
> > + *
> > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY 
> WARRANTY MADE
> > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN 
> RISK.
> > + *
> > + 
> *******************************************************************************/
> > +
> > 
> +/******************************************************************************
> > + * ==== IMPLEMENTATION WARNING ====
> > + *
> > + * This code was developed for use within SQRL's fixed environmnent. 
> Thus, it
> > + * is somewhat less "general purpose" than it would be if it were 
> designed as
> > + * a general purpose AES-GCM library. Specifically, it bothers with 
> almost NO
> > + * error checking on parameter limits, buffer bounds, etc. It assumes 
> that it
> > + * is being invoked by its author or by someone who understands the 
> values it
> > + * expects to receive. Its behavior will be undefined otherwise.
> > + *
> > + * All functions that might fail are defined to return 'ints' to 
> indicate a
> > + * problem. Most do not do so now. But this allows for error 
> propagation out
> > + * of internal functions if robust error checking should ever be 
> desired.
> > + *
> > + 
> ******************************************************************************/
> > +
> > +/* Calculating the "GHASH"
> > + *
> > + * There are many ways of calculating the so-called GHASH in software, 
> each with
> > + * a traditional size vs performance tradeoff. The GHASH (Galois field 
> hash) is
> > + * an intriguing construction which takes two 128-bit strings (also the 
> cipher's
> > + * block size and the fundamental operation size for the system) and 
> hashes them
> > + * into a third 128-bit result.
> > + *
> > + * Many implementation solutions have been worked out that use large 
> precomputed
> > + * table lookups in place of more time consuming bit fiddling, and this 
> approach
> > + * can be scaled easily upward or downward as needed to change the 
> time/space
> > + * tradeoff. It's been studied extensively and there's a solid body of 
> theory
> > + * and practice. For example, without using any lookup tables an 
> implementation
> > + * might obtain 119 cycles per byte throughput, whereas using a simple, 
> though
> > + * large, key-specific 64 kbyte 8-bit lookup table the performance 
> jumps to 13
> > + * cycles per byte.
> > + *
> > + * And Intel's processors have, since 2010, included an instruction 
> which does
> > + * the entire 128x128->128 bit job in just several 64x64->128 bit 
> pieces.
> > + *
> > + * Since SQRL is interactive, and only processing a few 128-bit blocks, 
> I've
> > + * settled upon a relatively slower but appealing small-table 
> compromise which
> > + * folds a bunch of not only time consuming but also bit twiddling into 
> a simple
> > + * 16-entry table which is attributed to Victor Shoup's 1996 work while 
> at
> > + * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on
> > + * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf
> > + * See, also section 4.1 of the "gcm-revised-spec" cited above.
> > + */
> >
> > - memcpy(ctx->in, buf, len);
> > -}
> > +/*
> > + * This 16-entry table of pre-computed constants is used by the
> > + * GHASH multiplier to improve over a strictly table-free but
> > + * significantly slower 128x128 bit multiple within GF(2^128).
> > + */
> > +static const uint64_t last4[16] = {
> > + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
> > + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
> >
> > -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> > - unsigned count;
> > - unsigned char *p;
> > - uint32_t *a;
> > +/*
> > + * Platform Endianness Neutralizing Load and Store Macro definitions
> > + * GCM wants platform-neutral Big Endian (BE) byte ordering
> > + */
> > +#define GET_UINT32_BE(n, b, i) \
> > + { \
> > + (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] << 16) | \
> > + ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]); \
> > + }
> >
> > - count = (ctx->bits[0] >> 3) & 0x3F;
> > +#define PUT_UINT32_BE(n, b, i) \
> > + { \
> > + (b)[(i)] = (uchar) ((n) >> 24); \
> > + (b)[(i) + 1] = (uchar) ((n) >> 16); \
> > + (b)[(i) + 2] = (uchar) ((n) >> 8); \
> > + (b)[(i) + 3] = (uchar) ((n)); \
> > + }
> >
> > - p = ctx->in + count;
> > - *p++ = 0x80;
> > - count = 64 - 1 - count;
> > - if (count < 8) {
> > - memset(p, 0, count);
> > - mg_byte_reverse(ctx->in, 16);
> > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > - memset(ctx->in, 0, 56);
> > - } else {
> > - memset(p, 0, count - 8);
> > 
> +/******************************************************************************
> > + *
> > + * GCM_INITIALIZE
> > + *
> > + * Must be called once to initialize the GCM library.
> > + *
> > + * At present, this only calls the AES keygen table generator, which 
> expands
> > + * the AES keying tables for use. This is NOT A THREAD-SAFE function, 
> so it
> > + * MUST be called during system initialization before a multi-threading
> > + * environment is running.
> > + *
> > + 
> ******************************************************************************/
> > +int mg_gcm_initialize(void) {
> > + aes_init_keygen_tables();
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_MULT
> > + *
> > + * Performs a GHASH operation on the 128-bit input vector 'x', setting
> > + * the 128-bit output vector to 'x' times H using our precomputed 
> tables.
> > + * 'x' and 'output' are seen as elements of GCM's GF(2^128) Galois 
> field.
> > + *
> > + 
> ******************************************************************************/
> > +static void gcm_mult(gcm_context *ctx, // pointer to established context
> > + const uchar x[16], // pointer to 128-bit input vector
> > + uchar output[16]) // pointer to 128-bit output vector
> > +{
> > + int i;
> > + uchar lo, hi, rem;
> > + uint64_t zh, zl;
> > +
> > + lo = (uchar) (x[15] & 0x0f);
> > + hi = (uchar) (x[15] >> 4);
> > + zh = ctx->HH[lo];
> > + zl = ctx->HL[lo];
> > +
> > + for (i = 15; i >= 0; i--) {
> > + lo = (uchar) (x[i] & 0x0f);
> > + hi = (uchar) (x[i] >> 4);
> > +
> > + if (i != 15) {
> > + rem = (uchar) (zl & 0x0f);
> > + zl = (zh << 60) | (zl >> 4);
> > + zh = (zh >> 4);
> > + zh ^= (uint64_t) last4[rem] << 48;
> > + zh ^= ctx->HH[lo];
> > + zl ^= ctx->HL[lo];
> > + }
> > + rem = (uchar) (zl & 0x0f);
> > + zl = (zh << 60) | (zl >> 4);
> > + zh = (zh >> 4);
> > + zh ^= (uint64_t) last4[rem] << 48;
> > + zh ^= ctx->HH[hi];
> > + zl ^= ctx->HL[hi];
> > }
> > - mg_byte_reverse(ctx->in, 14);
> > + PUT_UINT32_BE(zh >> 32, output, 0);
> > + PUT_UINT32_BE(zh, output, 4);
> > + PUT_UINT32_BE(zl >> 32, output, 8);
> > + PUT_UINT32_BE(zl, output, 12);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_SETKEY
> > + *
> > + * This is called to set the AES-GCM key. It initializes the AES key
> > + * and populates the gcm context's pre-calculated HTables.
> > + *
> > + 
> ******************************************************************************/
> > +static int gcm_setkey(
> > + gcm_context *ctx, // pointer to caller-provided gcm context
> > + const uchar *key, // pointer to the AES encryption key
> > + const uint keysize) // size in bytes (must be 16, 24, 32 for
> > + // 128, 192 or 256-bit keys respectively)
> > +{
> > + int ret, i, j;
> > + uint64_t hi, lo;
> > + uint64_t vl, vh;
> > + unsigned char h[16];
> > +
> > + memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided GCM 
> context
> > + memset(h, 0, 16); // initialize the block to encrypt
> > +
> > + // encrypt the null 128-bit block to generate a key-based value
> > + // which is then used to initialize our GHASH lookup tables
> > + if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize)) != 0)
> > + return (ret);
> > + if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
> > +
> > + GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian
> > + GET_UINT32_BE(lo, h, 4);
> > + vh = (uint64_t) hi << 32 | lo;
> > +
> > + GET_UINT32_BE(hi, h, 8);
> > + GET_UINT32_BE(lo, h, 12);
> > + vl = (uint64_t) hi << 32 | lo;
> > +
> > + ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128)
> > + ctx->HH[8] = vh;
> > + ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128)
> > + ctx->HL[0] = 0;
> > +
> > + for (i = 4; i > 0; i >>= 1) {
> > + uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
> > + vl = (vh << 63) | (vl >> 1);
> > + vh = (vh >> 1) ^ ((uint64_t) T << 32);
> > + ctx->HL[i] = vl;
> > + ctx->HH[i] = vh;
> > + }
> > + for (i = 2; i < 16; i <<= 1) {
> > + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
> > + vh = *HiH;
> > + vl = *HiL;
> > + for (j = 1; j < i; j++) {
> > + HiH[j] = vh ^ ctx->HH[j];
> > + HiL[j] = vl ^ ctx->HL[j];
> > + }
> > + }
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH.
> > + *
> > + * SETKEY:
> > + *
> > + * START: Sets the Encryption/Decryption mode.
> > + * Accepts the initialization vector and additional data.
> > + *
> > + * UPDATE: Encrypts or decrypts the plaintext or ciphertext.
> > + *
> > + * FINISH: Performs a final GHASH to generate the authentication tag.
> > + *
> > + 
> ******************************************************************************
> > + *
> > + * GCM_START
> > + *
> > + * Given a user-provided GCM context, this initializes it, sets the 
> encryption
> > + * mode, and preprocesses the initialization vector and additional AEAD 
> data.
> > + *
> > + 
> ******************************************************************************/
> > +int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context
> > + int mode, // GCM_ENCRYPT or GCM_DECRYPT
> > + const uchar *iv, // pointer to initialization vector
> > + size_t iv_len, // IV length in bytes (should == 12)
> > + const uchar *add, // ptr to additional AEAD data (NULL if none)
> > + size_t add_len) // length of additional AEAD data (bytes)
> > +{
> > + int ret; // our error return if the AES encrypt fails
> > + uchar work_buf[16]; // XOR source built from provided IV if len != 16
> > + const uchar *p; // general purpose array pointer
> > + size_t use_len; // byte count to process, up to 16 bytes
> > + size_t i; // local loop iterator
> > +
> > + // since the context might be reused under the same key
> > + // we zero the working buffers for this next new process
> > + memset(ctx->y, 0x00, sizeof(ctx->y));
> > + memset(ctx->buf, 0x00, sizeof(ctx->buf));
> > + ctx->len = 0;
> > + ctx->add_len = 0;
> > +
> > + ctx->mode = mode; // set the GCM encryption/decryption mode
> > + ctx->aes_ctx.mode = MG_ENCRYPT; // GCM *always* runs AES in ENCRYPTION 
> mode
> > +
> > + if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV
> > + memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the 'y' buff
> > + ctx->y[15] = 1; // start "counting" from 1 (not 0)
> > + } else // if we don't have a 12-byte IV, we GHASH whatever we've been 
> given
> > + {
> > + memset(work_buf, 0x00, 16); // clear the working buffer
> > + PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into buffer
> > +
> > + p = iv;
> > + while (iv_len > 0) {
> > + use_len = (iv_len < 16) ? iv_len : 16;
> > + for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
> > + gcm_mult(ctx, ctx->y, ctx->y);
> > + iv_len -= use_len;
> > + p += use_len;
> > + }
> > + for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
> > + gcm_mult(ctx, ctx->y, ctx->y);
> > + }
> > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0)
> > + return (ret);
> > +
> > + ctx->add_len = add_len;
> > + p = add;
> > + while (add_len > 0) {
> > + use_len = (add_len < 16) ? add_len : 16;
> > + for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
> > + gcm_mult(ctx, ctx->buf, ctx->buf);
> > + add_len -= use_len;
> > + p += use_len;
> > + }
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_UPDATE
> > + *
> > + * This is called once or more to process bulk plaintext or ciphertext 
> data.
> > + * We give this some number of bytes of input and it returns the same 
> number
> > + * of output bytes. If called multiple times (which is fine) all but 
> the final
> > + * invocation MUST be called with length mod 16 == 0. (Only the final 
> call can
> > + * have a partial block length of < 128 bits.)
> > + *
> > + 
> ******************************************************************************/
> > +int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context
> > + size_t length, // length, in bytes, of data to process
> > + const uchar *input, // pointer to source data
> > + uchar *output) // pointer to destination data
> > +{
> > + int ret; // our error return if the AES encrypt fails
> > + uchar ectr[16]; // counter-mode cipher output for XORing
> > + size_t use_len; // byte count to process, up to 16 bytes
> > + size_t i; // local loop iterator
> > +
> > + ctx->len += length; // bump the GCM context's running length count
> > +
> > + while (length > 0) {
> > + // clamp the length to process at 16 bytes
> > + use_len = (length < 16) ? length : 16;
> > +
> > + // increment the context's 128-bit IV||Counter 'y' vector
> > + for (i = 16; i > 12; i--)
> > + if (++ctx->y[i - 1] != 0) break;
> > +
> > + // encrypt the context's 'y' vector under the established key
> > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0) return (ret);
> > +
> > + // encrypt or decrypt the input to the output
> > + if (ctx->mode == MG_ENCRYPT) {
> > + for (i = 0; i < use_len; i++) {
> > + // XOR the cipher's ouptut vector (ectr) with our input
> > + output[i] = (uchar) (ectr[i] ^ input[i]);
> > + // now we mix in our data into the authentication hash.
> > + // if we're ENcrypting we XOR in the post-XOR (output)
> > + // results, but if we're DEcrypting we XOR in the input
> > + // data
> > + ctx->buf[i] ^= output[i];
> > + }
> > + } else {
> > + for (i = 0; i < use_len; i++) {
> > + // but if we're DEcrypting we XOR in the input data first,
> > + // i.e. before saving to ouput data, otherwise if the input
> > + // and output buffer are the same (inplace decryption) we
> > + // would not get the correct auth tag
> >
> > - a = (uint32_t *) ctx->in;
> > - a[14] = ctx->bits[0];
> > - a[15] = ctx->bits[1];
> > + ctx->buf[i] ^= input[i];
> >
> > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> > - mg_byte_reverse((unsigned char *) ctx->buf, 4);
> > - memcpy(digest, ctx->buf, 16);
> > - memset((char *) ctx, 0, sizeof(*ctx));
> > -}
> > -#endif
> > + // XOR the cipher's ouptut vector (ectr) with our input
> > + output[i] = (uchar) (ectr[i] ^ input[i]);
> > + }
> > + }
> > + gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/mqtt.c"
> > -#endif
> > + length -= use_len; // drop the remaining byte count to process
> > + input += use_len; // bump our input pointer forward
> > + output += use_len; // bump our output pointer forward
> > + }
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_FINISH
> > + *
> > + * This is called once after all calls to GCM_UPDATE to finalize the 
> GCM.
> > + * It performs the final GHASH to produce the resulting authentication 
> TAG.
> > + *
> > + 
> ******************************************************************************/
> > +int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context
> > + uchar *tag, // pointer to buffer which receives the tag
> > + size_t tag_len) // length, in bytes, of the tag-receiving buf
> > +{
> > + uchar work_buf[16];
> > + uint64_t orig_len = ctx->len * 8;
> > + uint64_t orig_add_len = ctx->add_len * 8;
> > + size_t i;
> >
> > + if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
> >
> > + if (orig_len || orig_add_len) {
> > + memset(work_buf, 0x00, 16);
> >
> > + PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
> > + PUT_UINT32_BE((orig_add_len), work_buf, 4);
> > + PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
> > + PUT_UINT32_BE((orig_len), work_buf, 12);
> >
> > + for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
> > + gcm_mult(ctx, ctx->buf, ctx->buf);
> > + for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
> > + }
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_CRYPT_AND_TAG
> > + *
> > + * This either encrypts or decrypts the user-provided data and, either
> > + * way, generates an authentication tag of the requested length. It 
> must be
> > + * called with a GCM context whose key has already been set with 
> GCM_SETKEY.
> > + *
> > + * The user would typically call this explicitly to ENCRYPT a buffer of 
> data
> > + * and optional associated data, and produce its an authentication tag.
> > + *
> > + * To reverse the process the user would typically call the companion
> > + * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
> > + * authentication tag. The GCM_AUTH_DECRYPT function calls this function
> > + * to perform its decryption and tag generation, which it then compares.
> > + *
> > + 
> ******************************************************************************/
> > +int gcm_crypt_and_tag(
> > + gcm_context *ctx, // gcm context with key already setup
> > + int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
> > + const uchar *iv, // pointer to the 12-byte initialization vector
> > + size_t iv_len, // byte length if the IV. should always be 12
> > + const uchar *add, // pointer to the non-ciphered additional data
> > + size_t add_len, // byte length of the additional AEAD data
> > + const uchar *input, // pointer to the cipher data source
> > + uchar *output, // pointer to the cipher data destination
> > + size_t length, // byte length of the cipher data
> > + uchar *tag, // pointer to the tag to be generated
> > + size_t tag_len) // byte length of the tag to be generated
> > +{ /*
> > + assuming that the caller has already invoked gcm_setkey to
> > + prepare the gcm context with the keying material, we simply
> > + invoke each of the three GCM sub-functions in turn...
> > + */
> > + gcm_start(ctx, mode, iv, iv_len, add, add_len);
> > + gcm_update(ctx, length, input, output);
> > + gcm_finish(ctx, tag, tag_len);
> > + return (0);
> > +}
> > +
> > 
> +/******************************************************************************
> > + *
> > + * GCM_ZERO_CTX
> > + *
> > + * The GCM context contains both the GCM context and the AES context.
> > + * This includes keying and key-related material which is security-
> > + * sensitive, so it MUST be zeroed after use. This function does that.
> > + *
> > + 
> ******************************************************************************/
> > +void gcm_zero_ctx(gcm_context *ctx) {
> > + // zero the context originally provided to us
> > + memset(ctx, 0, sizeof(gcm_context));
> > +}
> > +//
> > +// aes-gcm.c
> > +// Pods
> > +//
> > +// Created by Markus Kosmal on 20/11/14.
> > +//
> > +//
> >
> > +int mg_aes_gcm_encrypt(unsigned char *output, //
> > + const unsigned char *input, size_t input_length,
> > + const unsigned char *key, const size_t key_len,
> > + const unsigned char *iv, const size_t iv_len,
> > + unsigned char *aead, size_t aead_len, unsigned char *tag,
> > + const size_t tag_len) {
> > + int ret = 0; // our return value
> > + gcm_context ctx; // includes the AES context structure
> >
> > + gcm_setkey(&ctx, key, (uint) key_len);
> >
> > + ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead, aead_len, 
> input,
> > + output, input_length, tag, tag_len);
> >
> > -#define MQTT_CLEAN_SESSION 0x02
> > -#define MQTT_HAS_WILL 0x04
> > -#define MQTT_WILL_RETAIN 0x20
> > -#define MQTT_HAS_PASSWORD 0x40
> > -#define MQTT_HAS_USER_NAME 0x80
> > + gcm_zero_ctx(&ctx);
> >
> > -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t 
> flags,
> > - uint32_t len) {
> > - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> > - buf[0] = (uint8_t) ((cmd << 4) | flags);
> > - do {
> > - *vlen = len % 0x80;
> > - len /= 0x80;
> > - if (len > 0) *vlen |= 0x80;
> > - vlen++;
> > - } while (len > 0 && vlen < &buf[sizeof(buf)]);
> > - mg_send(c, buf, (size_t) (vlen - buf));
> > + return (ret);
> > }
> >
> > -static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> > - mg_send(c, &value, sizeof(value));
> > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char 
> *input,
> > + size_t input_length, const unsigned char *key,
> > + const size_t key_len, const unsigned char *iv,
> > + const size_t iv_len) {
> > + int ret = 0; // our return value
> > + gcm_context ctx; // includes the AES context structure
> > +
> > + size_t tag_len = 0;
> > + unsigned char *tag_buf = NULL;
> > +
> > + gcm_setkey(&ctx, key, (uint) key_len);
> > +
> > + ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0, input, 
> output,
> > + input_length, tag_buf, tag_len);
> > +
> > + gcm_zero_ctx(&ctx);
> > +
> > + return (ret);
> > }
> > +#endif
> > +// End of aes128 PD
> >
> > -void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts) {
> > - char rnd[10], client_id[21], zero = 0;
> > - struct mg_str cid = opts->client_id;
> > - uint32_t total_len = 7 + 1 + 2 + 2;
> > - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/tls_builtin.c"
> > +#endif
> >
> > - if (cid.len == 0) {
> > - mg_random(rnd, sizeof(rnd));
> > - mg_hex(rnd, sizeof(rnd), client_id);
> > - client_id[sizeof(client_id) - 1] = '\0';
> > - cid = mg_str(client_id);
> > - }
> >
> > - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1)
> > - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
> > - hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3); // Connection flags
> > - if (opts->user.len > 0) {
> > - total_len += 2 + (uint32_t) opts->user.len;
> > - hdr[7] |= MQTT_HAS_USER_NAME;
> > +
> > +
> > +#if MG_TLS == MG_TLS_BUILTIN
> > +
> > +/* TLS 1.3 Record Content Type (RFC8446 B.1) */
> > +#define MG_TLS_CHANGE_CIPHER 20
> > +#define MG_TLS_ALERT 21
> > +#define MG_TLS_HANDSHAKE 22
> > +#define MG_TLS_APP_DATA 23
> > +#define MG_TLS_HEARTBEAT 24
> > +
> > +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
> > +#define MG_TLS_CLIENT_HELLO 1
> > +#define MG_TLS_SERVER_HELLO 2
> > +#define MG_TLS_ENCRYPTED_EXTENSIONS 8
> > +#define MG_TLS_CERTIFICATE 11
> > +#define MG_TLS_CERTIFICATE_VERIFY 15
> > +#define MG_TLS_FINISHED 20
> > +
> > +// handshake is re-entrant, so we need to keep track of its state state 
> names
> > +// refer to RFC8446#A.1
> > +enum mg_tls_hs_state {
> > + // Client state machine:
> > + MG_TLS_STATE_CLIENT_START, // Send ClientHello
> > + MG_TLS_STATE_CLIENT_WAIT_SH, // Wait for ServerHello
> > + MG_TLS_STATE_CLIENT_WAIT_EE, // Wait for EncryptedExtensions
> > + MG_TLS_STATE_CLIENT_WAIT_CERT, // Wait for Certificate
> > + MG_TLS_STATE_CLIENT_WAIT_CV, // Wait for CertificateVerify
> > + MG_TLS_STATE_CLIENT_WAIT_FINISHED, // Wait for Finished
> > + MG_TLS_STATE_CLIENT_CONNECTED, // Done
> > +
> > + // Server state machine:
> > + MG_TLS_STATE_SERVER_START, // Wait for ClientHello
> > + MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finished
> > + MG_TLS_STATE_SERVER_CONNECTED // Done
> > +};
> > +
> > +// per-connection TLS data
> > +struct tls_data {
> > + enum mg_tls_hs_state state; // keep track of connection handshake 
> progress
> > +
> > + struct mg_iobuf send; // For the receive path, we're reusing c->rtls
> > + struct mg_iobuf recv; // While c->rtls contains full records, recv 
> reuses
> > + // the same underlying buffer but points at individual
> > + // decrypted messages
> > + uint8_t content_type; // Last received record content type
> > +
> > + mg_sha256_ctx sha256; // incremental SHA-256 hash for TLS handshake
> > +
> > + uint32_t sseq; // server sequence number, used in encryption
> > + uint32_t cseq; // client sequence number, used in decryption
> > +
> > + uint8_t random[32]; // client random from ClientHello
> > + uint8_t session_id[32]; // client session ID between the handshake 
> states
> > + uint8_t x25519_cli[32]; // client X25519 key between the handshake 
> states
> > + uint8_t x25519_sec[32]; // x25519 secret between the handshake states
> > +
> > + int skip_verification; // perform checks on server certificate?
> > + struct mg_str server_cert_der; // server certificate in DER format
> > + uint8_t server_key[32]; // server EC private key
> > + char hostname[254]; // server hostname (client extension)
> > +
> > + uint8_t certhash[32]; // certificate message hash
> > + uint8_t pubkey[64]; // server EC public key to verify cert
> > + uint8_t sighash[32]; // server EC public key to verify cert
> > +
> > + // keys for AES encryption
> > + uint8_t handshake_secret[32];
> > + uint8_t server_write_key[16];
> > + uint8_t server_write_iv[12];
> > + uint8_t server_finished_key[32];
> > + uint8_t client_write_key[16];
> > + uint8_t client_write_iv[12];
> > + uint8_t client_finished_key[32];
> > +};
> > +
> > +#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) | 
> MG_U8P(p)[1]))
> > +#define MG_LOAD_BE24(p) \
> > + ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) | 
> MG_U8P(p)[2]))
> > +#define MG_STORE_BE16(p, n) \
> > + do { \
> > + MG_U8P(p)[0] = ((n) >> 8U) & 255; \
> > + MG_U8P(p)[1] = (n) & 255; \
> > + } while (0)
> > +
> > +#define TLS_RECHDR_SIZE 5 // 1 byte type, 2 bytes version, 2 bytes 
> length
> > +#define TLS_MSGHDR_SIZE 4 // 1 byte type, 3 bytes length
> > +
> > +#if 1
> > +static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
> > + uint8_t *secret, size_t secretsz) {
> > + (void) label;
> > + (void) client_random;
> > + (void) secret;
> > + (void) secretsz;
> > +}
> > +#else
> > +#include <stdio.h>
> > +static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
> > + uint8_t *secret, size_t secretsz) {
> > + char *keylogfile = getenv("SSLKEYLOGFILE");
> > + if (keylogfile == NULL) {
> > + return;
> > }
> > - if (opts->pass.len > 0) {
> > - total_len += 2 + (uint32_t) opts->pass.len;
> > - hdr[7] |= MQTT_HAS_PASSWORD;
> > + FILE *f = fopen(keylogfile, "a");
> > + fprintf(f, "%s ", label);
> > + for (int i = 0; i < 32; i++) {
> > + fprintf(f, "%02x", client_random[i]);
> > }
> > - if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
> > - total_len +=
> > - 4 + (uint32_t) opts->will_topic.len + (uint32_t) 
> opts->will_message.len;
> > - hdr[7] |= MQTT_HAS_WILL;
> > + fprintf(f, " ");
> > + for (unsigned int i = 0; i < secretsz; i++) {
> > + fprintf(f, "%02x", secret[i]);
> > }
> > - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> > - if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
> > - total_len += (uint32_t) cid.len;
> > - if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U : 0);
> > + fprintf(f, "\n");
> > + fclose(f);
> > +}
> > +#endif
> >
> > - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
> > - mg_send(c, hdr, sizeof(hdr));
> > - // keepalive == 0 means "do not disconnect us!"
> > - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> > +// for derived tls keys we need SHA256([0]*32)
> > +static uint8_t zeros[32] = {0};
> > +static uint8_t zeros_sha256_digest[32] = {
> > + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
> > + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
> > + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
> >
> > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // V5 properties
> > - mg_send_u16(c, mg_htons((uint16_t) cid.len));
> > - mg_send(c, cid.ptr, cid.len);
> > +// helper to hexdump buffers inline
> > +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) 
> {
> > + MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
> > +}
> >
> > - if (hdr[7] & MQTT_HAS_WILL) {
> > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // will props
> > - mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
> > - mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
> > - mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
> > - mg_send(c, opts->will_message.ptr, opts->will_message.len);
> > +// helper utilities to parse ASN.1 DER
> > +struct mg_der_tlv {
> > + uint8_t type;
> > + uint32_t len;
> > + uint8_t *value;
> > +};
> > +
> > +// parse DER into a TLV record
> > +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv 
> *tlv) {
> > + if (dersz < 2) {
> > + return -1;
> > }
> > - if (opts->user.len > 0) {
> > - mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> > - mg_send(c, opts->user.ptr, opts->user.len);
> > + tlv->type = der[0];
> > + tlv->len = der[1];
> > + tlv->value = der + 2;
> > + if (tlv->len > 0x7f) {
> > + uint32_t i, n = tlv->len - 0x80;
> > + tlv->len = 0;
> > + for (i = 0; i < n; i++) {
> > + tlv->len = (tlv->len << 8) | (der[2 + i]);
> > + }
> > + tlv->value = der + 2 + n;
> > }
> > - if (opts->pass.len > 0) {
> > - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> > - mg_send(c, opts->pass.ptr, opts->pass.len);
> > + if (der + dersz < tlv->value + tlv->len) {
> > + return -1;
> > }
> > + return 0;
> > }
> >
> > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> > - struct mg_str data, int qos, bool retain) {
> > - uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)), zero 
> = 0;
> > - uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
> > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) 
> topic.ptr,
> > - (int) data.len, (char *) data.ptr));
> > - if (qos > 0) len += 2;
> > - if (c->is_mqtt5) len++;
> > - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
> > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
> > - mg_send(c, topic.ptr, topic.len);
> > - if (qos > 0) {
> > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> > +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid, size_t 
> oidsz,
> > + struct mg_der_tlv *tlv) {
> > + uint8_t *p, *end;
> > + struct mg_der_tlv child = {0, 0, NULL};
> > + if (mg_der_to_tlv(der, dersz, tlv) < 0) {
> > + return -1; // invalid DER
> > + } else if (tlv->type == 6) { // found OID, check value
> > + return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
> > + } else if ((tlv->type & 0x20) == 0) {
> > + return 0; // Primitive, but not OID: not found
> > + }
> > + // Constructed object: scan children
> > + p = tlv->value;
> > + end = tlv->value + tlv->len;
> > + while (end > p) {
> > + int r;
> > + mg_der_to_tlv(p, (size_t) (end - p), &child);
> > + r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
> > + if (r < 0) return -1; // error
> > + if (r > 0) return 1; // found OID!
> > + p = child.value + child.len;
> > }
> > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> > - mg_send(c, data.ptr, data.len);
> > + return 0; // not found
> > }
> >
> > -void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) 
> {
> > - uint8_t qos_ = qos & 3, zero = 0;
> > - uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5 ? 1 : 
> 0);
> > - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
> > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
> > - mg_send(c, topic.ptr, topic.len);
> > - mg_send(c, &qos_, sizeof(qos_));
> > +// Did we receive a full TLS record in the c->rtls buffer?
> > +static bool mg_tls_got_record(struct mg_connection *c) {
> > + return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
> > + c->rtls.len >=
> > + (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
> > }
> >
> > -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> > - struct mg_mqtt_message *m) {
> > - uint8_t lc = 0, *p, *end;
> > - uint32_t n = 0, len_len = 0;
> > +// Remove a single TLS record from the recv buffer
> > +static void mg_tls_drop_record(struct mg_connection *c) {
> > + struct mg_iobuf *rio = &c->rtls;
> > + uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
> > + mg_iobuf_del(rio, 0, n);
> > +}
> >
> > - memset(m, 0, sizeof(*m));
> > - m->dgram.ptr = (char *) buf;
> > - if (len < 2) return MQTT_INCOMPLETE;
> > - m->cmd = (uint8_t) (buf[0] >> 4);
> > - m->qos = (buf[0] >> 1) & 3;
> > +// Remove a single TLS message from decrypted buffer, remove the 
> wrapping
> > +// record if it was the last message within a record
> > +static void mg_tls_drop_message(struct mg_connection *c) {
> > + uint32_t len;
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (tls->recv.len == 0) {
> > + return;
> > + }
> > + len = MG_LOAD_BE24(tls->recv.buf + 1);
> > + mg_sha256_update(&tls->sha256, tls->recv.buf, len + TLS_MSGHDR_SIZE);
> > + tls->recv.buf += len + TLS_MSGHDR_SIZE;
> > + tls->recv.len -= len + TLS_MSGHDR_SIZE;
> > + if (tls->recv.len == 0) {
> > + mg_tls_drop_record(c);
> > + }
> > +}
> >
> > - n = len_len = 0;
> > - p = (uint8_t *) buf + 1;
> > - while ((size_t) (p - buf) < len) {
> > - lc = *((uint8_t *) p++);
> > - n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> > - len_len++;
> > - if (!(lc & 0x80)) break;
> > - if (len_len >= 4) return MQTT_MALFORMED;
> > +// TLS1.3 secret derivation based on the key label
> > +static void mg_tls_derive_secret(const char *label, uint8_t *key, 
> size_t keysz,
> > + uint8_t *data, size_t datasz, uint8_t *hash,
> > + size_t hashsz) {
> > + size_t labelsz = strlen(label);
> > + uint8_t secret[32];
> > + uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
> > + // TODO: assert lengths of label, key, data and hash
> > + if (labelsz > 0) memmove(packed + 3, label, labelsz);
> > + packed[3 + labelsz] = (uint8_t) datasz;
> > + if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
> > + packed[4 + labelsz + datasz] = 1;
> > +
> > + mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
> > + memmove(hash, secret, hashsz);
> > +}
> > +
> > +// at this point we have x25519 shared secret, we can generate a set of 
> derived
> > +// handshake encryption keys
> > +static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > +
> > + mg_sha256_ctx sha256;
> > + uint8_t early_secret[32];
> > + uint8_t pre_extract_secret[32];
> > + uint8_t hello_hash[32];
> > + uint8_t server_hs_secret[32];
> > + uint8_t client_hs_secret[32];
> > +
> > + mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
> > + mg_tls_derive_secret("tls13 derived", early_secret, 32, 
> zeros_sha256_digest,
> > + 32, pre_extract_secret, 32);
> > + mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
> > + sizeof(pre_extract_secret), tls->x25519_sec,
> > + sizeof(tls->x25519_sec));
> > + mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
> > +
> > + // mg_sha256_final is not idempotent, need to copy sha256 context to 
> calculate
> > + // the digest
> > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> > + mg_sha256_final(hello_hash, &sha256);
> > +
> > + mg_tls_hexdump("hello hash", hello_hash, 32);
> > + // derive keys needed for the rest of the handshake
> > + mg_tls_derive_secret("tls13 s hs traffic", tls->handshake_secret, 32,
> > + hello_hash, 32, server_hs_secret, 32);
> > + mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
> > + tls->server_write_key, 16);
> > + mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
> > + tls->server_write_iv, 12);
> > + mg_tls_derive_secret("tls13 finished", server_hs_secret, 32, NULL, 0,
> > + tls->server_finished_key, 32);
> > +
> > + mg_tls_derive_secret("tls13 c hs traffic", tls->handshake_secret, 32,
> > + hello_hash, 32, client_hs_secret, 32);
> > + mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
> > + tls->client_write_key, 16);
> > + mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
> > + tls->client_write_iv, 12);
> > + mg_tls_derive_secret("tls13 finished", client_hs_secret, 32, NULL, 0,
> > + tls->client_finished_key, 32);
> > +
> > + mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
> > + mg_tls_hexdump("s key", tls->server_write_key, 16);
> > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> > + mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
> > + mg_tls_hexdump("c key", tls->client_write_key, 16);
> > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> > +
> > + mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> > + server_hs_secret, 32);
> > + mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> > + client_hs_secret, 32);
> > +}
> > +
> > +static void mg_tls_generate_application_keys(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + uint8_t hash[32];
> > + uint8_t premaster_secret[32];
> > + uint8_t master_secret[32];
> > + uint8_t server_secret[32];
> > + uint8_t client_secret[32];
> > +
> > + mg_sha256_ctx sha256;
> > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> > + mg_sha256_final(hash, &sha256);
> > +
> > + mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
> > + zeros_sha256_digest, 32, premaster_secret, 32);
> > + mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
> > +
> > + mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32,
> > + server_secret, 32);
> > + mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
> > + tls->server_write_key, 16);
> > + mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
> > + tls->server_write_iv, 12);
> > + mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32,
> > + client_secret, 32);
> > + mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
> > + tls->client_write_key, 16);
> > + mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
> > + tls->client_write_iv, 12);
> > +
> > + mg_tls_hexdump("s ap traffic", server_secret, 32);
> > + mg_tls_hexdump("s key", tls->server_write_key, 16);
> > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> > + mg_tls_hexdump("c ap traffic", client_secret, 32);
> > + mg_tls_hexdump("c key", tls->client_write_key, 16);
> > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> > + tls->sseq = tls->cseq = 0;
> > +
> > + mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random, server_secret, 
> 32);
> > + mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random, client_secret, 
> 32);
> > +}
> > +
> > +// AES GCM encryption of the message + put encoded data into the write 
> buffer
> > +static void mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
> > + size_t msgsz, uint8_t msgtype) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *wio = &tls->send;
> > + uint8_t *outmsg;
> > + uint8_t *tag;
> > + size_t encsz = msgsz + 16 + 1;
> > + uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> > + (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
> > + uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> > + (uint8_t) ((encsz >> 8) & 0xff),
> > + (uint8_t) (encsz & 0xff)};
> > + uint8_t nonce[12];
> > +
> > + mg_gcm_initialize();
> > +
> > + if (c->is_client) {
> > + memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
> > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> > + } else {
> > + memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
> > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
> > }
> > - end = p + n;
> > - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> > - m->dgram.len = (size_t) (end - buf);
> >
> > - switch (m->cmd) {
> > - case MQTT_CMD_CONNACK:
> > - if (end - p < 2) return MQTT_MALFORMED;
> > - m->ack = p[1];
> > - break;
> > - case MQTT_CMD_PUBACK:
> > - case MQTT_CMD_PUBREC:
> > - case MQTT_CMD_PUBREL:
> > - case MQTT_CMD_PUBCOMP:
> > - case MQTT_CMD_SUBSCRIBE:
> > - case MQTT_CMD_SUBACK:
> > - case MQTT_CMD_UNSUBSCRIBE:
> > - case MQTT_CMD_UNSUBACK:
> > - if (p + 2 > end) return MQTT_MALFORMED;
> > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > - p += 2;
> > - break;
> > - case MQTT_CMD_PUBLISH: {
> > - if (p + 2 > end) return MQTT_MALFORMED;
> > - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > - m->topic.ptr = (char *) p + 2;
> > - p += 2 + m->topic.len;
> > - if (p > end) return MQTT_MALFORMED;
> > - if (m->qos > 0) {
> > - if (p + 2 > end) return MQTT_MALFORMED;
> > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> > - p += 2;
> > - }
> > - if (p > end) return MQTT_MALFORMED;
> > - if (version == 5 && p + 2 < end) p += 1 + p[0]; // Skip options
> > - if (p > end) return MQTT_MALFORMED;
> > - m->data.ptr = (char *) p;
> > - m->data.len = (size_t) (end - p);
> > - break;
> > + mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
> > + mg_iobuf_resize(wio, wio->len + encsz);
> > + outmsg = wio->buf + wio->len;
> > + tag = wio->buf + wio->len + msgsz + 1;
> > + memmove(outmsg, msg, msgsz);
> > + outmsg[msgsz] = msgtype;
> > + if (c->is_client) {
> > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->client_write_key,
> > + sizeof(tls->client_write_key), nonce, sizeof(nonce),
> > + associated_data, sizeof(associated_data), tag, 16);
> > + tls->cseq++;
> > + } else {
> > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->server_write_key,
> > + sizeof(tls->server_write_key), nonce, sizeof(nonce),
> > + associated_data, sizeof(associated_data), tag, 16);
> > + tls->sseq++;
> > + }
> > + wio->len += encsz;
> > +}
> > +
> > +// read an encrypted record, decrypt it in place
> > +static int mg_tls_recv_record(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *rio = &c->rtls;
> > + uint16_t msgsz;
> > + uint8_t *msg;
> > + uint8_t nonce[12];
> > + int r;
> > + if (tls->recv.len > 0) {
> > + return 0; /* some data from previous record is still present */
> > + }
> > + for (;;) {
> > + if (!mg_tls_got_record(c)) {
> > + return MG_IO_WAIT;
> > }
> > - default:
> > + if (rio->buf[0] == MG_TLS_APP_DATA) {
> > break;
> > + } else if (rio->buf[0] ==
> > + MG_TLS_CHANGE_CIPHER) { // Skip ChangeCipher messages
> > + mg_tls_drop_record(c);
> > + } else if (rio->buf[0] == MG_TLS_ALERT) { // Skip Alerts
> > + MG_INFO(("TLS ALERT packet received"));
> > + mg_tls_drop_record(c);
> > + } else {
> > + mg_error(c, "unexpected packet");
> > + return -1;
> > + }
> > }
> > - return MQTT_OK;
> > +
> > + mg_gcm_initialize();
> > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> > + msg = rio->buf + 5;
> > + if (c->is_client) {
> > + memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
> > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
> > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
> > + sizeof(tls->server_write_key), nonce, sizeof(nonce));
> > + tls->sseq++;
> > + } else {
> > + memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
> > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
> > + sizeof(tls->client_write_key), nonce, sizeof(nonce));
> > + tls->cseq++;
> > + }
> > + r = msgsz - 16 - 1;
> > + tls->content_type = msg[msgsz - 16 - 1];
> > + tls->recv.buf = msg;
> > + tls->recv.size = tls->recv.len = msgsz - 16 - 1;
> > + return r;
> > }
> >
> > -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
> > - void *fn_data) {
> > - if (ev == MG_EV_READ) {
> > - for (;;) {
> > - uint8_t version = c->is_mqtt5 ? 5 : 4;
> > - struct mg_mqtt_message mm;
> > - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> > - if (rc == MQTT_MALFORMED) {
> > - MG_ERROR(("%lu MQTT malformed message", c->id));
> > - c->is_closing = 1;
> > - break;
> > - } else if (rc == MQTT_OK) {
> > - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> > - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
> > - switch (mm.cmd) {
> > - case MQTT_CMD_CONNACK:
> > - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> > - if (mm.ack == 0) {
> > - MG_DEBUG(("%lu Connected", c->id));
> > - } else {
> > - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> > - c->is_closing = 1;
> > - }
> > - break;
> > - case MQTT_CMD_PUBLISH: {
> > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> > - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
> > - if (mm.qos > 0) {
> > - uint16_t id = mg_htons(mm.id);
> > - mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
> > - mg_send(c, &id, sizeof(id));
> > - }
> > - mg_call(c, MG_EV_MQTT_MSG, &mm);
> > - break;
> > - }
> > - }
> > - mg_call(c, MG_EV_MQTT_CMD, &mm);
> > - mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> > - } else {
> > - break;
> > +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
> > + uint8_t hash[32]) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + uint8_t sig_content[130] = {
> > + " "
> > + " "
> > + "TLS 1.3, server CertificateVerify\0"};
> > + mg_sha256_ctx sha256;
> > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> > + mg_sha256_final(sig_content + 98, &sha256);
> > +
> > + mg_sha256_init(&sha256);
> > + mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
> > + mg_sha256_final(hash, &sha256);
> > +}
> > +
> > +// read and parse ClientHello record
> > +static int mg_tls_server_recv_hello(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *rio = &c->rtls;
> > + uint8_t session_id_len;
> > + uint16_t j;
> > + uint16_t cipher_suites_len;
> > + uint16_t ext_len;
> > + uint8_t *ext;
> > + uint16_t msgsz;
> > +
> > + if (!mg_tls_got_record(c)) {
> > + return MG_IO_WAIT;
> > + }
> > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != 
> MG_TLS_CLIENT_HELLO) {
> > + mg_error(c, "not a client hello packet");
> > + return -1;
> > + }
> > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> > + // store client random
> > + memmove(tls->random, rio->buf + 11, sizeof(tls->random));
> > + // store session_id
> > + session_id_len = rio->buf[43];
> > + if (session_id_len == sizeof(tls->session_id)) {
> > + memmove(tls->session_id, rio->buf + 44, session_id_len);
> > + } else if (session_id_len != 0) {
> > + MG_INFO(("bad session id len"));
> > + }
> > + cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
> > + ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + 
> cipher_suites_len);
> > + ext = rio->buf + 50 + session_id_len + cipher_suites_len;
> > + for (j = 0; j < ext_len;) {
> > + uint16_t k;
> > + uint16_t key_exchange_len;
> > + uint8_t *key_exchange;
> > + uint16_t n = MG_LOAD_BE16(ext + j + 2);
> > + if (ext[j] != 0x00 ||
> > + ext[j + 1] != 0x33) { // not a key share extension, ignore
> > + j += (uint16_t) (n + 4);
> > + continue;
> > + }
> > + key_exchange_len = MG_LOAD_BE16(ext + j + 5);
> > + key_exchange = ext + j + 6;
> > + for (k = 0; k < key_exchange_len;) {
> > + uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
> > + if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) 
> {
> > + memmove(tls->x25519_cli, key_exchange + k + 4, m);
> > + mg_tls_drop_record(c);
> > + return 0;
> > }
> > + k += (uint16_t) (m + 4);
> > }
> > + j += (uint16_t) (n + 4);
> > }
> > - (void) ev_data;
> > - (void) fn_data;
> > -}
> > -
> > -void mg_mqtt_ping(struct mg_connection *nc) {
> > - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> > -}
> > -
> > -void mg_mqtt_pong(struct mg_connection *nc) {
> > - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> > + mg_error(c, "bad client hello");
> > + return -1;
> > }
> >
> > -void mg_mqtt_disconnect(struct mg_connection *nc) {
> > - mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
> > -}
> > +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
> > +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
> > +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
> > +
> > +// put ServerHello record into wio buffer
> > +static void mg_tls_server_send_hello(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *wio = &tls->send;
> > +
> > + uint8_t msg_server_hello[122] = {
> > + // server hello, tls 1.2
> > + 0x02,
> > + 0x00,
> > + 0x00,
> > + 0x76,
> > + 0x03,
> > + 0x03,
> > + // random (32 bytes)
> > + PLACEHOLDER_32B,
> > + // session ID length + session ID (32 bytes)
> > + 0x20,
> > + PLACEHOLDER_32B,
> > +#if defined(CHACHA20) && CHACHA20
> > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
> > + 0x13,
> > + 0x03,
> > + 0x00,
> > +#else
> > + // TLS_AES_128_GCM_SHA256 + no compression
> > + 0x13,
> > + 0x01,
> > + 0x00,
> > +#endif
> > + // extensions + keyshare
> > + 0x00,
> > + 0x2e,
> > + 0x00,
> > + 0x33,
> > + 0x00,
> > + 0x24,
> > + 0x00,
> > + 0x1d,
> > + 0x00,
> > + 0x20,
> > + // x25519 keyshare
> > + PLACEHOLDER_32B,
> > + // supported versions (tls1.3 == 0x304)
> > + 0x00,
> > + 0x2b,
> > + 0x00,
> > + 0x02,
> > + 0x03,
> > + 0x04
> > + };
> >
> > -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char 
> *url,
> > - const struct mg_mqtt_opts *opts,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> > - if (c != NULL) {
> > - struct mg_mqtt_opts empty;
> > - memset(&empty, 0, sizeof(empty));
> > - mg_mqtt_login(c, opts == NULL ? &empty : opts);
> > - c->pfn = mqtt_cb;
> > + // calculate keyshare
> > + uint8_t x25519_pub[X25519_BYTES];
> > + uint8_t x25519_prv[X25519_BYTES];
> > + mg_random(x25519_prv, sizeof(x25519_prv));
> > + mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
> > + mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
> > + mg_tls_hexdump("s x25519 sec", tls->x25519_sec, 
> sizeof(tls->x25519_sec));
> > +
> > + // fill in the gaps: random + session ID + keyshare
> > + memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
> > + memmove(msg_server_hello + 39, tls->session_id, 
> sizeof(tls->session_id));
> > + memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
> > +
> > + // server hello message
> > + mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
> > + mg_iobuf_add(wio, wio->len, msg_server_hello, 
> sizeof(msg_server_hello));
> > + mg_sha256_update(&tls->sha256, msg_server_hello, 
> sizeof(msg_server_hello));
> > +
> > + // change cipher message
> > + mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
> > +}
> > +
> > +static void mg_tls_server_send_ext(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + // server extensions
> > + uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
> > + mg_sha256_update(&tls->sha256, ext, sizeof(ext));
> > + mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
> > +}
> > +
> > +static void mg_tls_server_send_cert(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + // server DER certificate (empty)
> > + size_t n = tls->server_cert_der.len;
> > + uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
> > + if (cert == NULL) {
> > + mg_error(c, "tls cert oom");
> > + return;
> > }
> > - return c;
> > -}
> > + cert[0] = 0x0b; // handshake header
> > + cert[1] = (uint8_t) (((n + 9) >> 16) & 255U); // 3 bytes: payload 
> length
> > + cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
> > + cert[3] = (uint8_t) ((n + 9) & 255U);
> > + cert[4] = 0; // request context
> > + cert[5] = (uint8_t) (((n + 5) >> 16) & 255U); // 3 bytes: cert (s) 
> length
> > + cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
> > + cert[7] = (uint8_t) ((n + 5) & 255U);
> > + cert[8] =
> > + (uint8_t) (((n) >> 16) & 255U); // 3 bytes: first (and only) cert len
> > + cert[9] = (uint8_t) (((n) >> 8) & 255U);
> > + cert[10] = (uint8_t) (n & 255U);
> > + // bytes 11+ are certificate in DER format
> > + memmove(cert + 11, tls->server_cert_der.buf, n);
> > + cert[11 + n] = cert[12 + n] = 0; // certificate extensions (none)
> > + mg_sha256_update(&tls->sha256, cert, 13 + n);
> > + mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
> > + free(cert);
> > +}
> > +
> > +// type adapter between uECC hash context and our sha256 implementation
> > +typedef struct SHA256_HashContext {
> > + MG_UECC_HashContext uECC;
> > + mg_sha256_ctx ctx;
> > +} SHA256_HashContext;
> > +
> > +static void init_SHA256(const MG_UECC_HashContext *base) {
> > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> > + mg_sha256_init(&c->ctx);
> > +}
> > +
> > +static void update_SHA256(const MG_UECC_HashContext *base,
> > + const uint8_t *message, unsigned message_size) {
> > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> > + mg_sha256_update(&c->ctx, message, message_size);
> > +}
> > +static void finish_SHA256(const MG_UECC_HashContext *base,
> > + uint8_t *hash_result) {
> > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> > + mg_sha256_final(hash_result, &c->ctx);
> > +}
> > +
> > +static void mg_tls_server_send_cert_verify(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + // server certificate verify packet
> > + uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
> > + size_t sigsz, verifysz = 0;
> > + uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
> > + struct SHA256_HashContext ctx = {
> > + {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
> > + {{0}, 0, 0, {0}}};
> > + int neg1, neg2;
> > + uint8_t sig[64] = {0};
> > +
> > + mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
> > +
> > + mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash), 
> &ctx.uECC,
> > + sig, mg_uecc_secp256r1());
> > +
> > + neg1 = !!(sig[0] & 0x80);
> > + neg2 = !!(sig[32] & 0x80);
> > + verify[8] = 0x30; // ASN.1 SEQUENCE
> > + verify[9] = (uint8_t) (68 + neg1 + neg2);
> > + verify[10] = 0x02; // ASN.1 INTEGER
> > + verify[11] = (uint8_t) (32 + neg1);
> > + memmove(verify + 12 + neg1, sig, 32);
> > + verify[12 + 32 + neg1] = 0x02; // ASN.1 INTEGER
> > + verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
> > + memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
> > +
> > + sigsz = (size_t) (70 + neg1 + neg2);
> > + verifysz = 8U + sigsz;
> > + verify[3] = (uint8_t) (sigsz + 4);
> > + verify[7] = (uint8_t) sigsz;
> > +
> > + mg_sha256_update(&tls->sha256, verify, verifysz);
> > + mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
> > +}
> > +
> > +static void mg_tls_server_send_finish(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *wio = &tls->send;
> > + mg_sha256_ctx sha256;
> > + uint8_t hash[32];
> > + uint8_t finish[36] = {0x14, 0, 0, 32};
> > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> > + mg_sha256_final(hash, &sha256);
> > + mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash, 32);
> > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> > + mg_io_send(c, wio->buf, wio->len);
> > + wio->len = 0;
> > +
> > + mg_sha256_update(&tls->sha256, finish, sizeof(finish));
> > +}
> > +
> > +static int mg_tls_server_recv_finish(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + // we have to backup sha256 value to restore it later, since Finished 
> record
> > + // is exceptional and is not supposed to be added to the rolling hash
> > + // calculation.
> > + mg_sha256_ctx sha256 = tls->sha256;
> > + if (mg_tls_recv_record(c) < 0) {
> > + return -1;
> > + }
> > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> > + mg_error(c, "expected Finish but got msg 0x%02x", tls->recv.buf[0]);
> > + return -1;
> > + }
> > + mg_tls_drop_message(c);
> >
> > -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char 
> *url,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> > - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> > - return c;
> > + // restore hash
> > + tls->sha256 = sha256;
> > + return 0;
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/net.c"
> > +static void mg_tls_client_send_hello(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *wio = &tls->send;
> > +
> > + const char *hostname = tls->hostname;
> > + size_t hostnamesz = strlen(tls->hostname);
> > + uint8_t x25519_pub[X25519_BYTES];
> > +
> > + uint8_t msg_client_hello[162 + 32] = {
> > + // TLS Client Hello header reported as TLS1.2 (5)
> > + 0x16,
> > + 0x03,
> > + 0x01,
> > + 0x00,
> > + 0xfe,
> > + // server hello, tls 1.2 (6)
> > + 0x01,
> > + 0x00,
> > + 0x00,
> > + 0x8c,
> > + 0x03,
> > + 0x03,
> > + // random (32 bytes)
> > + PLACEHOLDER_32B,
> > + // session ID length + session ID (32 bytes)
> > + 0x20,
> > + PLACEHOLDER_32B,
> > +#if defined(CHACHA20) && CHACHA20
> > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
> > + 0x13,
> > + 0x03,
> > + 0x00,
> > +#else
> > + 0x00,
> > + 0x02, // size = 2 bytes
> > + 0x13,
> > + 0x01, // TLS_AES_128_GCM_SHA256
> > + 0x01,
> > + 0x00, // no compression
> > #endif
> >
> > + // extensions + keyshare
> > + 0x00,
> > + 0xfe,
> > + // x25519 keyshare
> > + 0x00,
> > + 0x33,
> > + 0x00,
> > + 0x26,
> > + 0x00,
> > + 0x24,
> > + 0x00,
> > + 0x1d,
> > + 0x00,
> > + 0x20,
> > + PLACEHOLDER_32B,
> > + // supported groups (x25519)
> > + 0x00,
> > + 0x0a,
> > + 0x00,
> > + 0x04,
> > + 0x00,
> > + 0x02,
> > + 0x00,
> > + 0x1d,
> > + // supported versions (tls1.3 == 0x304)
> > + 0x00,
> > + 0x2b,
> > + 0x00,
> > + 0x03,
> > + 0x02,
> > + 0x03,
> > + 0x04,
> > + // session ticket (none)
> > + 0x00,
> > + 0x23,
> > + 0x00,
> > + 0x00,
> > + // signature algorithms (we don't care, so list all the common ones)
> > + 0x00,
> > + 0x0d,
> > + 0x00,
> > + 0x24,
> > + 0x00,
> > + 0x22,
> > + 0x04,
> > + 0x03,
> > + 0x05,
> > + 0x03,
> > + 0x06,
> > + 0x03,
> > + 0x08,
> > + 0x07,
> > + 0x08,
> > + 0x08,
> > + 0x08,
> > + 0x1a,
> > + 0x08,
> > + 0x1b,
> > + 0x08,
> > + 0x1c,
> > + 0x08,
> > + 0x09,
> > + 0x08,
> > + 0x0a,
> > + 0x08,
> > + 0x0b,
> > + 0x08,
> > + 0x04,
> > + 0x08,
> > + 0x05,
> > + 0x08,
> > + 0x06,
> > + 0x04,
> > + 0x01,
> > + 0x05,
> > + 0x01,
> > + 0x06,
> > + 0x01,
> > + // server name
> > + 0x00,
> > + 0x00,
> > + 0x00,
> > + 0xfe,
> > + 0x00,
> > + 0xfe,
> > + 0x00,
> > + 0x00,
> > + 0xfe
> > + };
> >
> > + // patch ClientHello with correct hostname length + offset:
> > + MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
> > + MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
> > + MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
> > + MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
> > + MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
> > + MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
> > +
> > + // calculate keyshare
> > + mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
> > + mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
> > +
> > + // fill in the gaps: random + session ID + keyshare
> > + mg_random(tls->session_id, sizeof(tls->session_id));
> > + mg_random(tls->random, sizeof(tls->random));
> > + memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
> > + memmove(msg_client_hello + 44, tls->session_id, 
> sizeof(tls->session_id));
> > + memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
> > +
> > + // server hello message
> > + mg_iobuf_add(wio, wio->len, msg_client_hello, 
> sizeof(msg_client_hello));
> > + mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
> > + mg_sha256_update(&tls->sha256, msg_client_hello + 5,
> > + sizeof(msg_client_hello) - 5);
> > + mg_sha256_update(&tls->sha256, (uint8_t *) hostname, strlen(hostname));
> > +
> > + // change cipher message
> > + mg_iobuf_add(wio, wio->len, (const char *) "\x14\x03\x03\x00\x01\x01", 
> 6);
> > + mg_io_send(c, wio->buf, wio->len);
> > + wio->len = 0;
> > +}
> > +
> > +static int mg_tls_client_recv_hello(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *rio = &c->rtls;
> > + uint16_t msgsz;
> > + uint8_t *ext;
> > + uint16_t ext_len;
> > + int j;
> > +
> > + if (!mg_tls_got_record(c)) {
> > + return MG_IO_WAIT;
> > + }
> > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != 
> MG_TLS_SERVER_HELLO) {
> > + if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
> > + mg_error(c, "tls alert %d", rio->buf[6]);
> > + return -1;
> > + }
> > + MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0], rio->buf[5]));
> > + mg_error(c, "not a server hello packet");
> > + return -1;
> > + }
> >
> > -
> > -
> > -
> > -
> > -size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list 
> *ap) {
> > - size_t old = c->send.len;
> > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> > - return c->send.len - old;
> > -}
> > -
> > -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> > - size_t len = 0;
> > - va_list ap;
> > - va_start(ap, fmt);
> > - len = mg_vprintf(c, fmt, &ap);
> > - va_end(ap);
> > - return len;
> > -}
> > -
> > -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> > - if (mg_vcasecmp(&str, "localhost") != 0) return false;
> > - addr->ip = mg_htonl(0x7f000001);
> > - addr->is_ip6 = false;
> > - return true;
> > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> > +
> > + ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
> > + ext = rio->buf + 5 + 39 + 32 + 3 + 2;
> > +
> > + for (j = 0; j < ext_len;) {
> > + uint16_t ext_type = MG_LOAD_BE16(ext + j);
> > + uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
> > + uint16_t group;
> > + uint8_t *key_exchange;
> > + uint16_t key_exchange_len;
> > + if (ext_type != 0x0033) { // not a key share extension, ignore
> > + j += (uint16_t) (ext_len2 + 4);
> > + continue;
> > + }
> > + group = MG_LOAD_BE16(ext + j + 4);
> > + if (group != 0x001d) {
> > + mg_error(c, "bad key exchange group");
> > + return -1;
> > + }
> > + key_exchange_len = MG_LOAD_BE16(ext + j + 6);
> > + key_exchange = ext + j + 8;
> > + if (key_exchange_len != 32) {
> > + mg_error(c, "bad key exchange length");
> > + return -1;
> > + }
> > + mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
> > + mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
> > + mg_tls_drop_record(c);
> > + /* generate handshake keys */
> > + mg_tls_generate_handshake_keys(c);
> > + return 0;
> > + }
> > + mg_error(c, "bad client hello");
> > + return -1;
> > }
> >
> > -static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> > - if (str.len > 0) return false;
> > - addr->ip = 0;
> > - addr->is_ip6 = false;
> > - return true;
> > +static int mg_tls_client_recv_ext(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (mg_tls_recv_record(c) < 0) {
> > + return -1;
> > + }
> > + if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
> > + mg_error(c, "expected server extensions but got msg 0x%02x",
> > + tls->recv.buf[0]);
> > + return -1;
> > + }
> > + mg_tls_drop_message(c);
> > + return 0;
> > }
> >
> > -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> > - uint8_t data[4] = {0, 0, 0, 0};
> > - size_t i, num_dots = 0;
> > - for (i = 0; i < str.len; i++) {
> > - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> > - int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
> > - if (octet > 255) return false;
> > - data[num_dots] = (uint8_t) octet;
> > - } else if (str.ptr[i] == '.') {
> > - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false;
> > - num_dots++;
> > - } else {
> > - return false;
> > - }
> > +static int mg_tls_client_recv_cert(struct mg_connection *c) {
> > + uint8_t *cert;
> > + uint32_t certsz;
> > + struct mg_der_tlv oid, pubkey, seq, subj;
> > + int subj_match = 0;
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (mg_tls_recv_record(c) < 0) {
> > + return -1;
> > + }
> > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
> > + mg_error(c, "expected server certificate but got msg 0x%02x",
> > + tls->recv.buf[0]);
> > + return -1;
> > + }
> > + if (tls->skip_verification) {
> > + mg_tls_drop_message(c);
> > + return 0;
> > }
> > - if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
> > - memcpy(&addr->ip, data, sizeof(data));
> > - addr->is_ip6 = false;
> > - return true;
> > -}
> >
> > -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> > - int i;
> > - if (str.len < 14) return false;
> > - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') 
> return false;
> > - for (i = 2; i < 6; i++) {
> > - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
> > + if (tls->recv.len < 11) {
> > + mg_error(c, "certificate list too short");
> > + return -1;
> > }
> > - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false;
> > - memset(addr->ip6, 0, sizeof(addr->ip6));
> > - addr->ip6[10] = addr->ip6[11] = 255;
> > - memcpy(&addr->ip6[12], &addr->ip, 4);
> > - addr->is_ip6 = true;
> > - return true;
> > -}
> >
> > -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> > - size_t i, j = 0, n = 0, dc = 42;
> > - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
> > - if (mg_v4mapped(str, addr)) return true;
> > - for (i = 0; i < str.len; i++) {
> > - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
> > - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
> > - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
> > - unsigned long val;
> > - if (i > j + 3) return false;
> > - // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j]));
> > - val = mg_unhexn(&str.ptr[j], i - j + 1);
> > - addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
> > - addr->ip6[n + 1] = (uint8_t) (val & 255);
> > - } else if (str.ptr[i] == ':') {
> > - j = i + 1;
> > - if (i > 0 && str.ptr[i - 1] == ':') {
> > - dc = n; // Double colon
> > - if (i > 1 && str.ptr[i - 2] == ':') return false;
> > - } else if (i > 0) {
> > - n += 2;
> > + cert = tls->recv.buf + 11;
> > + certsz = MG_LOAD_BE24(tls->recv.buf + 8);
> > + if (certsz > tls->recv.len - 11) {
> > + mg_error(c, "certificate too long: %d vs %d", certsz, tls->recv.len - 
> 11);
> > + return -1;
> > + }
> > +
> > + do {
> > + // secp256r1 public key
> > + if (mg_der_find(cert, certsz,
> > + (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
> > + &oid) < 0) {
> > + mg_error(c, "certificate secp256r1 public key OID not found");
> > + return -1;
> > + }
> > + if (mg_der_to_tlv(oid.value + oid.len,
> > + (size_t) (cert + certsz - (oid.value + oid.len)),
> > + &pubkey) < 0) {
> > + mg_error(c, "certificate secp256r1 public key not found");
> > + return -1;
> > + }
> > +
> > + // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32 content 
> bytes
> > + if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] != 0 ||
> > + pubkey.value[1] != 4) {
> > + mg_error(c, "unsupported public key bitstring encoding");
> > + return -1;
> > + }
> > + memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
> > + } while (0);
> > +
> > + // Subject Alternative Names
> > + do {
> > + if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3, &oid) < 
> 0) {
> > + mg_error(c, "certificate does not contain subject alternative names");
> > + return -1;
> > + }
> > + if (mg_der_to_tlv(oid.value + oid.len,
> > + (size_t) (cert + certsz - (oid.value + oid.len)),
> > + &seq) < 0) {
> > + mg_error(c, "certificate subject alternative names not found");
> > + return -1;
> > + }
> > + if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
> > + mg_error(
> > + c,
> > + "certificate subject alternative names is not a constructed object");
> > + return -1;
> > + }
> > + MG_VERBOSE(("verify hostname %s", tls->hostname));
> > + while (seq.len > 0) {
> > + if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
> > + mg_error(c, "bad subject alternative name");
> > + return -1;
> > }
> > - if (n > 14) return false;
> > - addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing ::
> > - } else {
> > - return false;
> > + MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
> > + if (mg_match(mg_str((const char *) tls->hostname),
> > + mg_str_n((const char *) subj.value, subj.len), NULL)) {
> > + subj_match = 1;
> > + break;
> > + }
> > + seq.len = (uint32_t) (seq.value + seq.len - (subj.value + subj.len));
> > + seq.value = subj.value + subj.len;
> > + }
> > + if (!subj_match) {
> > + mg_error(c, "certificate did not match the hostname");
> > + return -1;
> > }
> > + } while (0);
> > +
> > + mg_tls_drop_message(c);
> > + mg_tls_calc_cert_verify_hash(c, tls->sighash);
> > + return 0;
> > +}
> > +
> > +static int mg_tls_client_recv_cert_verify(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (mg_tls_recv_record(c) < 0) {
> > + return -1;
> > }
> > - if (n < 14 && dc == 42) return false;
> > - if (n < 14) {
> > - memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
> > - memset(&addr->ip6[dc], 0, 14 - n);
> > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
> > + mg_error(c, "expected server certificate verify but got msg 0x%02x",
> > + tls->recv.buf[0]);
> > + return -1;
> > + }
> > + // Ignore CertificateVerify is strict checks are not required
> > + if (tls->skip_verification) {
> > + mg_tls_drop_message(c);
> > + return 0;
> > }
> > - addr->is_ip6 = true;
> > - return true;
> > -}
> >
> > -bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> > - // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
> > - return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, 
> addr) ||
> > - mg_aton6(str, addr);
> > + // Extract certificate signature and verify it using pubkey and sighash
> > + do {
> > + uint8_t sig[64];
> > + struct mg_der_tlv seq, a, b;
> > + if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) < 0) {
> > + mg_error(c, "verification message is not an ASN.1 DER sequence");
> > + return -1;
> > + }
> > + if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
> > + mg_error(c, "missing first part of the signature");
> > + return -1;
> > + }
> > + if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
> > + mg_error(c, "missing second part of the signature");
> > + return -1;
> > + }
> > + // Integers may be padded with zeroes
> > + if (a.len > 32) {
> > + a.value = a.value + (a.len - 32);
> > + a.len = 32;
> > + }
> > + if (b.len > 32) {
> > + b.value = b.value + (b.len - 32);
> > + b.len = 32;
> > + }
> > +
> > + memmove(sig, a.value, a.len);
> > + memmove(sig + 32, b.value, b.len);
> > +
> > + if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), 
> sig,
> > + mg_uecc_secp256r1()) != 1) {
> > + mg_error(c, "failed to verify certificate");
> > + return -1;
> > + }
> > + } while (0);
> > +
> > + mg_tls_drop_message(c);
> > + return 0;
> > }
> >
> > -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> > - struct mg_connection *c =
> > - (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
> > - if (c != NULL) {
> > - c->mgr = mgr;
> > - c->send.align = c->recv.align = MG_IO_SIZE;
> > - c->id = ++mgr->nextid;
> > +static int mg_tls_client_recv_finish(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (mg_tls_recv_record(c) < 0) {
> > + return -1;
> > }
> > - return c;
> > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> > + mg_error(c, "expected server finished but got msg 0x%02x",
> > + tls->recv.buf[0]);
> > + return -1;
> > + }
> > + mg_tls_drop_message(c);
> > + return 0;
> > }
> >
> > -void mg_close_conn(struct mg_connection *c) {
> > - mg_resolve_cancel(c); // Close any pending DNS query
> > - LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> > - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> > - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> > - // Order of operations is important. `MG_EV_CLOSE` event must be fired
> > - // before we deallocate received data, see #1331
> > - mg_call(c, MG_EV_CLOSE, NULL);
> > - MG_DEBUG(("%lu %p closed", c->id, c->fd));
> > -
> > - mg_tls_free(c);
> > - mg_iobuf_free(&c->recv);
> > - mg_iobuf_free(&c->send);
> > - memset(c, 0, sizeof(*c));
> > - free(c);
> > +static void mg_tls_client_send_finish(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + struct mg_iobuf *wio = &tls->send;
> > + mg_sha256_ctx sha256;
> > + uint8_t hash[32];
> > + uint8_t finish[36] = {0x14, 0, 0, 32};
> > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> > + mg_sha256_final(hash, &sha256);
> > + mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash, 32);
> > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> > + mg_io_send(c, wio->buf, wio->len);
> > + wio->len = 0;
> > +}
> > +
> > +static void mg_tls_client_handshake(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + switch (tls->state) {
> > + case MG_TLS_STATE_CLIENT_START:
> > + mg_tls_client_send_hello(c);
> > + tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
> > + // Fallthrough
> > + case MG_TLS_STATE_CLIENT_WAIT_SH:
> > + if (mg_tls_client_recv_hello(c) < 0) {
> > + break;
> > + }
> > + tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
> > + // Fallthrough
> > + case MG_TLS_STATE_CLIENT_WAIT_EE:
> > + if (mg_tls_client_recv_ext(c) < 0) {
> > + break;
> > + }
> > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
> > + // Fallthrough
> > + case MG_TLS_STATE_CLIENT_WAIT_CERT:
> > + if (mg_tls_client_recv_cert(c) < 0) {
> > + break;
> > + }
> > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
> > + // Fallthrough
> > + case MG_TLS_STATE_CLIENT_WAIT_CV:
> > + if (mg_tls_client_recv_cert_verify(c) < 0) {
> > + break;
> > + }
> > + tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
> > + // Fallthrough
> > + case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
> > + if (mg_tls_client_recv_finish(c) < 0) {
> > + break;
> > + }
> > + mg_tls_client_send_finish(c);
> > + mg_tls_generate_application_keys(c);
> > + tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
> > + c->is_tls_hs = 0;
> > + break;
> > + default: mg_error(c, "unexpected client state: %d", tls->state); break;
> > + }
> > }
> >
> > -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = NULL;
> > - if (url == NULL || url[0] == '\0') {
> > - MG_ERROR(("null url"));
> > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> > - MG_ERROR(("OOM"));
> > - } else {
> > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > - c->is_udp = (strncmp(url, "udp:", 4) == 0);
> > - c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> > - c->fn = fn;
> > - c->is_client = true;
> > - c->fn_data = fn_data;
> > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> > - mg_call(c, MG_EV_OPEN, NULL);
> > - mg_resolve(c, url);
> > +static void mg_tls_server_handshake(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + switch (tls->state) {
> > + case MG_TLS_STATE_SERVER_START:
> > + if (mg_tls_server_recv_hello(c) < 0) {
> > + return;
> > + }
> > + mg_tls_server_send_hello(c);
> > + mg_tls_generate_handshake_keys(c);
> > + mg_tls_server_send_ext(c);
> > + mg_tls_server_send_cert(c);
> > + mg_tls_server_send_cert_verify(c);
> > + mg_tls_server_send_finish(c);
> > + tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
> > + // fallthrough
> > + case MG_TLS_STATE_SERVER_NEGOTIATED:
> > + if (mg_tls_server_recv_finish(c) < 0) {
> > + return;
> > + }
> > + mg_tls_generate_application_keys(c);
> > + tls->state = MG_TLS_STATE_SERVER_CONNECTED;
> > + c->is_tls_hs = 0;
> > + return;
> > + default: mg_error(c, "unexpected server state: %d", tls->state); break;
> > }
> > - return c;
> > }
> >
> > -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = NULL;
> > - if ((c = mg_alloc_conn(mgr)) == NULL) {
> > - MG_ERROR(("OOM %s", url));
> > - } else if (!mg_open_listener(c, url)) {
> > - MG_ERROR(("Failed: %s, errno %d", url, errno));
> > - free(c);
> > - c = NULL;
> > +void mg_tls_handshake(struct mg_connection *c) {
> > + if (c->is_client) {
> > + mg_tls_client_handshake(c);
> > } else {
> > - c->is_listening = 1;
> > - c->is_udp = strncmp(url, "udp:", 4) == 0;
> > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > - c->fn = fn;
> > - c->fn_data = fn_data;
> > - mg_call(c, MG_EV_OPEN, NULL);
> > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> > + mg_tls_server_handshake(c);
> > }
> > - return c;
> > }
> >
> > -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> > - mg_event_handler_t fn, void *fn_data) {
> > - struct mg_connection *c = mg_alloc_conn(mgr);
> > - if (c != NULL) {
> > - c->fd = (void *) (size_t) fd;
> > - c->fn = fn;
> > - c->fn_data = fn_data;
> > - MG_EPOLL_ADD(c);
> > - mg_call(c, MG_EV_OPEN, NULL);
> > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > +static int mg_parse_pem(const struct mg_str pem, const struct mg_str 
> label,
> > + struct mg_str *der) {
> > + size_t n = 0, m = 0;
> > + char *s;
> > + const char *c;
> > + struct mg_str caps[5];
> > + if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END #-----#"), 
> caps)) {
> > + der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
> > + der->len = pem.len;
> > + return 0;
> > + }
> > + if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label) != 0) {
> > + return -1; // bad label
> > + }
> > + if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
> > + return -1;
> > }
> > - return c;
> > -}
> >
> > -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
> > - unsigned flags, void (*fn)(void *), void *arg) {
> > - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> > - if (t != NULL) {
> > - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> > - t->id = mgr->timerid++;
> > + for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
> > + if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
> > + continue;
> > + }
> > + s[n++] = *c;
> > }
> > - return t;
> > + m = mg_base64_decode(s, n, s, n);
> > + if (m == 0) {
> > + free(s);
> > + return -1;
> > + }
> > + der->buf = s;
> > + der->len = m;
> > + return 0;
> > }
> >
> > -void mg_mgr_free(struct mg_mgr *mgr) {
> > - struct mg_connection *c;
> > - struct mg_timer *tmp, *t = mgr->timers;
> > - while (t != NULL) tmp = t->next, free(t), t = tmp;
> > - mgr->timers = NULL; // Important. Next call to poll won't touch timers
> > - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> > - mg_mgr_poll(mgr, 0);
> > -#if MG_ENABLE_FREERTOS_TCP
> > - FreeRTOS_DeleteSocketSet(mgr->ss);
> > -#endif
> > - MG_DEBUG(("All connections closed"));
> > -#if MG_ENABLE_EPOLL
> > - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> > -#endif
> > -}
> > +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > + struct mg_str key;
> > + struct tls_data *tls = (struct tls_data *) calloc(1, sizeof(struct 
> tls_data));
> > + if (tls == NULL) {
> > + mg_error(c, "tls oom");
> > + return;
> > + }
> >
> > -void mg_mgr_init(struct mg_mgr *mgr) {
> > - memset(mgr, 0, sizeof(*mgr));
> > -#if MG_ENABLE_EPOLL
> > - if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll: %d", 
> errno));
> > -#else
> > - mgr->epoll_fd = -1;
> > -#endif
> > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > - // clang-format off
> > - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> > - // clang-format on
> > -#elif MG_ENABLE_FREERTOS_TCP
> > - mgr->ss = FreeRTOS_CreateSocketSet();
> > -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> > - // Ignore SIGPIPE signal, so if client cancels the request, it
> > - // won't kill the whole process.
> > - signal(SIGPIPE, SIG_IGN);
> > -#endif
> > - mgr->dnstimeout = 3000;
> > - mgr->dns4.url = "udp://8.8.8.8:53";
> > - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> > -}
> > + tls->state =
> > + c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/rpc.c"
> > -#endif
> > + tls->skip_verification = opts->skip_verification;
> > + tls->send.align = MG_IO_SIZE;
> >
> > + c->tls = tls;
> > + c->is_tls = c->is_tls_hs = 1;
> > + mg_sha256_init(&tls->sha256);
> >
> > -void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> > - void (*fn)(struct mg_rpc_req *), void *fn_data) {
> > - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> > - if (rpc != NULL) {
> > - rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data;
> > - rpc->next = *head, *head = rpc;
> > + // save hostname (client extension)
> > + if (opts->name.len > 0) {
> > + if (opts->name.len >= sizeof(tls->hostname) - 1) {
> > + mg_error(c, "hostname too long");
> > + }
> > + strncpy((char *) tls->hostname, opts->name.buf, sizeof(tls->hostname) 
> - 1);
> > + tls->hostname[opts->name.len] = 0;
> > }
> > -}
> >
> > -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
> > - struct mg_rpc *r;
> > - while ((r = *head) != NULL) {
> > - if (r->fn == fn || fn == NULL) {
> > - *head = r->next;
> > - free((void *) r->method.ptr);
> > - free(r);
> > - } else {
> > - head = &(*head)->next;
> > - }
> > + if (c->is_client) {
> > + tls->server_cert_der.buf = NULL;
> > + return;
> > }
> > -}
> >
> > -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
> > - struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> > - while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
> > - if (h != NULL) {
> > - r->rpc = h;
> > - h->fn(r);
> > + // parse PEM or DER certificate
> > + if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"), 
> &tls->server_cert_der) <
> > + 0) {
> > + MG_ERROR(("Failed to load certificate"));
> > + return;
> > + }
> > +
> > + // parse PEM or DER EC key
> > + if (opts->key.buf == NULL) {
> > + mg_error(c, "certificate provided without a private key");
> > + return;
> > + }
> > +
> > + if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) == 0) {
> > + if (key.len < 39) {
> > + MG_ERROR(("EC private key too short"));
> > + return;
> > + }
> > + // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
> > + // 30 nn 02 01 01 04 20 [key] ...
> > + if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
> > + MG_ERROR(("EC private key: ASN.1 bad sequence"));
> > + return;
> > + }
> > + if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
> > + MG_ERROR(("EC private key: ASN.1 bad data"));
> > + }
> > + memmove(tls->server_key, key.buf + 7, 32);
> > + free((void *) key.buf);
> > + } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 
> 0) {
> > + mg_error(c, "PKCS8 private key format is not supported");
> > } else {
> > - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, 
> method.ptr);
> > + mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
> > }
> > }
> >
> > -void mg_rpc_process(struct mg_rpc_req *r) {
> > - int len, off = mg_json_get(r->frame, "$.method", &len);
> > - if (off > 0 && r->frame.ptr[off] == '"') {
> > - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 
> 2);
> > - mg_rpc_call(r, method);
> > - } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> > - (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> > - mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler
> > - } else {
> > - mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len, r->frame.ptr); // 
> Invalid
> > +void mg_tls_free(struct mg_connection *c) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + if (tls != NULL) {
> > + mg_iobuf_free(&tls->send);
> > + free((void *) tls->server_cert_der.buf);
> > }
> > + free(c->tls);
> > + c->tls = NULL;
> > }
> >
> > -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
> > - int len, off = mg_json_get(r->frame, "$.id", &len);
> > - if (off > 0) {
> > - mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
> > - &r->frame.ptr[off], "result");
> > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> > - mg_xprintf(r->pfn, r->pfn_data, "}");
> > +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + long n = MG_IO_WAIT;
> > + if (len > MG_IO_SIZE) len = MG_IO_SIZE;
> > + mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
> > + while (tls->send.len > 0 &&
> > + (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
> > + mg_iobuf_del(&tls->send, 0, (size_t) n);
> > }
> > + if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
> > + return (long) len;
> > }
> >
> > -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> > - va_list ap;
> > - va_start(ap, fmt);
> > - mg_rpc_vok(r, fmt, &ap);
> > - va_end(ap);
> > -}
> > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > + int r = 0;
> > + struct tls_data *tls = (struct tls_data *) c->tls;
> > + size_t minlen;
> >
> > -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, 
> va_list *ap) {
> > - int len, off = mg_json_get(r->frame, "$.id", &len);
> > - mg_xprintf(r->pfn, r->pfn_data, "{");
> > - if (off > 0) {
> > - mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len, 
> &r->frame.ptr[off]);
> > + r = mg_tls_recv_record(c);
> > + if (r < 0) {
> > + return r;
> > }
> > - mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error", "code", code,
> > - "message");
> > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> > - mg_xprintf(r->pfn, r->pfn_data, "}}");
> > + if (tls->content_type != MG_TLS_APP_DATA) {
> > + tls->recv.len = 0;
> > + mg_tls_drop_record(c);
> > + return MG_IO_WAIT;
> > + }
> > + minlen = len < tls->recv.len ? len : tls->recv.len;
> > + memmove(buf, tls->recv.buf, minlen);
> > + tls->recv.buf += minlen;
> > + tls->recv.len -= minlen;
> > + if (tls->recv.len == 0) {
> > + mg_tls_drop_record(c);
> > + }
> > + return (long) minlen;
> > }
> >
> > -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
> > - va_list ap;
> > - va_start(ap, fmt);
> > - mg_rpc_verr(r, code, fmt, &ap);
> > - va_end(ap);
> > +size_t mg_tls_pending(struct mg_connection *c) {
> > + return mg_tls_got_record(c) ? 1 : 0;
> > }
> >
> > -static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
> > - struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
> > - size_t len = 0;
> > - for (h = *head; h != NULL; h = h->next) {
> > - if (h->method.len == 0) continue; // Ignore response handler
> > - len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
> > - (int) h->method.len, h->method.ptr);
> > - }
> > - return len;
> > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> > + (void) mgr;
> > }
> >
> > -void mg_rpc_list(struct mg_rpc_req *r) {
> > - mg_rpc_ok(r, "[%M]", print_methods, r->head);
> > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> > + (void) mgr;
> > }
> > +#endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/sha1.c"
> > +#line 1 "src/tls_dummy.c"
> > +#endif
> > +
> > +
> > +#if MG_TLS == MG_TLS_NONE
> > +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > + (void) opts;
> > + mg_error(c, "TLS is not enabled");
> > +}
> > +void mg_tls_handshake(struct mg_connection *c) {
> > + (void) c;
> > +}
> > +void mg_tls_free(struct mg_connection *c) {
> > + (void) c;
> > +}
> > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> > +}
> > +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> > +}
> > +size_t mg_tls_pending(struct mg_connection *c) {
> > + (void) c;
> > + return 0;
> > +}
> > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> > + (void) mgr;
> > +}
> > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> > + (void) mgr;
> > +}
> > #endif
> > -/* Copyright(c) By Steve Reid <st...@edmweb.com> */
> > -/* 100% Public Domain */
> >
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/tls_mbed.c"
> > +#endif
> >
> >
> > -union char64long16 {
> > - unsigned char c[64];
> > - uint32_t l[16];
> > -};
> >
> > -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - 
> (bits))))
> > +#if MG_TLS == MG_TLS_MBED
> >
> > -static uint32_t blk0(union char64long16 *block, int i) {
> > - if (MG_BIG_ENDIAN) {
> > - } else {
> > - block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> > - (rol(block->l[i], 8) & 0x00FF00FF);
> > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 
> 0x03000000
> > +#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
> > +#else
> > +#define MG_MBEDTLS_RNG_GET
> > +#endif
> > +
> > +static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> > + mg_random(buf, len);
> > + (void) ctx;
> > + return 0;
> > +}
> > +
> > +static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
> > + int rc;
> > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return 
> true;
> > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
> > + if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf, str.len)) != 
> 0) {
> > + MG_ERROR(("cert err %#x", -rc));
> > + return false;
> > }
> > - return block->l[i];
> > + return true;
> > }
> >
> > -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define 
> R0~R4) */
> > -#undef blk
> > -#undef R0
> > -#undef R1
> > -#undef R2
> > -#undef R3
> > -#undef R4
> > +static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
> > + int rc;
> > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return 
> true;
> > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
> > + if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len, NULL,
> > + 0 MG_MBEDTLS_RNG_GET)) != 0) {
> > + MG_ERROR(("key err %#x", -rc));
> > + return false;
> > + }
> > + return true;
> > +}
> >
> > -#define blk(i) \
> > - (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 
> 15] ^ \
> > - block->l[(i + 2) & 15] ^ block->l[i & 15], \
> > - 1))
> > -#define R0(v, w, x, y, z, i) \
> > - z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
> > - w = rol(w, 30);
> > -#define R1(v, w, x, y, z, i) \
> > - z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> > - w = rol(w, 30);
> > -#define R2(v, w, x, y, z, i) \
> > - z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> > - w = rol(w, 30);
> > -#define R3(v, w, x, y, z, i) \
> > - z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
> > - w = rol(w, 30);
> > -#define R4(v, w, x, y, z, i) \
> > - z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> > - w = rol(w, 30);
> > +void mg_tls_free(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + if (tls != NULL) {
> > + mbedtls_ssl_free(&tls->ssl);
> > + mbedtls_pk_free(&tls->pk);
> > + mbedtls_x509_crt_free(&tls->ca);
> > + mbedtls_x509_crt_free(&tls->cert);
> > + mbedtls_ssl_config_free(&tls->conf);
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + mbedtls_ssl_ticket_free(&tls->ticket);
> > +#endif
> > + free(tls);
> > + c->tls = NULL;
> > + }
> > +}
> >
> > -static void mg_sha1_transform(uint32_t state[5],
> > - const unsigned char buffer[64]) {
> > - uint32_t a, b, c, d, e;
> > - union char64long16 block[1];
> > +static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) 
> {
> > + long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> > + MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *) ctx)->id, n, 
> errno));
> > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> > + return (int) n;
> > +}
> >
> > - memcpy(block, buffer, 64);
> > - a = state[0];
> > - b = state[1];
> > - c = state[2];
> > - d = state[3];
> > - e = state[4];
> > - R0(a, b, c, d, e, 0);
> > - R0(e, a, b, c, d, 1);
> > - R0(d, e, a, b, c, 2);
> > - R0(c, d, e, a, b, 3);
> > - R0(b, c, d, e, a, 4);
> > - R0(a, b, c, d, e, 5);
> > - R0(e, a, b, c, d, 6);
> > - R0(d, e, a, b, c, 7);
> > - R0(c, d, e, a, b, 8);
> > - R0(b, c, d, e, a, 9);
> > - R0(a, b, c, d, e, 10);
> > - R0(e, a, b, c, d, 11);
> > - R0(d, e, a, b, c, 12);
> > - R0(c, d, e, a, b, 13);
> > - R0(b, c, d, e, a, 14);
> > - R0(a, b, c, d, e, 15);
> > - R1(e, a, b, c, d, 16);
> > - R1(d, e, a, b, c, 17);
> > - R1(c, d, e, a, b, 18);
> > - R1(b, c, d, e, a, 19);
> > - R2(a, b, c, d, e, 20);
> > - R2(e, a, b, c, d, 21);
> > - R2(d, e, a, b, c, 22);
> > - R2(c, d, e, a, b, 23);
> > - R2(b, c, d, e, a, 24);
> > - R2(a, b, c, d, e, 25);
> > - R2(e, a, b, c, d, 26);
> > - R2(d, e, a, b, c, 27);
> > - R2(c, d, e, a, b, 28);
> > - R2(b, c, d, e, a, 29);
> > - R2(a, b, c, d, e, 30);
> > - R2(e, a, b, c, d, 31);
> > - R2(d, e, a, b, c, 32);
> > - R2(c, d, e, a, b, 33);
> > - R2(b, c, d, e, a, 34);
> > - R2(a, b, c, d, e, 35);
> > - R2(e, a, b, c, d, 36);
> > - R2(d, e, a, b, c, 37);
> > - R2(c, d, e, a, b, 38);
> > - R2(b, c, d, e, a, 39);
> > - R3(a, b, c, d, e, 40);
> > - R3(e, a, b, c, d, 41);
> > - R3(d, e, a, b, c, 42);
> > - R3(c, d, e, a, b, 43);
> > - R3(b, c, d, e, a, 44);
> > - R3(a, b, c, d, e, 45);
> > - R3(e, a, b, c, d, 46);
> > - R3(d, e, a, b, c, 47);
> > - R3(c, d, e, a, b, 48);
> > - R3(b, c, d, e, a, 49);
> > - R3(a, b, c, d, e, 50);
> > - R3(e, a, b, c, d, 51);
> > - R3(d, e, a, b, c, 52);
> > - R3(c, d, e, a, b, 53);
> > - R3(b, c, d, e, a, 54);
> > - R3(a, b, c, d, e, 55);
> > - R3(e, a, b, c, d, 56);
> > - R3(d, e, a, b, c, 57);
> > - R3(c, d, e, a, b, 58);
> > - R3(b, c, d, e, a, 59);
> > - R4(a, b, c, d, e, 60);
> > - R4(e, a, b, c, d, 61);
> > - R4(d, e, a, b, c, 62);
> > - R4(c, d, e, a, b, 63);
> > - R4(b, c, d, e, a, 64);
> > - R4(a, b, c, d, e, 65);
> > - R4(e, a, b, c, d, 66);
> > - R4(d, e, a, b, c, 67);
> > - R4(c, d, e, a, b, 68);
> > - R4(b, c, d, e, a, 69);
> > - R4(a, b, c, d, e, 70);
> > - R4(e, a, b, c, d, 71);
> > - R4(d, e, a, b, c, 72);
> > - R4(c, d, e, a, b, 73);
> > - R4(b, c, d, e, a, 74);
> > - R4(a, b, c, d, e, 75);
> > - R4(e, a, b, c, d, 76);
> > - R4(d, e, a, b, c, 77);
> > - R4(c, d, e, a, b, 78);
> > - R4(b, c, d, e, a, 79);
> > - state[0] += a;
> > - state[1] += b;
> > - state[2] += c;
> > - state[3] += d;
> > - state[4] += e;
> > - /* Erase working structures. The order of operations is important,
> > - * used to ensure that compiler doesn't optimize those out. */
> > - memset(block, 0, sizeof(block));
> > - a = b = c = d = e = 0;
> > - (void) a;
> > - (void) b;
> > - (void) c;
> > - (void) d;
> > - (void) e;
> > +static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> > + long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> > + MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> > + return (int) n;
> > +}
> > +
> > +void mg_tls_handshake(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + int rc = mbedtls_ssl_handshake(&tls->ssl);
> > + if (rc == 0) { // Success
> > + MG_DEBUG(("%lu success", c->id));
> > + c->is_tls_hs = 0;
> > + mg_call(c, MG_EV_TLS_HS, NULL);
> > + } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> > + rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
> > + MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
> > + c->is_tls_hs, rc, -rc));
> > + } else {
> > + mg_error(c, "TLS handshake: -%#x", -rc); // Error
> > + }
> > }
> >
> > -void mg_sha1_init(mg_sha1_ctx *context) {
> > - context->state[0] = 0x67452301;
> > - context->state[1] = 0xEFCDAB89;
> > - context->state[2] = 0x98BADCFE;
> > - context->state[3] = 0x10325476;
> > - context->state[4] = 0xC3D2E1F0;
> > - context->count[0] = context->count[1] = 0;
> > +static void debug_cb(void *c, int lev, const char *s, int n, const char 
> *s2) {
> > + n = (int) strlen(s2) - 1;
> > + MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
> > + (void) s;
> > }
> >
> > -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
> > - size_t len) {
> > - size_t i, j;
> > +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> > + int rc = 0;
> > + c->tls = tls;
> > + if (c->tls == NULL) {
> > + mg_error(c, "TLS OOM");
> > + goto fail;
> > + }
> > + if (c->is_listening) goto fail;
> > + MG_DEBUG(("%lu Setting TLS", c->id));
> > + MG_PROF_ADD(c, "mbedtls_init_start");
> > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 
> 0x03000000 && \
> > + defined(MBEDTLS_PSA_CRYPTO_C)
> > + psa_crypto_init(); // 
> https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711
> > +#endif
> > + mbedtls_ssl_init(&tls->ssl);
> > + mbedtls_ssl_config_init(&tls->conf);
> > + mbedtls_x509_crt_init(&tls->ca);
> > + mbedtls_x509_crt_init(&tls->cert);
> > + mbedtls_pk_init(&tls->pk);
> > + mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> > +#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> > + mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> > +#endif
> > + if ((rc = mbedtls_ssl_config_defaults(
> > + &tls->conf,
> > + c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> > + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> > + mg_error(c, "tls defaults %#x", -rc);
> > + goto fail;
> > + }
> > + mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
> >
> > - j = context->count[0];
> > - if ((context->count[0] += (uint32_t) len << 3) < j) 
> context->count[1]++;
> > - context->count[1] += (uint32_t) (len >> 29);
> > - j = (j >> 3) & 63;
> > - if ((j + len) > 63) {
> > - memcpy(&context->buffer[j], data, (i = 64 - j));
> > - mg_sha1_transform(context->state, context->buffer);
> > - for (; i + 63 < len; i += 64) {
> > - mg_sha1_transform(context->state, &data[i]);
> > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> > + // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client 
> side
> > + // See https://github.com/Mbed-TLS/mbedtls/issues/7075
> > + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> > + } else {
> > + if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
> > + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> > + if (c->is_client && opts->name.buf != NULL && opts->name.buf[0] != 
> '\0') {
> > + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
> > + mbedtls_ssl_set_hostname(&tls->ssl, host);
> > + MG_DEBUG(("%lu hostname verification: %s", c->id, host));
> > + free(host);
> > }
> > - j = 0;
> > - } else
> > - i = 0;
> > - memcpy(&context->buffer[j], &data[i], len - i);
> > + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
> > + }
> > + if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
> > + if (!mg_load_key(opts->key, &tls->pk)) goto fail;
> > + if (tls->cert.version &&
> > + (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk)) != 
> 0) {
> > + mg_error(c, "own cert %#x", -rc);
> > + goto fail;
> > + }
> > +
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + mbedtls_ssl_conf_session_tickets_cb(
> > + &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
> > + &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
> > +#endif
> > +
> > + if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> > + mg_error(c, "setup err %#x", -rc);
> > + goto fail;
> > + }
> > + c->is_tls = 1;
> > + c->is_tls_hs = 1;
> > + mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> > + MG_PROF_ADD(c, "mbedtls_init_end");
> > + if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> > + mg_tls_handshake(c);
> > + }
> > + return;
> > +fail:
> > + mg_tls_free(c);
> > }
> >
> > -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
> > - unsigned i;
> > - unsigned char finalcount[8], c;
> > +size_t mg_tls_pending(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> > +}
> >
> > - for (i = 0; i < 8; i++) {
> > - finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
> > - ((3 - (i & 3)) * 8)) &
> > - 255);
> > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> > + return MG_IO_WAIT;
> > +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
> > + if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
> > + return MG_IO_WAIT;
> > }
> > - c = 0200;
> > - mg_sha1_update(context, &c, 1);
> > - while ((context->count[0] & 504) != 448) {
> > - c = 0000;
> > - mg_sha1_update(context, &c, 1);
> > +#endif
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > +}
> > +
> > +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> > + return MG_IO_WAIT;
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > +}
> > +
> > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1, sizeof(*ctx));
> > + if (ctx == NULL) {
> > + MG_ERROR(("TLS context init OOM"));
> > + } else {
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + int rc;
> > + mbedtls_ssl_ticket_init(&ctx->tickets);
> > + if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng, NULL,
> > + MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
> > + 0) {
> > + MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
> > + }
> > +#endif
> > + mgr->tls_ctx = ctx;
> > }
> > - mg_sha1_update(context, finalcount, 8);
> > - for (i = 0; i < 20; i++) {
> > - digest[i] =
> > - (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 
> 255);
> > +}
> > +
> > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
> > + if (ctx != NULL) {
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + mbedtls_ssl_ticket_free(&ctx->tickets);
> > +#endif
> > + free(ctx);
> > + mgr->tls_ctx = NULL;
> > }
> > - memset(context, '\0', sizeof(*context));
> > - memset(&finalcount, '\0', sizeof(finalcount));
> > }
> > +#endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/sntp.c"
> > +#line 1 "src/tls_openssl.c"
> > #endif
> >
> >
> >
> > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
> >
> > +static int tls_err_cb(const char *s, size_t len, void *c) {
> > + int n = (int) len - 1;
> > + MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
> > + return 0; // undocumented
> > +}
> >
> > +static int mg_tls_err(struct mg_connection *c, struct mg_tls *tls, int 
> res) {
> > + int err = SSL_get_error(tls->ssl, res);
> > + // We've just fetched the last error from the queue.
> > + // Now we need to clear the error queue. If we do not, then the 
> following
> > + // can happen (actually reported):
> > + // - A new connection is accept()-ed with cert error (e.g. self-signed 
> cert)
> > + // - Since all accept()-ed connections share listener's context,
> > + // - *ALL* SSL accepted connection report read error on the next poll 
> cycle.
> > + // Thus a single errored connection can close all the rest, unrelated 
> ones.
> > + // Clearing the error keeps the shared SSL_CTX in an OK state.
> >
> > -#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
> > -#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
> > + if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
> > + ERR_clear_error();
> > + if (err == SSL_ERROR_WANT_READ) return 0;
> > + if (err == SSL_ERROR_WANT_WRITE) return 0;
> > + return err;
> > +}
> >
> > -static int64_t gettimestamp(const uint32_t *data) {
> > - uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> > - if (sec) sec -= SNTP_TIME_OFFSET;
> > - return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 
> 1000.0);
> > +static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
> > + BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
> > + STACK_OF(X509_INFO) *certs =
> > + bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
> > + if (bio) BIO_free(bio);
> > + return certs;
> > }
> >
> > -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> > - int64_t res = -1;
> > - int mode = len > 0 ? buf[0] & 7 : 0;
> > - int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> > - if (len < 48) {
> > - MG_ERROR(("%s", "corrupt packet"));
> > - } else if (mode != 4 && mode != 5) {
> > - MG_ERROR(("%s", "not a server reply"));
> > - } else if (buf[1] == 0) {
> > - MG_ERROR(("%s", "server sent a kiss of death"));
> > - } else if (version == 4 || version == 3) {
> > - // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> > - int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> > - int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> > - int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> > - int64_t t3 = (int64_t) mg_millis();
> > - int64_t delta = (t3 - t0) - (t2 - t1);
> > - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
> > - res = t2 + delta / 2;
> > - } else {
> > - MG_ERROR(("unexpected version: %d", version));
> > +static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) * certs) {
> > + X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
> > + for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
> > + X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
> > + if (cert_info->x509 && !X509_STORE_add_cert(cert_store, 
> cert_info->x509))
> > + return false;
> > + }
> > + return true;
> > +}
> > +
> > +static EVP_PKEY *load_key(struct mg_str s) {
> > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> > + EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0, NULL) : 
> NULL;
> > + if (bio) BIO_free(bio);
> > + return key;
> > +}
> > +
> > +static X509 *load_cert(struct mg_str s) {
> > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> > + X509 *cert = bio == NULL ? NULL
> > + : s.buf[0] == '-'
> > + ? PEM_read_bio_X509(bio, NULL, NULL, NULL) // PEM
> > + : d2i_X509_bio(bio, NULL); // DER
> > + if (bio) BIO_free(bio);
> > + return cert;
> > +}
> > +
> > +static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
> > + long ret = 0;
> > + if (cmd == BIO_CTRL_PUSH) ret = 1;
> > + if (cmd == BIO_CTRL_POP) ret = 1;
> > + if (cmd == BIO_CTRL_FLUSH) ret = 1;
> > +#if MG_TLS == MG_TLS_OPENSSL
> > + if (cmd == BIO_C_SET_NBIO) ret = 1;
> > +#endif
> > + // MG_DEBUG(("%d -> %ld", cmd, ret));
> > + (void) b, (void) cmd, (void) larg, (void) pargs;
> > + return ret;
> > +}
> > +
> > +static int mg_bio_read(BIO *bio, char *buf, int len) {
> > + struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
> > + long res = mg_io_recv(c, buf, (size_t) len);
> > + // MG_DEBUG(("%p %d %ld", buf, len, res));
> > + len = res > 0 ? (int) res : -1;
> > + if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
> > + return len;
> > +}
> > +
> > +static int mg_bio_write(BIO *bio, const char *buf, int len) {
> > + struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
> > + long res = mg_io_send(c, buf, (size_t) len);
> > + // MG_DEBUG(("%p %d %ld", buf, len, res));
> > + len = res > 0 ? (int) res : -1;
> > + if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
> > + return len;
> > +}
> > +
> > +void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> > + const char *id = "mongoose";
> > + static unsigned char s_initialised = 0;
> > + BIO *bio = NULL;
> > + int rc;
> > +
> > + if (tls == NULL) {
> > + mg_error(c, "TLS OOM");
> > + goto fail;
> > + }
> > +
> > + if (!s_initialised) {
> > + SSL_library_init();
> > + s_initialised++;
> > + }
> > + MG_DEBUG(("%lu Setting TLS", c->id));
> > + tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> > + : SSL_CTX_new(SSLv23_server_method());
> > + if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> > + mg_error(c, "SSL_new");
> > + goto fail;
> > + }
> > + SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> > + (unsigned) strlen(id));
> > + // Disable deprecated protocols
> > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> > +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> > + SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> > +#endif
> > +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> > + SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> > +#endif
> > +
> > +#if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS)
> > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> > + // Older versions require that either the CA is loaded or 
> SSL_VERIFY_NONE
> > + // explicitly set
> > + SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
> > + }
> > +#endif
> > + if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
> > + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | 
> SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> > + NULL);
> > + STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
> > + rc = add_ca_certs(tls->ctx, certs);
> > + sk_X509_INFO_pop_free(certs, X509_INFO_free);
> > + if (!rc) {
> > + mg_error(c, "CA err");
> > + goto fail;
> > + }
> > + }
> > + if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
> > + X509 *cert = load_cert(opts->cert);
> > + rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
> > + X509_free(cert);
> > + if (cert == NULL || rc != 1) {
> > + mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
> > + goto fail;
> > + }
> > + }
> > + if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
> > + EVP_PKEY *key = load_key(opts->key);
> > + rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
> > + EVP_PKEY_free(key);
> > + if (key == NULL || rc != 1) {
> > + mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
> > + goto fail;
> > + }
> > + }
> > +
> > + SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> > +#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER > 0x10002000L
> > + (void) SSL_set_ecdh_auto(tls->ssl, 1);
> > +#endif
> > +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> > + if (opts->name.len > 0) {
> > + char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf);
> > +#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002
> > + SSL_set1_host(tls->ssl, s);
> > +#else
> > + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
> > +#endif
> > + SSL_set_tlsext_host_name(tls->ssl, s);
> > + free(s);
> > }
> > - return res;
> > -}
> > +#endif
> > +#if MG_TLS == MG_TLS_WOLFSSL
> > + tls->bm = BIO_meth_new(0, "bio_mg");
> > +#else
> > + tls->bm = BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, 
> "bio_mg");
> > +#endif
> > + BIO_meth_set_write(tls->bm, mg_bio_write);
> > + BIO_meth_set_read(tls->bm, mg_bio_read);
> > + BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
> >
> > -static void sntp_cb(struct mg_connection *c, int ev, void *evd, void 
> *fnd) {
> > - if (ev == MG_EV_READ) {
> > - int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> > - if (milliseconds > 0) {
> > - MG_INFO(("%lu got time: %lld ms from epoch", c->id, milliseconds));
> > - mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> > - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> > - (unsigned) (milliseconds % 1000)));
> > - }
> > - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
> > - } else if (ev == MG_EV_CONNECT) {
> > - mg_sntp_request(c);
> > - } else if (ev == MG_EV_CLOSE) {
> > + bio = BIO_new(tls->bm);
> > + BIO_set_data(bio, c);
> > + SSL_set_bio(tls->ssl, bio, bio);
> > +
> > + c->tls = tls;
> > + c->is_tls = 1;
> > + c->is_tls_hs = 1;
> > + if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> > + mg_tls_handshake(c);
> > }
> > - (void) fnd;
> > - (void) evd;
> > + MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : 
> "client"));
> > + return;
> > +fail:
> > + free(tls);
> > }
> >
> > -void mg_sntp_request(struct mg_connection *c) {
> > - if (c->is_resolving) {
> > - MG_ERROR(("%lu wait until resolved", c->id));
> > +void mg_tls_handshake(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
> > + if (rc == 1) {
> > + MG_DEBUG(("%lu success", c->id));
> > + c->is_tls_hs = 0;
> > + mg_call(c, MG_EV_TLS_HS, NULL);
> > } else {
> > - int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
> > - uint8_t buf[48] = {0};
> > - uint32_t *t = (uint32_t *) &buf[40];
> > - double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> > - buf[0] = (0 << 6) | (4 << 3) | 3;
> > - t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> > - t[1] = mg_htonl((uint32_t) frac);
> > - mg_send(c, buf, sizeof(buf));
> > + int code = mg_tls_err(c, tls, rc);
> > + if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
> > }
> > }
> >
> > -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char 
> *url,
> > - mg_event_handler_t fn, void *fnd) {
> > - struct mg_connection *c = NULL;
> > - if (url == NULL) url = "udp://time.google.com:123";
> > - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> > - return c;
> > +void mg_tls_free(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + if (tls == NULL) return;
> > + SSL_free(tls->ssl);
> > + SSL_CTX_free(tls->ctx);
> > + BIO_meth_free(tls->bm);
> > + free(tls);
> > + c->tls = NULL;
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/sock.c"
> > -#endif
> > -
> > +size_t mg_tls_pending(struct mg_connection *c) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> > +}
> >
> > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + int n = SSL_read(tls->ssl, buf, (int) len);
> > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > +}
> >
> > +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> > + int n = SSL_write(tls->ssl, buf, (int) len);
> > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> > + if (n <= 0) return MG_IO_ERR;
> > + return n;
> > +}
> >
> > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> > + (void) mgr;
> > +}
> >
> > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> > + (void) mgr;
> > +}
> > +#endif
> >
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/tls_uecc.c"
> > +#endif
> > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause 
> license. */
> >
> >
> >
> >
> > -#if MG_ENABLE_SOCKET
> > +#if MG_TLS == MG_TLS_BUILTIN
> >
> > -#ifndef closesocket
> > -#define closesocket(x) close(x)
> > +#ifndef MG_UECC_RNG_MAX_TRIES
> > +#define MG_UECC_RNG_MAX_TRIES 64
> > #endif
> >
> > -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> > -#define S2PTR(s_) ((void *) (size_t) (s_))
> > -
> > -#ifndef MSG_NONBLOCKING
> > -#define MSG_NONBLOCKING 0
> > +#if MG_UECC_ENABLE_VLI_API
> > +#define MG_UECC_VLI_API
> > +#else
> > +#define MG_UECC_VLI_API static
> > #endif
> >
> > -#ifndef AF_INET6
> > -#define AF_INET6 10
> > +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM == 
> mg_uecc_arm) || \
> > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb) || \
> > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
> > +#define CONCATX(a, ...) a##__VA_ARGS__
> > +#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
> > +
> > +#define STRX(a) #a
> > +#define STR(a) STRX(a)
> > +
> > +#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
> > +#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
> > +#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
> > +#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
> > +#define EVAL4(...) __VA_ARGS__
> > +
> > +#define DEC_1 0
> > +#define DEC_2 1
> > +#define DEC_3 2
> > +#define DEC_4 3
> > +#define DEC_5 4
> > +#define DEC_6 5
> > +#define DEC_7 6
> > +#define DEC_8 7
> > +#define DEC_9 8
> > +#define DEC_10 9
> > +#define DEC_11 10
> > +#define DEC_12 11
> > +#define DEC_13 12
> > +#define DEC_14 13
> > +#define DEC_15 14
> > +#define DEC_16 15
> > +#define DEC_17 16
> > +#define DEC_18 17
> > +#define DEC_19 18
> > +#define DEC_20 19
> > +#define DEC_21 20
> > +#define DEC_22 21
> > +#define DEC_23 22
> > +#define DEC_24 23
> > +#define DEC_25 24
> > +#define DEC_26 25
> > +#define DEC_27 26
> > +#define DEC_28 27
> > +#define DEC_29 28
> > +#define DEC_30 29
> > +#define DEC_31 30
> > +#define DEC_32 31
> > +
> > +#define DEC(N) CONCAT(DEC_, N)
> > +
> > +#define SECOND_ARG(_, val, ...) val
> > +#define SOME_CHECK_0 ~, 0
> > +#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
> > +#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
> > +
> > +#define EMPTY(...)
> > +#define DEFER(...) __VA_ARGS__ EMPTY()
> > +
> > +#define REPEAT_NAME_0() REPEAT_0
> > +#define REPEAT_NAME_SOME() REPEAT_SOME
> > +#define REPEAT_0(...)
> > +#define REPEAT_SOME(N, stuff) \
> > + DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff) stuff
> > +#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
> > +
> > +#define REPEATM_NAME_0() REPEATM_0
> > +#define REPEATM_NAME_SOME() REPEATM_SOME
> > +#define REPEATM_0(...)
> > +#define REPEATM_SOME(N, macro) \
> > + macro(N) DEFER(CONCAT(REPEATM_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), 
> macro)
> > +#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
> > #endif
> >
> > -union usa {
> > - struct sockaddr sa;
> > - struct sockaddr_in sin;
> > -#if MG_ENABLE_IPV6
> > - struct sockaddr_in6 sin6;
> > -#endif
> > -};
> > +//
> >
> > -static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> > - socklen_t len = sizeof(usa->sin);
> > - memset(usa, 0, sizeof(*usa));
> > - usa->sin.sin_family = AF_INET;
> > - usa->sin.sin_port = a->port;
> > - *(uint32_t *) &usa->sin.sin_addr = a->ip;
> > -#if MG_ENABLE_IPV6
> > - if (a->is_ip6) {
> > - usa->sin.sin_family = AF_INET6;
> > - usa->sin6.sin6_port = a->port;
> > - memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
> > - len = sizeof(usa->sin6);
> > - }
> > +#if (MG_UECC_WORD_SIZE == 1)
> > +#if MG_UECC_SUPPORTS_secp160r1
> > +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
> > #endif
> > - return len;
> > -}
> > -
> > -static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
> > - a->is_ip6 = is_ip6;
> > - a->port = usa->sin.sin_port;
> > - memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
> > -#if MG_ENABLE_IPV6
> > - if (is_ip6) {
> > - memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
> > - a->port = usa->sin6.sin6_port;
> > - }
> > +#if MG_UECC_SUPPORTS_secp192r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 24
> > #endif
> > -}
> > -
> > -static bool mg_sock_would_block(void) {
> > - int err = MG_SOCKET_ERRNO;
> > - return err == EINPROGRESS || err == EWOULDBLOCK
> > -#ifndef WINCE
> > - || err == EAGAIN || err == EINTR
> > +#if MG_UECC_SUPPORTS_secp224r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 28
> > #endif
> > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > - || err == WSAEINTR || err == WSAEWOULDBLOCK
> > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 32
> > #endif
> > - ;
> > -}
> > -
> > -static bool mg_sock_conn_reset(void) {
> > - int err = MG_SOCKET_ERRNO;
> > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > - return err == WSAECONNRESET;
> > -#else
> > - return err == EPIPE || err == ECONNRESET;
> > +#elif (MG_UECC_WORD_SIZE == 4)
> > +#if MG_UECC_SUPPORTS_secp160r1
> > +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
> > #endif
> > -}
> > +#if MG_UECC_SUPPORTS_secp192r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 6
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp224r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 7
> > +#endif
> > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 8
> > +#endif
> > +#elif (MG_UECC_WORD_SIZE == 8)
> > +#if MG_UECC_SUPPORTS_secp160r1
> > +#define MG_UECC_MAX_WORDS 3
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp192r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 3
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp224r1
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 4
> > +#endif
> > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> > +#undef MG_UECC_MAX_WORDS
> > +#define MG_UECC_MAX_WORDS 4
> > +#endif
> > +#endif /* MG_UECC_WORD_SIZE */
> > +
> > +#define BITS_TO_WORDS(num_bits) \
> > + ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
> > + (MG_UECC_WORD_SIZE * 8)))
> > +#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
> > +
> > +struct MG_UECC_Curve_t {
> > + wordcount_t num_words;
> > + wordcount_t num_bytes;
> > + bitcount_t num_n_bits;
> > + mg_uecc_word_t p[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t n[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
> > + mg_uecc_word_t b[MG_UECC_MAX_WORDS];
> > + void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
> > +#endif
> > + void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> > + MG_UECC_Curve curve);
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t *product);
> > +#endif
> > +};
> >
> > -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> > - union usa usa;
> > - socklen_t n = sizeof(usa);
> > - if (getsockname(fd, &usa.sa, &n) == 0) {
> > - tomgaddr(&usa, addr, n != sizeof(usa.sin));
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > +static void bcopy(uint8_t *dst, const uint8_t *src, unsigned num_bytes) 
> {
> > + while (0 != num_bytes) {
> > + num_bytes--;
> > + dst[num_bytes] = src[num_bytes];
> > }
> > }
> > +#endif
> >
> > -static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
> > - if (n == MG_IO_WAIT) {
> > - // Do nothing
> > - } else if (n <= 0) {
> > - c->is_closing = 1; // Termination. Don't call mg_error(): #1529
> > - } else if (n > 0) {
> > - if (c->is_hexdumping) {
> > - union usa usa;
> > - socklen_t slen = sizeof(usa.sin);
> > - if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0; // Ignore result
> > - MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
> > - r ? "<-" : "->", 4, &c->rem.ip, n));
> > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words);
> >
> > - mg_hexdump(buf, (size_t) n);
> > - }
> > - if (r) {
> > - c->recv.len += (size_t) n;
> > - mg_call(c, MG_EV_READ, &n);
> > - } else {
> > - mg_iobuf_del(&c->send, 0, (size_t) n);
> > - // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> > - if (c->send.len == 0) {
> > - MG_EPOLL_MOD(c, 0);
> > - }
> > - mg_call(c, MG_EV_WRITE, &n);
> > - }
> > - }
> > -}
> > +#if (MG_UECC_PLATFORM == mg_uecc_arm || \
> > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
> > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
> >
> > -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> > - long n;
> > - if (c->is_udp) {
> > - union usa usa;
> > - socklen_t slen = tousa(&c->rem, &usa);
> > - n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
> > - if (n > 0) setlocaddr(FD(c), &c->loc);
> > - } else {
> > - n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> > -#if MG_ARCH == MG_ARCH_RTX
> > - if (n == EWOULDBLOCK) return MG_IO_WAIT;
> > #endif
> > - }
> > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > -}
> >
> > -bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> > - if (c->is_udp) {
> > - long n = mg_io_send(c, buf, len);
> > - MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
> > - (int) c->recv.len, n, MG_SOCKET_ERRNO));
> > - iolog(c, (char *) buf, n, false);
> > - return n > 0;
> > - } else {
> > - return mg_iobuf_add(&c->send, c->send.len, buf, len);
> > - }
> > -}
> > +#if (MG_UECC_PLATFORM == mg_uecc_avr)
> >
> > -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> > -#if defined(MG_CUSTOM_NONBLOCK)
> > - MG_CUSTOM_NONBLOCK(fd);
> > -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> > - unsigned long on = 1;
> > - ioctlsocket(fd, FIONBIO, &on);
> > -#elif MG_ARCH == MG_ARCH_RTX
> > - unsigned long on = 1;
> > - ioctlsocket(fd, FIONBIO, &on);
> > -#elif MG_ENABLE_FREERTOS_TCP
> > - const BaseType_t off = 0;
> > - if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) 
> (void) 0;
> > - if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) 
> (void) 0;
> > -#elif MG_ENABLE_LWIP
> > - lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> > -#elif MG_ARCH == MG_ARCH_AZURERTOS
> > - fcntl(fd, F_SETFL, O_NONBLOCK);
> > -#elif MG_ARCH == MG_ARCH_TIRTOS
> > - int val = 0;
> > - setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> > - // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> > - int sz = sizeof(val);
> > - getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> > - val /= 2; // set send low-water mark at half send buffer size
> > - setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> > -#else
> > - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // 
> Non-blocking mode
> > - fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
> > #endif
> > -}
> >
> > -bool mg_open_listener(struct mg_connection *c, const char *url) {
> > - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> > - bool success = false;
> > - c->loc.port = mg_htons(mg_url_port(url));
> > - if (!mg_aton(mg_url_host(url), &c->loc)) {
> > - MG_ERROR(("invalid listening URL: %s", url));
> > - } else {
> > - union usa usa;
> > - socklen_t slen = tousa(&c->loc, &usa);
> > - int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> > - int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
> > - int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> > - (void) on;
> > +#ifndef asm_clear
> > +#define asm_clear 0
> > +#endif
> > +#ifndef asm_set
> > +#define asm_set 0
> > +#endif
> > +#ifndef asm_add
> > +#define asm_add 0
> > +#endif
> > +#ifndef asm_sub
> > +#define asm_sub 0
> > +#endif
> > +#ifndef asm_mult
> > +#define asm_mult 0
> > +#endif
> > +#ifndef asm_rshift1
> > +#define asm_rshift1 0
> > +#endif
> > +#ifndef asm_mmod_fast_secp256r1
> > +#define asm_mmod_fast_secp256r1 0
> > +#endif
> >
> > - if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> > - MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
> > -#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
> > - (defined(LWIP_SOCKET) && SO_REUSE == 1))
> > - } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
> > - sizeof(on)) != 0) {
> > - // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of
> > - // SO_REUSEADDR on UNIX and Windows is different. On Windows,
> > - // SO_REUSEADDR allows to bind a socket to a port without error even
> > - // if the port is already open by another program. This is not the
> > - // behavior SO_REUSEADDR was designed for, and leads to hard-to-track
> > - // failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows
> > - // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
> > - // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by
> > - // defining
> > - // SO_REUSE (in lwipopts.h), otherwise the code below will compile
> > - // but won't work! (setsockopt will return EINVAL)
> > - MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
> > -#endif
> > -#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && 
> !defined(WINCE)
> > - } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) 
> &on,
> > - sizeof(on)) != 0) {
> > - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> > - MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
> > +#if defined(default_RNG_defined) && default_RNG_defined
> > +static MG_UECC_RNG_Function g_rng_function = &default_RNG;
> > +#else
> > +static MG_UECC_RNG_Function g_rng_function = 0;
> > #endif
> > - } else if (bind(fd, &usa.sa, slen) != 0) {
> > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> > - } else if ((type == SOCK_STREAM &&
> > - listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
> > - // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> > - // In case port was set to 0, get the real port number
> > - MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
> > - } else {
> > - setlocaddr(fd, &c->loc);
> > - mg_set_non_blocking_mode(fd);
> > - c->fd = S2PTR(fd);
> > - MG_EPOLL_ADD(c);
> > - success = true;
> > - }
> > - }
> > - if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> > - return success;
> > +
> > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
> > + g_rng_function = rng_function;
> > +}
> > +
> > +MG_UECC_RNG_Function mg_uecc_get_rng(void) {
> > + return g_rng_function;
> > }
> >
> > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> > - long n = 0;
> > - if (c->is_udp) {
> > - union usa usa;
> > - socklen_t slen = tousa(&c->rem, &usa);
> > - n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
> > - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
> > - } else {
> > - n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
> > + return BITS_TO_BYTES(curve->num_n_bits);
> > +}
> > +
> > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
> > + return 2 * curve->num_bytes;
> > +}
> > +
> > +#if !asm_clear
> > +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
> > + wordcount_t num_words) {
> > + wordcount_t i;
> > + for (i = 0; i < num_words; ++i) {
> > + vli[i] = 0;
> > }
> > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > }
> > +#endif /* !asm_clear */
> >
> > -// NOTE(lsm): do only one iteration of reads, cause some systems
> > -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
> > -static void read_conn(struct mg_connection *c) {
> > - long n = -1;
> > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
> > - mg_error(c, "max_recv_buf_size reached");
> > - } else if (c->recv.size <= c->recv.len &&
> > - !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
> > - mg_error(c, "oom");
> > - } else {
> > - char *buf = (char *) &c->recv.buf[c->recv.len];
> > - size_t len = c->recv.size - c->recv.len;
> > - n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf, len);
> > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
> > - iolog(c, buf, n, true);
> > +/* Constant-time comparison to zero - secure way to compare long 
> integers */
> > +/* Returns 1 if vli == 0, 0 otherwise. */
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t 
> *vli,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t bits = 0;
> > + wordcount_t i;
> > + for (i = 0; i < num_words; ++i) {
> > + bits |= vli[i];
> > }
> > + return (bits == 0);
> > }
> >
> > -static void write_conn(struct mg_connection *c) {
> > - char *buf = (char *) c->send.buf;
> > - size_t len = c->send.len;
> > - long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, 
> len);
> > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
> > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
> > - iolog(c, buf, n, false);
> > +/* Returns nonzero if bit 'bit' of vli is set. */
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t 
> *vli,
> > + bitcount_t bit) {
> > + return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
> > + ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
> > }
> >
> > -static void close_conn(struct mg_connection *c) {
> > - if (FD(c) != MG_INVALID_SOCKET) {
> > -#if MG_ENABLE_EPOLL
> > - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> > -#endif
> > - closesocket(FD(c));
> > -#if MG_ENABLE_FREERTOS_TCP
> > - FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> > -#endif
> > +/* Counts the number of words in vli. */
> > +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
> > + const wordcount_t max_words) {
> > + wordcount_t i;
> > + /* Search from the end until we find a non-zero digit.
> > + We do it in reverse because we expect that most digits will be 
> nonzero. */
> > + for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
> > }
> > - mg_close_conn(c);
> > +
> > + return (i + 1);
> > }
> >
> > -static void connect_conn(struct mg_connection *c) {
> > - union usa usa;
> > - socklen_t n = sizeof(usa);
> > - // Use getpeername() to test whether we have connected
> > - if (getpeername(FD(c), &usa.sa, &n) == 0) {
> > - c->is_connecting = 0;
> > - mg_call(c, MG_EV_CONNECT, NULL);
> > - MG_EPOLL_MOD(c, 0);
> > - if (c->is_tls_hs) mg_tls_handshake(c);
> > - } else {
> > - mg_error(c, "socket error");
> > +/* Counts the number of bits required to represent vli. */
> > +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t 
> *vli,
> > + const wordcount_t max_words) {
> > + mg_uecc_word_t i;
> > + mg_uecc_word_t digit;
> > +
> > + wordcount_t num_digits = vli_numDigits(vli, max_words);
> > + if (num_digits == 0) {
> > + return 0;
> > }
> > -}
> >
> > -static void setsockopts(struct mg_connection *c) {
> > -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> > - MG_ARCH == MG_ARCH_TIRTOS
> > - (void) c;
> > -#else
> > - int on = 1;
> > -#if !defined(SOL_TCP)
> > -#define SOL_TCP IPPROTO_TCP
> > -#endif
> > - if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) 
> != 0)
> > - (void) 0;
> > - if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, 
> sizeof(on)) !=
> > - 0)
> > - (void) 0;
> > -#endif
> > + digit = vli[num_digits - 1];
> > + for (i = 0; digit; ++i) {
> > + digit >>= 1;
> > + }
> > +
> > + return (((bitcount_t) ((num_digits - 1) << MG_UECC_WORD_BITS_SHIFT)) +
> > + (bitcount_t) i);
> > }
> >
> > -void mg_connect_resolved(struct mg_connection *c) {
> > - int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> > - int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has 
> resolved IP
> > - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
> > - c->is_resolving = 0; // Clear resolving flag
> > - if (FD(c) == MG_INVALID_SOCKET) {
> > - mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
> > - } else if (c->is_udp) {
> > - MG_EPOLL_ADD(c);
> > -#if MG_ARCH == MG_ARCH_TIRTOS
> > - union usa usa; // TI-RTOS NDK requires binding to receive on UDP 
> sockets
> > - socklen_t slen = tousa(&c->loc, &usa);
> > - if (bind(c->fd, &usa.sa, slen) != 0)
> > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> > -#endif
> > - mg_call(c, MG_EV_RESOLVE, NULL);
> > - mg_call(c, MG_EV_CONNECT, NULL);
> > - } else {
> > - union usa usa;
> > - socklen_t slen = tousa(&c->rem, &usa);
> > - mg_set_non_blocking_mode(FD(c));
> > - setsockopts(c);
> > - MG_EPOLL_ADD(c);
> > - mg_call(c, MG_EV_RESOLVE, NULL);
> > - if ((rc = connect(FD(c), &usa.sa, slen)) == 0) {
> > - mg_call(c, MG_EV_CONNECT, NULL);
> > - } else if (mg_sock_would_block()) {
> > - MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
> > - mg_ntohs(c->rem.port)));
> > - c->is_connecting = 1;
> > - } else {
> > - mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
> > +/* Sets dest = src. */
> > +#if !asm_set
> > +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
> > + const mg_uecc_word_t *src,
> > + wordcount_t num_words) {
> > + wordcount_t i;
> > + for (i = 0; i < num_words; ++i) {
> > + dest[i] = src[i];
> > + }
> > +}
> > +#endif /* !asm_set */
> > +
> > +/* Returns sign of left - right. */
> > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + wordcount_t i;
> > + for (i = num_words - 1; i >= 0; --i) {
> > + if (left[i] > right[i]) {
> > + return 1;
> > + } else if (left[i] < right[i]) {
> > + return -1;
> > }
> > }
> > - (void) rc;
> > + return 0;
> > }
> >
> > -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> > - socklen_t *len) {
> > - MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
> > - do {
> > - memset(usa, 0, sizeof(*usa));
> > - s = accept(sock, &usa->sa, len);
> > - } while (s == MG_INVALID_SOCKET && errno == EINTR);
> > - return s;
> > +/* Constant-time comparison function - secure way to compare long 
> integers */
> > +/* Returns one if left == right, zero otherwise. */
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t diff = 0;
> > + wordcount_t i;
> > + for (i = num_words - 1; i >= 0; --i) {
> > + diff |= (left[i] ^ right[i]);
> > + }
> > + return (diff == 0);
> > +}
> > +
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words);
> > +
> > +/* Returns sign of left - right, in constant time. */
> > +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right, num_words);
> > + mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
> > + return (cmpresult_t) (!equal - 2 * neg);
> > +}
> > +
> > +/* Computes vli = vli >> 1. */
> > +#if !asm_rshift1
> > +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t *end = vli;
> > + mg_uecc_word_t carry = 0;
> > +
> > + vli += num_words;
> > + while (vli-- > end) {
> > + mg_uecc_word_t temp = *vli;
> > + *vli = (temp >> 1) | carry;
> > + carry = temp << (MG_UECC_WORD_BITS - 1);
> > + }
> > }
> > +#endif /* !asm_rshift1 */
> > +
> > +/* Computes result = left + right, returning carry. Can modify in 
> place. */
> > +#if !asm_add
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t carry = 0;
> > + wordcount_t i;
> > + for (i = 0; i < num_words; ++i) {
> > + mg_uecc_word_t sum = left[i] + right[i] + carry;
> > + if (sum != left[i]) {
> > + carry = (sum < left[i]);
> > + }
> > + result[i] = sum;
> > + }
> > + return carry;
> > +}
> > +#endif /* !asm_add */
> > +
> > +/* Computes result = left - right, returning borrow. Can modify in 
> place. */
> > +#if !asm_sub
> > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t borrow = 0;
> > + wordcount_t i;
> > + for (i = 0; i < num_words; ++i) {
> > + mg_uecc_word_t diff = left[i] - right[i] - borrow;
> > + if (diff != left[i]) {
> > + borrow = (diff > left[i]);
> > + }
> > + result[i] = diff;
> > + }
> > + return borrow;
> > +}
> > +#endif /* !asm_sub */
> > +
> > +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) || \
> > + (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0) && \
> > + ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
> > +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t 
> *r0,
> > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> > +#if MG_UECC_WORD_SIZE == 8
> > + uint64_t a0 = a & 0xffffffff;
> > + uint64_t a1 = a >> 32;
> > + uint64_t b0 = b & 0xffffffff;
> > + uint64_t b1 = b >> 32;
> > +
> > + uint64_t i0 = a0 * b0;
> > + uint64_t i1 = a0 * b1;
> > + uint64_t i2 = a1 * b0;
> > + uint64_t i3 = a1 * b1;
> > +
> > + uint64_t p0, p1;
> > +
> > + i2 += (i0 >> 32);
> > + i2 += i1;
> > + if (i2 < i1) { /* overflow */
> > + i3 += 0x100000000;
> > + }
> >
> > -static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
> > - struct mg_connection *c = NULL;
> > - union usa usa;
> > - socklen_t sa_len = sizeof(usa);
> > - MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> > - if (fd == MG_INVALID_SOCKET) {
> > -#if MG_ARCH == MG_ARCH_AZURERTOS
> > - // AzureRTOS, in non-block socket mode can mark listening socket 
> readable
> > - // even it is not. See comment for 'select' func implementation in
> > - // nx_bsd.c That's not an error, just should try later
> > - if (MG_SOCKET_ERRNO != EAGAIN)
> > -#endif
> > - MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCKET_ERRNO));
> > -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> > - (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
> > - } else if ((long) fd >= FD_SETSIZE) {
> > - MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> > - closesocket(fd);
> > + p0 = (i0 & 0xffffffff) | (i2 << 32);
> > + p1 = i3 + (i2 >> 32);
> > +
> > + *r0 += p0;
> > + *r1 += (p1 + (*r0 < p0));
> > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> > +#else
> > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | 
> *r0;
> > + r01 += p;
> > + *r2 += (r01 < p);
> > + *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
> > + *r0 = (mg_uecc_word_t) r01;
> > #endif
> > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> > - MG_ERROR(("%lu OOM", lsn->id));
> > - closesocket(fd);
> > - } else {
> > - tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> > - c->fd = S2PTR(fd);
> > - MG_EPOLL_ADD(c);
> > - mg_set_non_blocking_mode(FD(c));
> > - setsockopts(c);
> > - c->is_accepted = 1;
> > - c->is_hexdumping = lsn->is_hexdumping;
> > - c->loc = lsn->loc;
> > - c->pfn = lsn->pfn;
> > - c->pfn_data = lsn->pfn_data;
> > - c->fn = lsn->fn;
> > - c->fn_data = lsn->fn_data;
> > - MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4, 
> &c->rem.ip,
> > - mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
> > - mg_call(c, MG_EV_OPEN, NULL);
> > - mg_call(c, MG_EV_ACCEPT, NULL);
> > +}
> > +#endif /* muladd needed */
> > +
> > +#if !asm_mult
> > +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t r0 = 0;
> > + mg_uecc_word_t r1 = 0;
> > + mg_uecc_word_t r2 = 0;
> > + wordcount_t i, k;
> > +
> > + /* Compute each digit of result in sequence, maintaining the carries. 
> */
> > + for (k = 0; k < num_words; ++k) {
> > + for (i = 0; i <= k; ++i) {
> > + muladd(left[i], right[k - i], &r0, &r1, &r2);
> > + }
> > + result[k] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + r2 = 0;
> > }
> > + for (k = num_words; k < num_words * 2 - 1; ++k) {
> > + for (i = (wordcount_t) ((k + 1) - num_words); i < num_words; ++i) {
> > + muladd(left[i], right[k - i], &r0, &r1, &r2);
> > + }
> > + result[k] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + r2 = 0;
> > + }
> > + result[num_words * 2 - 1] = r0;
> > }
> > +#endif /* !asm_mult */
> >
> > -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2], bool 
> udp) {
> > - MG_SOCKET_TYPE sock;
> > - socklen_t n = sizeof(usa[0].sin);
> > - bool success = false;
> > +#if MG_UECC_SQUARE_FUNC
> >
> > - sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
> > - (void) memset(&usa[0], 0, sizeof(usa[0]));
> > - usa[0].sin.sin_family = AF_INET;
> > - *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1
> > - usa[1] = usa[0];
> > +#if !asm_square
> > +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t 
> *r0,
> > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> > +#if MG_UECC_WORD_SIZE == 8
> > + uint64_t a0 = a & 0xffffffffull;
> > + uint64_t a1 = a >> 32;
> > + uint64_t b0 = b & 0xffffffffull;
> > + uint64_t b1 = b >> 32;
> >
> > - if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != 
> MG_INVALID_SOCKET &&
> > - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> > - bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 &&
> > - getsockname(sp[0], &usa[0].sa, &n) == 0 &&
> > - getsockname(sp[1], &usa[1].sa, &n) == 0 &&
> > - connect(sp[0], &usa[1].sa, n) == 0 &&
> > - connect(sp[1], &usa[0].sa, n) == 0) {
> > - success = true;
> > - } else if (!udp &&
> > - (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> > - bind(sock, &usa[0].sa, n) == 0 &&
> > - listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
> > - getsockname(sock, &usa[0].sa, &n) == 0 &&
> > - (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> > - connect(sp[0], &usa[0].sa, n) == 0 &&
> > - (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
> > - success = true;
> > - }
> > - if (success) {
> > - mg_set_non_blocking_mode(sp[1]);
> > - } else {
> > - if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> > - if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> > - sp[0] = sp[1] = MG_INVALID_SOCKET;
> > + uint64_t i0 = a0 * b0;
> > + uint64_t i1 = a0 * b1;
> > + uint64_t i2 = a1 * b0;
> > + uint64_t i3 = a1 * b1;
> > +
> > + uint64_t p0, p1;
> > +
> > + i2 += (i0 >> 32);
> > + i2 += i1;
> > + if (i2 < i1) { /* overflow */
> > + i3 += 0x100000000ull;
> > }
> > - if (sock != MG_INVALID_SOCKET) closesocket(sock);
> > - return success;
> > +
> > + p0 = (i0 & 0xffffffffull) | (i2 << 32);
> > + p1 = i3 + (i2 >> 32);
> > +
> > + *r2 += (p1 >> 63);
> > + p1 = (p1 << 1) | (p0 >> 63);
> > + p0 <<= 1;
> > +
> > + *r0 += p0;
> > + *r1 += (p1 + (*r0 < p0));
> > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> > +#else
> > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | 
> *r0;
> > + *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
> > + p *= 2;
> > + r01 += p;
> > + *r2 += (r01 < p);
> > + *r1 = r01 >> MG_UECC_WORD_BITS;
> > + *r0 = (mg_uecc_word_t) r01;
> > +#endif
> > }
> >
> > -int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data,
> > - bool udp) {
> > - union usa usa[2];
> > - MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> > - struct mg_connection *c = NULL;
> > - if (!mg_socketpair(sp, usa, udp)) {
> > - MG_ERROR(("Cannot create socket pair"));
> > - } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) {
> > - closesocket(sp[0]);
> > - closesocket(sp[1]);
> > - sp[0] = sp[1] = MG_INVALID_SOCKET;
> > - } else {
> > - tomgaddr(&usa[0], &c->rem, false);
> > - MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
> > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t r0 = 0;
> > + mg_uecc_word_t r1 = 0;
> > + mg_uecc_word_t r2 = 0;
> > +
> > + wordcount_t i, k;
> > +
> > + for (k = 0; k < num_words * 2 - 1; ++k) {
> > + mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
> > + for (i = min; i <= k && i <= k - i; ++i) {
> > + if (i < k - i) {
> > + mul2add(left[i], left[k - i], &r0, &r1, &r2);
> > + } else {
> > + muladd(left[i], left[k - i], &r0, &r1, &r2);
> > + }
> > + }
> > + result[k] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + r2 = 0;
> > }
> > - return (int) sp[0];
> > -}
> >
> > -static bool can_read(const struct mg_connection *c) {
> > - return c->is_full == false;
> > + result[num_words * 2 - 1] = r0;
> > }
> > +#endif /* !asm_square */
> >
> > -static bool can_write(const struct mg_connection *c) {
> > - return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
> > -}
> > +#else /* MG_UECC_SQUARE_FUNC */
> >
> > -static bool skip_iotest(const struct mg_connection *c) {
> > - return (c->is_closing || c->is_resolving || FD(c) == 
> MG_INVALID_SOCKET) ||
> > - (can_read(c) == false && can_write(c) == false);
> > +#if MG_UECC_ENABLE_VLI_API
> > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + wordcount_t num_words) {
> > + mg_uecc_vli_mult(result, left, left, num_words);
> > }
> > +#endif /* MG_UECC_ENABLE_VLI_API */
> >
> > -static void mg_iotest(struct mg_mgr *mgr, int ms) {
> > -#if MG_ENABLE_FREERTOS_TCP
> > - struct mg_connection *c;
> > - for (c = mgr->conns; c != NULL; c = c->next) {
> > - c->is_readable = c->is_writable = 0;
> > - if (skip_iotest(c)) continue;
> > - if (can_read(c))
> > - FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> > - if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> > - }
> > - FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> > - for (c = mgr->conns; c != NULL; c = c->next) {
> > - EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> > - c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> > - c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> > - FreeRTOS_FD_CLR(c->fd, mgr->ss,
> > - eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> > +#endif /* MG_UECC_SQUARE_FUNC */
> > +
> > +/* Computes result = (left + right) % mod.
> > + Assumes that left < mod and right < mod, and that result does not 
> overlap
> > + mod. */
> > +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right, num_words);
> > + if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) != 1) {
> > + /* result > mod (result = mod + remainder), so subtract mod to get
> > + * remainder. */
> > + mg_uecc_vli_sub(result, result, mod, num_words);
> > }
> > -#elif MG_ENABLE_EPOLL
> > - size_t max = 1;
> > - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > - c->is_readable = c->is_writable = 0;
> > - if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
> > - if (can_write(c)) MG_EPOLL_MOD(c, 1);
> > - max++;
> > +}
> > +
> > +/* Computes result = (left - right) % mod.
> > + Assumes that left < mod and right < mod, and that result does not 
> overlap
> > + mod. */
> > +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right, 
> num_words);
> > + if (l_borrow) {
> > + /* In this case, result == -diff == (max int) - diff. Since -x % d == 
> d - x,
> > + we can get the correct result from result + mod (with overflow). */
> > + mg_uecc_vli_add(result, result, mod, num_words);
> > }
> > - struct epoll_event *evs = (struct epoll_event *) alloca(max * 
> sizeof(evs[0]));
> > - int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> > - for (int i = 0; i < n; i++) {
> > - struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
> > - if (evs[i].events & EPOLLERR) {
> > - mg_error(c, "socket error");
> > - } else if (c->is_readable == 0) {
> > - bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> > - bool wr = evs[i].events & EPOLLOUT;
> > - c->is_readable = can_read(c) && rd ? 1U : 0;
> > - c->is_writable = can_write(c) && wr ? 1U : 0;
> > +}
> > +
> > +/* Computes result = product % mod, where product is 2N words long. */
> > +/* Currently only designed to work for curve_p or curve_n. */
> > +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t *v[2] = {tmp, product};
> > + mg_uecc_word_t index;
> > +
> > + /* Shift mod so its highest set bit is at the maximum position. */
> > + bitcount_t shift = (bitcount_t) ((num_words * 2 * MG_UECC_WORD_BITS) -
> > + mg_uecc_vli_numBits(mod, num_words));
> > + wordcount_t word_shift = (wordcount_t) (shift / MG_UECC_WORD_BITS);
> > + wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
> > + mg_uecc_word_t carry = 0;
> > + mg_uecc_vli_clear(mod_multiple, word_shift);
> > + if (bit_shift > 0) {
> > + for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
> > + mod_multiple[(mg_uecc_word_t) word_shift + index] =
> > + (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
> > + carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
> > }
> > + } else {
> > + mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
> > }
> > - (void) skip_iotest;
> > -#elif MG_ENABLE_POLL
> > - nfds_t n = 0;
> > - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
> > - struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> > - memset(fds, 0, n * sizeof(fds[0]));
> > - n = 0;
> > - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > - c->is_readable = c->is_writable = 0;
> > - if (skip_iotest(c)) {
> > - // Socket not valid, ignore
> > - } else if (mg_tls_pending(c) > 0) {
> > - ms = 1; // Don't wait if TLS is ready
> > - } else {
> > - fds[n].fd = FD(c);
> > - if (can_read(c)) fds[n].events |= POLLIN;
> > - if (can_write(c)) fds[n].events |= POLLOUT;
> > - n++;
> > +
> > + for (index = 1; shift >= 0; --shift) {
> > + mg_uecc_word_t borrow = 0;
> > + wordcount_t i;
> > + for (i = 0; i < num_words * 2; ++i) {
> > + mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
> > + if (diff != v[index][i]) {
> > + borrow = (diff > v[index][i]);
> > + }
> > + v[1 - index][i] = diff;
> > }
> > + index = !(index ^ borrow); /* Swap the index if there was no borrow */
> > + mg_uecc_vli_rshift1(mod_multiple, num_words);
> > + mod_multiple[num_words - 1] |= mod_multiple[num_words]
> > + << (MG_UECC_WORD_BITS - 1);
> > + mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
> > + }
> > + mg_uecc_vli_set(result, v[index], num_words);
> > +}
> > +
> > +/* Computes result = (left * right) % mod. */
> > +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_vli_mult(product, left, right, num_words);
> > + mg_uecc_vli_mmod(result, product, mod, num_words);
> > +}
> > +
> > +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_vli_mult(product, left, right, curve->num_words);
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + curve->mmod_fast(result, product);
> > +#else
> > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> > +#endif
> > +}
> > +
> > +#if MG_UECC_SQUARE_FUNC
> > +
> > +#if MG_UECC_ENABLE_VLI_API
> > +/* Computes result = left^2 % mod. */
> > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_vli_square(product, left, num_words);
> > + mg_uecc_vli_mmod(result, product, mod, num_words);
> > +}
> > +#endif /* MG_UECC_ENABLE_VLI_API */
> > +
> > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> > + mg_uecc_vli_square(product, left, curve->num_words);
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + curve->mmod_fast(result, product);
> > +#else
> > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> > +#endif
> > +}
> > +
> > +#else /* MG_UECC_SQUARE_FUNC */
> > +
> > +#if MG_UECC_ENABLE_VLI_API
> > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_vli_modMult(result, left, left, mod, num_words);
> > +}
> > +#endif /* MG_UECC_ENABLE_VLI_API */
> > +
> > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_vli_modMult_fast(result, left, left, curve);
> > +}
> > +
> > +#endif /* MG_UECC_SQUARE_FUNC */
> > +
> > +#define EVEN(vli) (!(vli[0] & 1))
> > +static void vli_modInv_update(mg_uecc_word_t *uv, const mg_uecc_word_t 
> *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t carry = 0;
> > + if (!EVEN(uv)) {
> > + carry = mg_uecc_vli_add(uv, uv, mod, num_words);
> > }
> > + mg_uecc_vli_rshift1(uv, num_words);
> > + if (carry) {
> > + uv[num_words - 1] |= HIGH_BIT_SET;
> > + }
> > +}
> >
> > - // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> > - if (poll(fds, n, ms) < 0) {
> > -#if MG_ARCH == MG_ARCH_WIN32
> > - if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
> > -#endif
> > - memset(fds, 0, n * sizeof(fds[0]));
> > +/* Computes result = (1 / input) % mod. All VLIs are the same size.
> > + See "From Euclid's GCD to Montgomery Multiplication to the Great 
> Divide" */
> > +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *input,
> > + const mg_uecc_word_t *mod,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
> > + u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
> > + cmpresult_t cmpResult;
> > +
> > + if (mg_uecc_vli_isZero(input, num_words)) {
> > + mg_uecc_vli_clear(result, num_words);
> > + return;
> > }
> > - n = 0;
> > - for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
> > - if (skip_iotest(c)) {
> > - // Socket not valid, ignore
> > - } else if (mg_tls_pending(c) > 0) {
> > - c->is_readable = 1;
> > +
> > + mg_uecc_vli_set(a, input, num_words);
> > + mg_uecc_vli_set(b, mod, num_words);
> > + mg_uecc_vli_clear(u, num_words);
> > + u[0] = 1;
> > + mg_uecc_vli_clear(v, num_words);
> > + while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) != 0) {
> > + if (EVEN(a)) {
> > + mg_uecc_vli_rshift1(a, num_words);
> > + vli_modInv_update(u, mod, num_words);
> > + } else if (EVEN(b)) {
> > + mg_uecc_vli_rshift1(b, num_words);
> > + vli_modInv_update(v, mod, num_words);
> > + } else if (cmpResult > 0) {
> > + mg_uecc_vli_sub(a, a, b, num_words);
> > + mg_uecc_vli_rshift1(a, num_words);
> > + if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
> > + mg_uecc_vli_add(u, u, mod, num_words);
> > + }
> > + mg_uecc_vli_sub(u, u, v, num_words);
> > + vli_modInv_update(u, mod, num_words);
> > } else {
> > - if (fds[n].revents & POLLERR) {
> > - mg_error(c, "socket error");
> > - } else {
> > - c->is_readable =
> > - (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> > - c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> > + mg_uecc_vli_sub(b, b, a, num_words);
> > + mg_uecc_vli_rshift1(b, num_words);
> > + if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
> > + mg_uecc_vli_add(v, v, mod, num_words);
> > }
> > - n++;
> > + mg_uecc_vli_sub(v, v, u, num_words);
> > + vli_modInv_update(v, mod, num_words);
> > }
> > }
> > -#else
> > - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0};
> > - struct mg_connection *c;
> > - fd_set rset, wset, eset;
> > - MG_SOCKET_TYPE maxfd = 0;
> > - int rc;
> > + mg_uecc_vli_set(result, u, num_words);
> > +}
> >
> > - FD_ZERO(&rset);
> > - FD_ZERO(&wset);
> > - FD_ZERO(&eset);
> > - for (c = mgr->conns; c != NULL; c = c->next) {
> > - c->is_readable = c->is_writable = 0;
> > - if (skip_iotest(c)) continue;
> > - FD_SET(FD(c), &eset);
> > - if (can_read(c)) FD_SET(FD(c), &rset);
> > - if (can_write(c)) FD_SET(FD(c), &wset);
> > - if (mg_tls_pending(c) > 0) tv = tv_zero;
> > - if (FD(c) > maxfd) maxfd = FD(c);
> > +/* ------ Point operations ------ */
> > +
> > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause 
> license. */
> > +
> > +#ifndef _UECC_CURVE_SPECIFIC_H_
> > +#define _UECC_CURVE_SPECIFIC_H_
> > +
> > +#define num_bytes_secp160r1 20
> > +#define num_bytes_secp192r1 24
> > +#define num_bytes_secp224r1 28
> > +#define num_bytes_secp256r1 32
> > +#define num_bytes_secp256k1 32
> > +
> > +#if (MG_UECC_WORD_SIZE == 1)
> > +
> > +#define num_words_secp160r1 20
> > +#define num_words_secp192r1 24
> > +#define num_words_secp224r1 28
> > +#define num_words_secp256r1 32
> > +#define num_words_secp256k1 32
> > +
> > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
> > + 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
> > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
> > +
> > +#elif (MG_UECC_WORD_SIZE == 4)
> > +
> > +#define num_words_secp160r1 5
> > +#define num_words_secp192r1 6
> > +#define num_words_secp224r1 7
> > +#define num_words_secp256r1 8
> > +#define num_words_secp256k1 8
> > +
> > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 
> 0x##h##g##f##e
> > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
> > +
> > +#elif (MG_UECC_WORD_SIZE == 8)
> > +
> > +#define num_words_secp160r1 3
> > +#define num_words_secp192r1 3
> > +#define num_words_secp224r1 4
> > +#define num_words_secp256r1 4
> > +#define num_words_secp256k1 4
> > +
> > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 
> 0x##h##g##f##e##d##c##b##a##U
> > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
> > +
> > +#endif /* MG_UECC_WORD_SIZE */
> > +
> > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> > + MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
> > +static void double_jacobian_default(mg_uecc_word_t *X1, mg_uecc_word_t 
> *Y1,
> > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> > + /* t1 = X, t2 = Y, t3 = Z */
> > + mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
> > + wordcount_t num_words = curve->num_words;
> > +
> > + if (mg_uecc_vli_isZero(Z1, num_words)) {
> > + return;
> > }
> >
> > - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) < 0) {
> > -#if MG_ARCH == MG_ARCH_WIN32
> > - if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets
> > + mg_uecc_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */
> > + mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */
> > + mg_uecc_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */
> > + mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
> > + mg_uecc_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */
> > +
> > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 
> */
> > + mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */
> > + mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 
> */
> > + mg_uecc_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 - z1^4 */
> > +
> > + mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
> > + num_words); /* t3 = 2*(x1^2 - z1^4) */
> > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
> > + num_words); /* t1 = 3*(x1^2 - z1^4) */
> > + if (mg_uecc_vli_testBit(X1, 0)) {
> > + mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p, num_words);
> > + mg_uecc_vli_rshift1(X1, num_words);
> > + X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
> > + } else {
> > + mg_uecc_vli_rshift1(X1, num_words);
> > + }
> > + /* t1 = 3/2*(x1^2 - z1^4) = B */
> > +
> > + mg_uecc_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */
> > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */
> > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A 
> = x3 */
> > + mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */
> > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A - x3) */
> > + mg_uecc_vli_modSub(t4, X1, t4, curve->p,
> > + num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
> > +
> > + mg_uecc_vli_set(X1, Z1, num_words);
> > + mg_uecc_vli_set(Z1, Y1, num_words);
> > + mg_uecc_vli_set(Y1, t4, num_words);
> > +}
> > +
> > +/* Computes result = x^3 + ax + b. result must not overlap x. */
> > +static void x_side_default(mg_uecc_word_t *result, const mg_uecc_word_t 
> *x,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
> > + wordcount_t num_words = curve->num_words;
> > +
> > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
> > + mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /* r = 
> x^2 - 3 */
> > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */
> > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> > + num_words); /* r = x^3 - 3x + b */
> > +}
> > +#endif /* MG_UECC_SUPPORTS_secp... */
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> > + MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
> > +/* Compute a = sqrt(a) (mod curve_p). */
> > +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> > + bitcount_t i;
> > + mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
> > + mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
> > + wordcount_t num_words = curve->num_words;
> > +
> > + /* When curve->p == 3 (mod 4), we can compute
> > + sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
> > + mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */
> > + for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
> > + mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
> > + if (mg_uecc_vli_testBit(p1, i)) {
> > + mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
> > + }
> > + }
> > + mg_uecc_vli_set(a, l_result, num_words);
> > +}
> > +#endif /* MG_UECC_SUPPORTS_secp... */
> > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> > +
> > +#if MG_UECC_SUPPORTS_secp160r1
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product);
> > +#endif
> > +
> > +static const struct MG_UECC_Curve_t curve_secp160r1 = {
> > + num_words_secp160r1,
> > + num_bytes_secp160r1,
> > + 161, /* num_n_bits */
> > + {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
> > + BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
> > + {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
> > + BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
> > + BYTES_TO_WORDS_4(68, B5, 96, 4A),
> > +
> > + BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
> > + BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
> > + BYTES_TO_WORDS_4(55, 28, A6, 23)},
> > + {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
> > + BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
> > + BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
> > + &double_jacobian_default,
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + &mod_sqrt_default,
> > +#endif
> > + &x_side_default,
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + &vli_mmod_fast_secp160r1
> > +#endif
> > +};
> > +
> > +MG_UECC_Curve mg_uecc_secp160r1(void) {
> > + return &curve_secp160r1;
> > +}
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
> > +/* Computes result = product % curve_p
> > + see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354
> > +
> > + Note that this only works if log2(omega) < log2(p) / 2 */
> > +static void omega_mult_secp160r1(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *right);
> > +#if MG_UECC_WORD_SIZE == 8
> > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product) {
> > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
> > + mg_uecc_word_t copy;
> > +
> > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
> > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> > +
> > + omega_mult_secp160r1(tmp,
> > + product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
> > +
> > + product[num_words_secp160r1 - 1] &= 0xffffffff;
> > + copy = tmp[num_words_secp160r1 - 1];
> > + tmp[num_words_secp160r1 - 1] &= 0xffffffff;
> > + mg_uecc_vli_add(result, product, tmp,
> > + num_words_secp160r1); /* (C, r) = r + q */
> > + mg_uecc_vli_clear(product, num_words_secp160r1);
> > + tmp[num_words_secp160r1 - 1] = copy;
> > + omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c 
> */
> > + mg_uecc_vli_add(result, result, product,
> > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
> > +
> > + while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
> > + num_words_secp160r1) > 0) {
> > + mg_uecc_vli_sub(result, result, curve_secp160r1.p, 
> num_words_secp160r1);
> > + }
> > +}
> > +
> > +static void omega_mult_secp160r1(uint64_t *result, const uint64_t 
> *right) {
> > + uint32_t carry;
> > + unsigned i;
> > +
> > + /* Multiply by (2^31 + 1). */
> > + carry = 0;
> > + for (i = 0; i < num_words_secp160r1; ++i) {
> > + uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
> > + result[i] = (tmp << 31) + tmp + carry;
> > + carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i] == tmp));
> > + }
> > + result[i] = carry;
> > +}
> > #else
> > - MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
> > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product) {
> > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
> > + mg_uecc_word_t carry;
> > +
> > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
> > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> > +
> > + omega_mult_secp160r1(tmp,
> > + product + num_words_secp160r1); /* (Rq, q) = q * c */
> > +
> > + carry = mg_uecc_vli_add(result, product, tmp,
> > + num_words_secp160r1); /* (C, r) = r + q */
> > + mg_uecc_vli_clear(product, num_words_secp160r1);
> > + omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */
> > + carry += mg_uecc_vli_add(result, result, product,
> > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
> > +
> > + while (carry > 0) {
> > + --carry;
> > + mg_uecc_vli_sub(result, result, curve_secp160r1.p, 
> num_words_secp160r1);
> > + }
> > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p, 
> num_words_secp160r1) >
> > + 0) {
> > + mg_uecc_vli_sub(result, result, curve_secp160r1.p, 
> num_words_secp160r1);
> > + }
> > +}
> > #endif
> > - FD_ZERO(&rset);
> > - FD_ZERO(&wset);
> > - FD_ZERO(&eset);
> > +
> > +#if MG_UECC_WORD_SIZE == 1
> > +static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) 
> {
> > + uint8_t carry;
> > + uint8_t i;
> > +
> > + /* Multiply by (2^31 + 1). */
> > + mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */
> > + mg_uecc_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */
> > + result[3] = right[0] << 7; /* get last bit from shift */
> > +
> > + carry = mg_uecc_vli_add(result, result, right,
> > + num_words_secp160r1); /* 2^31 + 1 */
> > + for (i = num_words_secp160r1; carry; ++i) {
> > + uint16_t sum = (uint16_t) result[i] + carry;
> > + result[i] = (uint8_t) sum;
> > + carry = sum >> 8;
> > }
> > +}
> > +#elif MG_UECC_WORD_SIZE == 4
> > +static void omega_mult_secp160r1(uint32_t *result, const uint32_t 
> *right) {
> > + uint32_t carry;
> > + unsigned i;
> >
> > - for (c = mgr->conns; c != NULL; c = c->next) {
> > - if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> > - mg_error(c, "socket error");
> > - } else {
> > - c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
> > - c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
> > - if (mg_tls_pending(c) > 0) c->is_readable = 1;
> > - }
> > + /* Multiply by (2^31 + 1). */
> > + mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */
> > + mg_uecc_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */
> > + result[0] = right[0] << 31; /* get last bit from shift */
> > +
> > + carry = mg_uecc_vli_add(result, result, right,
> > + num_words_secp160r1); /* 2^31 + 1 */
> > + for (i = num_words_secp160r1; carry; ++i) {
> > + uint64_t sum = (uint64_t) result[i] + carry;
> > + result[i] = (uint32_t) sum;
> > + carry = sum >> 32;
> > }
> > +}
> > +#endif /* MG_UECC_WORD_SIZE */
> > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) 
> */
> > +
> > +#endif /* MG_UECC_SUPPORTS_secp160r1 */
> > +
> > +#if MG_UECC_SUPPORTS_secp192r1
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product);
> > #endif
> > +
> > +static const struct MG_UECC_Curve_t curve_secp192r1 = {
> > + num_words_secp192r1,
> > + num_bytes_secp192r1,
> > + 192, /* num_n_bits */
> > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
> > + BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
> > + BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
> > + BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
> > +
> > + BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
> > + BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
> > + BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
> > + {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
> > + BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
> > + BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
> > + &double_jacobian_default,
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + &mod_sqrt_default,
> > +#endif
> > + &x_side_default,
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + &vli_mmod_fast_secp192r1
> > +#endif
> > +};
> > +
> > +MG_UECC_Curve mg_uecc_secp192r1(void) {
> > + return &curve_secp192r1;
> > }
> >
> > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> > - struct mg_connection *c, *tmp;
> > - uint64_t now;
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +/* Computes result = product % curve_p.
> > + See algorithm 5 and 6 from 
> http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
> > + */
> > +#if MG_UECC_WORD_SIZE == 1
> > +static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) {
> > + uint8_t tmp[num_words_secp192r1];
> > + uint8_t carry;
> > +
> > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> > +
> > + mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
> > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] 
> = 0;
> > + tmp[8] = product[24];
> > + tmp[9] = product[25];
> > + tmp[10] = product[26];
> > + tmp[11] = product[27];
> > + tmp[12] = product[28];
> > + tmp[13] = product[29];
> > + tmp[14] = product[30];
> > + tmp[15] = product[31];
> > + tmp[16] = product[32];
> > + tmp[17] = product[33];
> > + tmp[18] = product[34];
> > + tmp[19] = product[35];
> > + tmp[20] = product[36];
> > + tmp[21] = product[37];
> > + tmp[22] = product[38];
> > + tmp[23] = product[39];
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + tmp[0] = tmp[8] = product[40];
> > + tmp[1] = tmp[9] = product[41];
> > + tmp[2] = tmp[10] = product[42];
> > + tmp[3] = tmp[11] = product[43];
> > + tmp[4] = tmp[12] = product[44];
> > + tmp[5] = tmp[13] = product[45];
> > + tmp[6] = tmp[14] = product[46];
> > + tmp[7] = tmp[15] = product[47];
> > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] =
> > + tmp[23] = 0;
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> > + num_words_secp192r1) != 1) {
> > + carry -=
> > + mg_uecc_vli_sub(result, result, curve_secp192r1.p, 
> num_words_secp192r1);
> > + }
> > +}
> > +#elif MG_UECC_WORD_SIZE == 4
> > +static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t 
> *product) {
> > + uint32_t tmp[num_words_secp192r1];
> > + int carry;
> >
> > - mg_iotest(mgr, ms);
> > - now = mg_millis();
> > - mg_timer_poll(&mgr->timers, now);
> > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> >
> > - for (c = mgr->conns; c != NULL; c = tmp) {
> > - bool is_resp = c->is_resp;
> > - tmp = c->next;
> > - mg_call(c, MG_EV_POLL, &now);
> > - if (is_resp && !c->is_resp) {
> > - long n = 0;
> > - mg_call(c, MG_EV_READ, &n);
> > - }
> > - MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
> > - c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
> > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> > - if (c->is_resolving || c->is_closing) {
> > - // Do nothing
> > - } else if (c->is_listening && c->is_udp == 0) {
> > - if (c->is_readable) accept_conn(mgr, c);
> > - } else if (c->is_connecting) {
> > - if (c->is_readable || c->is_writable) connect_conn(c);
> > - } else if (c->is_tls_hs) {
> > - if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> > - } else {
> > - if (c->is_readable) read_conn(c);
> > - if (c->is_writable) write_conn(c);
> > - }
> > + mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
> > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> >
> > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> > - if (c->is_closing) close_conn(c);
> > + tmp[0] = tmp[1] = 0;
> > + tmp[2] = product[6];
> > + tmp[3] = product[7];
> > + tmp[4] = product[8];
> > + tmp[5] = product[9];
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + tmp[0] = tmp[2] = product[10];
> > + tmp[1] = tmp[3] = product[11];
> > + tmp[4] = tmp[5] = 0;
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> > + num_words_secp192r1) != 1) {
> > + carry -=
> > + mg_uecc_vli_sub(result, result, curve_secp192r1.p, 
> num_words_secp192r1);
> > + }
> > +}
> > +#else
> > +static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t 
> *product) {
> > + uint64_t tmp[num_words_secp192r1];
> > + int carry;
> > +
> > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> > +
> > + mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
> > + carry = (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp192r1);
> > +
> > + tmp[0] = 0;
> > + tmp[1] = product[3];
> > + tmp[2] = product[4];
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + tmp[0] = tmp[1] = product[5];
> > + tmp[2] = 0;
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> > +
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> > + num_words_secp192r1) != 1) {
> > + carry -=
> > + mg_uecc_vli_sub(result, result, curve_secp192r1.p, 
> num_words_secp192r1);
> > }
> > }
> > +#endif /* MG_UECC_WORD_SIZE */
> > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
> > +
> > +#endif /* MG_UECC_SUPPORTS_secp192r1 */
> > +
> > +#if MG_UECC_SUPPORTS_secp224r1
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve);
> > +#endif
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product);
> > #endif
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/ssi.c"
> > +static const struct MG_UECC_Curve_t curve_secp224r1 = {
> > + num_words_secp224r1,
> > + num_bytes_secp224r1,
> > + 224, /* num_n_bits */
> > + {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
> > + BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
> > + BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
> > + BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
> > + BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
> > +
> > + BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
> > + BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
> > + BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
> > + BYTES_TO_WORDS_4(88, 63, 37, BD)},
> > + {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
> > + BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
> > + BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
> > + BYTES_TO_WORDS_4(85, 0A, 05, B4)},
> > + &double_jacobian_default,
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + &mod_sqrt_secp224r1,
> > #endif
> > + &x_side_default,
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + &vli_mmod_fast_secp224r1
> > +#endif
> > +};
> > +
> > +MG_UECC_Curve mg_uecc_secp224r1(void) {
> > + return &curve_secp224r1;
> > +}
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +/* Routine 3.2.4 RS; from 
> http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1, mg_uecc_word_t 
> *e1,
> > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> > + const mg_uecc_word_t *e0,
> > + const mg_uecc_word_t *f0) {
> > + mg_uecc_word_t t[num_words_secp224r1];
> > +
> > + mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <-- d0 ^ 2 */
> > + mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * 
> e0 */
> > + mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
> > + num_words_secp224r1); /* d1 <-- t + f0 */
> > + mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
> > + num_words_secp224r1); /* e1 <-- e1 + e1 */
> > + mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t * 
> f0 */
> > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> > + num_words_secp224r1); /* f1 <-- f1 + f1 */
> > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> > + num_words_secp224r1); /* f1 <-- f1 + f1 */
> > +}
> > +
> > +/* Routine 3.2.5 RSS; from 
> http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1, mg_uecc_word_t 
> *e1,
> > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> > + const mg_uecc_word_t *e0,
> > + const mg_uecc_word_t *f0,
> > + const bitcount_t j) {
> > + bitcount_t i;
> > +
> > + mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
> > + mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
> > + mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
> > + for (i = 1; i <= j; i++) {
> > + mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS 
> (d1,e1,f1,d1,e1,f1) */
> > + }
> > +}
> > +
> > +/* Routine 3.2.6 RM; from 
> http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2, mg_uecc_word_t 
> *e2,
> > + mg_uecc_word_t *f2, const mg_uecc_word_t *c,
> > + const mg_uecc_word_t *d0,
> > + const mg_uecc_word_t *e0,
> > + const mg_uecc_word_t *d1,
> > + const mg_uecc_word_t *e1) {
> > + mg_uecc_word_t t1[num_words_secp224r1];
> > + mg_uecc_word_t t2[num_words_secp224r1];
> > +
> > + mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * 
> e1 */
> > + mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1 <-- t1 * 
> c */
> > + /* t1 <-- p - t1 */
> > + mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * 
> d1 */
> > + mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
> > + num_words_secp224r1); /* t2 <-- t2 + t1 */
> > + mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * 
> e1 */
> > + mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * 
> e0 */
> > + mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
> > + num_words_secp224r1); /* e2 <-- e2 + t1 */
> > + mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <-- e2^2 */
> > + mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * 
> c */
> > + /* f2 <-- p - f2 */
> > + mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
> > +}
> > +
> > +/* Routine 3.2.7 RP; from 
> http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1, mg_uecc_word_t 
> *e1,
> > + mg_uecc_word_t *f1, const mg_uecc_word_t *c,
> > + const mg_uecc_word_t *r) {
> > + wordcount_t i;
> > + wordcount_t pow2i = 1;
> > + mg_uecc_word_t d0[num_words_secp224r1];
> > + mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
> > + mg_uecc_word_t f0[num_words_secp224r1];
> > +
> > + mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
> > + /* f0 <-- p - c */
> > + mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + for (i = 0; i <= 6; i++) {
> > + mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
> > + pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
> > + mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
> > + e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
> > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> > + pow2i *= 2;
> > + }
> > +}
> >
> > +/* Compute a = sqrt(a) (mod curve_p). */
> > +/* Routine 3.2.8 mp_mod_sqrt_224; from
> > + * http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> > + (void) curve;
> > + bitcount_t i;
> > + mg_uecc_word_t e1[num_words_secp224r1];
> > + mg_uecc_word_t f1[num_words_secp224r1];
> > + mg_uecc_word_t d0[num_words_secp224r1];
> > + mg_uecc_word_t e0[num_words_secp224r1];
> > + mg_uecc_word_t f0[num_words_secp224r1];
> > + mg_uecc_word_t d1[num_words_secp224r1];
> > +
> > + /* s = a; using constant instead of random value */
> > + mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */
> > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
> > + for (i = 1; i <= 95; i++) {
> > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
> > + if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */
> > + break;
> > + }
> > + }
> > + mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
> > + num_words_secp224r1); /* f1 <-- 1 / e0 */
> > + mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <-- d0 / 
> e0 */
> > +}
> > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +/* Computes result = product % curve_p
> > + from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +#if MG_UECC_WORD_SIZE == 1
> > +static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) {
> > + uint8_t tmp[num_words_secp224r1];
> > + int8_t carry;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> > +
> > + /* s1 */
> > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> > + tmp[12] = product[28];
> > + tmp[13] = product[29];
> > + tmp[14] = product[30];
> > + tmp[15] = product[31];
> > + tmp[16] = product[32];
> > + tmp[17] = product[33];
> > + tmp[18] = product[34];
> > + tmp[19] = product[35];
> > + tmp[20] = product[36];
> > + tmp[21] = product[37];
> > + tmp[22] = product[38];
> > + tmp[23] = product[39];
> > + tmp[24] = product[40];
> > + tmp[25] = product[41];
> > + tmp[26] = product[42];
> > + tmp[27] = product[43];
> > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* s2 */
> > + tmp[12] = product[44];
> > + tmp[13] = product[45];
> > + tmp[14] = product[46];
> > + tmp[15] = product[47];
> > + tmp[16] = product[48];
> > + tmp[17] = product[49];
> > + tmp[18] = product[50];
> > + tmp[19] = product[51];
> > + tmp[20] = product[52];
> > + tmp[21] = product[53];
> > + tmp[22] = product[54];
> > + tmp[23] = product[55];
> > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d1 */
> > + tmp[0] = product[28];
> > + tmp[1] = product[29];
> > + tmp[2] = product[30];
> > + tmp[3] = product[31];
> > + tmp[4] = product[32];
> > + tmp[5] = product[33];
> > + tmp[6] = product[34];
> > + tmp[7] = product[35];
> > + tmp[8] = product[36];
> > + tmp[9] = product[37];
> > + tmp[10] = product[38];
> > + tmp[11] = product[39];
> > + tmp[12] = product[40];
> > + tmp[13] = product[41];
> > + tmp[14] = product[42];
> > + tmp[15] = product[43];
> > + tmp[16] = product[44];
> > + tmp[17] = product[45];
> > + tmp[18] = product[46];
> > + tmp[19] = product[47];
> > + tmp[20] = product[48];
> > + tmp[21] = product[49];
> > + tmp[22] = product[50];
> > + tmp[23] = product[51];
> > + tmp[24] = product[52];
> > + tmp[25] = product[53];
> > + tmp[26] = product[54];
> > + tmp[27] = product[55];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d2 */
> > + tmp[0] = product[44];
> > + tmp[1] = product[45];
> > + tmp[2] = product[46];
> > + tmp[3] = product[47];
> > + tmp[4] = product[48];
> > + tmp[5] = product[49];
> > + tmp[6] = product[50];
> > + tmp[7] = product[51];
> > + tmp[8] = product[52];
> > + tmp[9] = product[53];
> > + tmp[10] = product[54];
> > + tmp[11] = product[55];
> > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + } while (carry < 0);
> > + } else {
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> > + num_words_secp224r1) != 1) {
> > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + }
> > + }
> > +}
> > +#elif MG_UECC_WORD_SIZE == 4
> > +static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t 
> *product) {
> > + uint32_t tmp[num_words_secp224r1];
> > + int carry;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> > +
> > + /* s1 */
> > + tmp[0] = tmp[1] = tmp[2] = 0;
> > + tmp[3] = product[7];
> > + tmp[4] = product[8];
> > + tmp[5] = product[9];
> > + tmp[6] = product[10];
> > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* s2 */
> > + tmp[3] = product[11];
> > + tmp[4] = product[12];
> > + tmp[5] = product[13];
> > + tmp[6] = 0;
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d1 */
> > + tmp[0] = product[7];
> > + tmp[1] = product[8];
> > + tmp[2] = product[9];
> > + tmp[3] = product[10];
> > + tmp[4] = product[11];
> > + tmp[5] = product[12];
> > + tmp[6] = product[13];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d2 */
> > + tmp[0] = product[11];
> > + tmp[1] = product[12];
> > + tmp[2] = product[13];
> > + tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + } while (carry < 0);
> > + } else {
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> > + num_words_secp224r1) != 1) {
> > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + }
> > + }
> > +}
> > +#else
> > +static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t 
> *product) {
> > + uint64_t tmp[num_words_secp224r1];
> > + int carry = 0;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> > + result[num_words_secp224r1 - 1] &= 0xffffffff;
> > +
> > + /* s1 */
> > + tmp[0] = 0;
> > + tmp[1] = product[3] & 0xffffffff00000000ull;
> > + tmp[2] = product[4];
> > + tmp[3] = product[5] & 0xffffffff;
> > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* s2 */
> > + tmp[1] = product[5] & 0xffffffff00000000ull;
> > + tmp[2] = product[6];
> > + tmp[3] = 0;
> > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d1 */
> > + tmp[0] = (product[3] >> 32) | (product[4] << 32);
> > + tmp[1] = (product[4] >> 32) | (product[5] << 32);
> > + tmp[2] = (product[5] >> 32) | (product[6] << 32);
> > + tmp[3] = product[6] >> 32;
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + /* d2 */
> > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
> > + tmp[1] = product[6] >> 32;
> > + tmp[2] = tmp[3] = 0;
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> > + num_words_secp224r1);
> > + } while (carry < 0);
> > + } else {
> > + while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> > + num_words_secp224r1) != 1) {
> > + mg_uecc_vli_sub(result, result, curve_secp224r1.p, 
> num_words_secp224r1);
> > + }
> > + }
> > +}
> > +#endif /* MG_UECC_WORD_SIZE */
> > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
> >
> > +#endif /* MG_UECC_SUPPORTS_secp224r1 */
> >
> > +#if MG_UECC_SUPPORTS_secp256r1
> >
> > -#ifndef MG_MAX_SSI_DEPTH
> > -#define MG_MAX_SSI_DEPTH 5
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product);
> > #endif
> >
> > -#ifndef MG_SSI_BUFSIZ
> > -#define MG_SSI_BUFSIZ 1024
> > +static const struct MG_UECC_Curve_t curve_secp256r1 = {
> > + num_words_secp256r1,
> > + num_bytes_secp256r1,
> > + 256, /* num_n_bits */
> > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
> > + BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
> > + BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
> > + BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
> > + BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
> > +
> > + BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
> > + BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
> > + BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
> > + BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
> > + {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
> > + BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
> > + BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
> > + BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
> > + &double_jacobian_default,
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + &mod_sqrt_default,
> > +#endif
> > + &x_side_default,
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + &vli_mmod_fast_secp256r1
> > #endif
> > +};
> >
> > -#if MG_ENABLE_SSI
> > -static char *mg_ssi(const char *path, const char *root, int depth) {
> > - struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> > - FILE *fp = fopen(path, "rb");
> > - if (fp != NULL) {
> > - char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> > - int ch, intag = 0;
> > - size_t len = 0;
> > - buf[0] = arg[0] = '\0';
> > - while ((ch = fgetc(fp)) != EOF) {
> > - if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
> > - buf[len++] = (char) (ch & 0xff);
> > - buf[len] = '\0';
> > - if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> > - *p = (char *) path + strlen(path), *data;
> > - while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> > - mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
> > - if (depth < MG_MAX_SSI_DEPTH &&
> > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> > - mg_iobuf_add(&b, b.len, data, strlen(data));
> > - free(data);
> > - } else {
> > - MG_ERROR(("%s: file=%s error or too deep", path, arg));
> > - }
> > - } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> > - mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> > - if (depth < MG_MAX_SSI_DEPTH &&
> > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> > - mg_iobuf_add(&b, b.len, data, strlen(data));
> > - free(data);
> > - } else {
> > - MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> > - }
> > - } else {
> > - // Unknown SSI tag
> > - MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> > - mg_iobuf_add(&b, b.len, buf, len);
> > - }
> > - intag = 0;
> > - len = 0;
> > - } else if (ch == '<') {
> > - intag = 1;
> > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> > - len = 0;
> > - buf[len++] = (char) (ch & 0xff);
> > - } else if (intag) {
> > - if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> > - intag = 0;
> > - } else if (len >= sizeof(buf) - 2) {
> > - MG_ERROR(("%s: SSI tag is too large", path));
> > - len = 0;
> > - }
> > - buf[len++] = (char) (ch & 0xff);
> > - } else {
> > - buf[len++] = (char) (ch & 0xff);
> > - if (len >= sizeof(buf)) {
> > - mg_iobuf_add(&b, b.len, buf, len);
> > - len = 0;
> > - }
> > - }
> > +MG_UECC_Curve mg_uecc_secp256r1(void) {
> > + return &curve_secp256r1;
> > +}
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
> > +/* Computes result = product % curve_p
> > + from http://www.nsa.gov/ia/_files/nist-routines.pdf */
> > +#if MG_UECC_WORD_SIZE == 1
> > +static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) {
> > + uint8_t tmp[num_words_secp256r1];
> > + int8_t carry;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> > +
> > + /* s1 */
> > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> > + tmp[12] = product[44];
> > + tmp[13] = product[45];
> > + tmp[14] = product[46];
> > + tmp[15] = product[47];
> > + tmp[16] = product[48];
> > + tmp[17] = product[49];
> > + tmp[18] = product[50];
> > + tmp[19] = product[51];
> > + tmp[20] = product[52];
> > + tmp[21] = product[53];
> > + tmp[22] = product[54];
> > + tmp[23] = product[55];
> > + tmp[24] = product[56];
> > + tmp[25] = product[57];
> > + tmp[26] = product[58];
> > + tmp[27] = product[59];
> > + tmp[28] = product[60];
> > + tmp[29] = product[61];
> > + tmp[30] = product[62];
> > + tmp[31] = product[63];
> > + carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> > +
> > + /* s2 */
> > + tmp[12] = product[48];
> > + tmp[13] = product[49];
> > + tmp[14] = product[50];
> > + tmp[15] = product[51];
> > + tmp[16] = product[52];
> > + tmp[17] = product[53];
> > + tmp[18] = product[54];
> > + tmp[19] = product[55];
> > + tmp[20] = product[56];
> > + tmp[21] = product[57];
> > + tmp[22] = product[58];
> > + tmp[23] = product[59];
> > + tmp[24] = product[60];
> > + tmp[25] = product[61];
> > + tmp[26] = product[62];
> > + tmp[27] = product[63];
> > + tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
> > + carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> > +
> > + /* s3 */
> > + tmp[0] = product[32];
> > + tmp[1] = product[33];
> > + tmp[2] = product[34];
> > + tmp[3] = product[35];
> > + tmp[4] = product[36];
> > + tmp[5] = product[37];
> > + tmp[6] = product[38];
> > + tmp[7] = product[39];
> > + tmp[8] = product[40];
> > + tmp[9] = product[41];
> > + tmp[10] = product[42];
> > + tmp[11] = product[43];
> > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> > + tmp[24] = product[56];
> > + tmp[25] = product[57];
> > + tmp[26] = product[58];
> > + tmp[27] = product[59];
> > + tmp[28] = product[60];
> > + tmp[29] = product[61];
> > + tmp[30] = product[62];
> > + tmp[31] = product[63];
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> > +
> > + /* s4 */
> > + tmp[0] = product[36];
> > + tmp[1] = product[37];
> > + tmp[2] = product[38];
> > + tmp[3] = product[39];
> > + tmp[4] = product[40];
> > + tmp[5] = product[41];
> > + tmp[6] = product[42];
> > + tmp[7] = product[43];
> > + tmp[8] = product[44];
> > + tmp[9] = product[45];
> > + tmp[10] = product[46];
> > + tmp[11] = product[47];
> > + tmp[12] = product[52];
> > + tmp[13] = product[53];
> > + tmp[14] = product[54];
> > + tmp[15] = product[55];
> > + tmp[16] = product[56];
> > + tmp[17] = product[57];
> > + tmp[18] = product[58];
> > + tmp[19] = product[59];
> > + tmp[20] = product[60];
> > + tmp[21] = product[61];
> > + tmp[22] = product[62];
> > + tmp[23] = product[63];
> > + tmp[24] = product[52];
> > + tmp[25] = product[53];
> > + tmp[26] = product[54];
> > + tmp[27] = product[55];
> > + tmp[28] = product[32];
> > + tmp[29] = product[33];
> > + tmp[30] = product[34];
> > + tmp[31] = product[35];
> > + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
> > +
> > + /* d1 */
> > + tmp[0] = product[44];
> > + tmp[1] = product[45];
> > + tmp[2] = product[46];
> > + tmp[3] = product[47];
> > + tmp[4] = product[48];
> > + tmp[5] = product[49];
> > + tmp[6] = product[50];
> > + tmp[7] = product[51];
> > + tmp[8] = product[52];
> > + tmp[9] = product[53];
> > + tmp[10] = product[54];
> > + tmp[11] = product[55];
> > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> > + tmp[24] = product[32];
> > + tmp[25] = product[33];
> > + tmp[26] = product[34];
> > + tmp[27] = product[35];
> > + tmp[28] = product[40];
> > + tmp[29] = product[41];
> > + tmp[30] = product[42];
> > + tmp[31] = product[43];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> > +
> > + /* d2 */
> > + tmp[0] = product[48];
> > + tmp[1] = product[49];
> > + tmp[2] = product[50];
> > + tmp[3] = product[51];
> > + tmp[4] = product[52];
> > + tmp[5] = product[53];
> > + tmp[6] = product[54];
> > + tmp[7] = product[55];
> > + tmp[8] = product[56];
> > + tmp[9] = product[57];
> > + tmp[10] = product[58];
> > + tmp[11] = product[59];
> > + tmp[12] = product[60];
> > + tmp[13] = product[61];
> > + tmp[14] = product[62];
> > + tmp[15] = product[63];
> > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> > + tmp[24] = product[36];
> > + tmp[25] = product[37];
> > + tmp[26] = product[38];
> > + tmp[27] = product[39];
> > + tmp[28] = product[44];
> > + tmp[29] = product[45];
> > + tmp[30] = product[46];
> > + tmp[31] = product[47];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> > +
> > + /* d3 */
> > + tmp[0] = product[52];
> > + tmp[1] = product[53];
> > + tmp[2] = product[54];
> > + tmp[3] = product[55];
> > + tmp[4] = product[56];
> > + tmp[5] = product[57];
> > + tmp[6] = product[58];
> > + tmp[7] = product[59];
> > + tmp[8] = product[60];
> > + tmp[9] = product[61];
> > + tmp[10] = product[62];
> > + tmp[11] = product[63];
> > + tmp[12] = product[32];
> > + tmp[13] = product[33];
> > + tmp[14] = product[34];
> > + tmp[15] = product[35];
> > + tmp[16] = product[36];
> > + tmp[17] = product[37];
> > + tmp[18] = product[38];
> > + tmp[19] = product[39];
> > + tmp[20] = product[40];
> > + tmp[21] = product[41];
> > + tmp[22] = product[42];
> > + tmp[23] = product[43];
> > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> > + tmp[28] = product[48];
> > + tmp[29] = product[49];
> > + tmp[30] = product[50];
> > + tmp[31] = product[51];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> > +
> > + /* d4 */
> > + tmp[0] = product[56];
> > + tmp[1] = product[57];
> > + tmp[2] = product[58];
> > + tmp[3] = product[59];
> > + tmp[4] = product[60];
> > + tmp[5] = product[61];
> > + tmp[6] = product[62];
> > + tmp[7] = product[63];
> > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> > + tmp[12] = product[36];
> > + tmp[13] = product[37];
> > + tmp[14] = product[38];
> > + tmp[15] = product[39];
> > + tmp[16] = product[40];
> > + tmp[17] = product[41];
> > + tmp[18] = product[42];
> > + tmp[19] = product[43];
> > + tmp[20] = product[44];
> > + tmp[21] = product[45];
> > + tmp[22] = product[46];
> > + tmp[23] = product[47];
> > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> > + tmp[28] = product[52];
> > + tmp[29] = product[53];
> > + tmp[30] = product[54];
> > + tmp[31] = product[55];
> > + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > + } while (carry < 0);
> > + } else {
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> > + num_words_secp256r1) != 1) {
> > + carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > }
> > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> > - if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
> > - fclose(fp);
> > }
> > - (void) depth;
> > - (void) root;
> > - return (char *) b.buf;
> > }
> > -
> > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> > - const char *fullpath) {
> > - const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
> > - char *data = mg_ssi(fullpath, root, 0);
> > - mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> > - free(data);
> > +#elif MG_UECC_WORD_SIZE == 4
> > +static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t 
> *product) {
> > + uint32_t tmp[num_words_secp256r1];
> > + int carry;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> > +
> > + /* s1 */
> > + tmp[0] = tmp[1] = tmp[2] = 0;
> > + tmp[3] = product[11];
> > + tmp[4] = product[12];
> > + tmp[5] = product[13];
> > + tmp[6] = product[14];
> > + tmp[7] = product[15];
> > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s2 */
> > + tmp[3] = product[12];
> > + tmp[4] = product[13];
> > + tmp[5] = product[14];
> > + tmp[6] = product[15];
> > + tmp[7] = 0;
> > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s3 */
> > + tmp[0] = product[8];
> > + tmp[1] = product[9];
> > + tmp[2] = product[10];
> > + tmp[3] = tmp[4] = tmp[5] = 0;
> > + tmp[6] = product[14];
> > + tmp[7] = product[15];
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s4 */
> > + tmp[0] = product[9];
> > + tmp[1] = product[10];
> > + tmp[2] = product[11];
> > + tmp[3] = product[13];
> > + tmp[4] = product[14];
> > + tmp[5] = product[15];
> > + tmp[6] = product[13];
> > + tmp[7] = product[8];
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d1 */
> > + tmp[0] = product[11];
> > + tmp[1] = product[12];
> > + tmp[2] = product[13];
> > + tmp[3] = tmp[4] = tmp[5] = 0;
> > + tmp[6] = product[8];
> > + tmp[7] = product[10];
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d2 */
> > + tmp[0] = product[12];
> > + tmp[1] = product[13];
> > + tmp[2] = product[14];
> > + tmp[3] = product[15];
> > + tmp[4] = tmp[5] = 0;
> > + tmp[6] = product[9];
> > + tmp[7] = product[11];
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d3 */
> > + tmp[0] = product[13];
> > + tmp[1] = product[14];
> > + tmp[2] = product[15];
> > + tmp[3] = product[8];
> > + tmp[4] = product[9];
> > + tmp[5] = product[10];
> > + tmp[6] = 0;
> > + tmp[7] = product[12];
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d4 */
> > + tmp[0] = product[14];
> > + tmp[1] = product[15];
> > + tmp[2] = 0;
> > + tmp[3] = product[9];
> > + tmp[4] = product[10];
> > + tmp[5] = product[11];
> > + tmp[6] = 0;
> > + tmp[7] = product[13];
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > + } while (carry < 0);
> > + } else {
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> > + num_words_secp256r1) != 1) {
> > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > + }
> > + }
> > }
> > #else
> > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> > - const char *fullpath) {
> > - mg_http_reply(c, 501, NULL, "SSI not enabled");
> > - (void) root, (void) fullpath;
> > +static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t 
> *product) {
> > + uint64_t tmp[num_words_secp256r1];
> > + int carry;
> > +
> > + /* t */
> > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> > +
> > + /* s1 */
> > + tmp[0] = 0;
> > + tmp[1] = product[5] & 0xffffffff00000000U;
> > + tmp[2] = product[6];
> > + tmp[3] = product[7];
> > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s2 */
> > + tmp[1] = product[6] << 32;
> > + tmp[2] = (product[6] >> 32) | (product[7] << 32);
> > + tmp[3] = product[7] >> 32;
> > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s3 */
> > + tmp[0] = product[4];
> > + tmp[1] = product[5] & 0xffffffff;
> > + tmp[2] = 0;
> > + tmp[3] = product[7];
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* s4 */
> > + tmp[0] = (product[4] >> 32) | (product[5] << 32);
> > + tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
> > + tmp[2] = product[7];
> > + tmp[3] = (product[6] >> 32) | (product[4] << 32);
> > + carry += (int) mg_uecc_vli_add(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d1 */
> > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
> > + tmp[1] = (product[6] >> 32);
> > + tmp[2] = 0;
> > + tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d2 */
> > + tmp[0] = product[6];
> > + tmp[1] = product[7];
> > + tmp[2] = 0;
> > + tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d3 */
> > + tmp[0] = (product[6] >> 32) | (product[7] << 32);
> > + tmp[1] = (product[7] >> 32) | (product[4] << 32);
> > + tmp[2] = (product[4] >> 32) | (product[5] << 32);
> > + tmp[3] = (product[6] << 32);
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + /* d4 */
> > + tmp[0] = product[7];
> > + tmp[1] = product[4] & 0xffffffff00000000U;
> > + tmp[2] = product[5];
> > + tmp[3] = product[6] & 0xffffffff00000000U;
> > + carry -= (int) mg_uecc_vli_sub(result, result, tmp, 
> num_words_secp256r1);
> > +
> > + if (carry < 0) {
> > + do {
> > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > + } while (carry < 0);
> > + } else {
> > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> > + num_words_secp256r1) != 1) {
> > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> > + num_words_secp256r1);
> > + }
> > + }
> > }
> > -#endif
> > +#endif /* MG_UECC_WORD_SIZE */
> > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) 
> */
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/str.c"
> > +#endif /* MG_UECC_SUPPORTS_secp256r1 */
> > +
> > +#if MG_UECC_SUPPORTS_secp256k1
> > +
> > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, 
> mg_uecc_word_t *Y1,
> > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> > +static void x_side_secp256k1(mg_uecc_word_t *result, const 
> mg_uecc_word_t *x,
> > + MG_UECC_Curve curve);
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product);
> > #endif
> >
> > +static const struct MG_UECC_Curve_t curve_secp256k1 = {
> > + num_words_secp256k1,
> > + num_bytes_secp256k1,
> > + 256, /* num_n_bits */
> > + {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
> > + BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
> > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> > + {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
> > + BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
> > + BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
> > + BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
> > +
> > + BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
> > + BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
> > + BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
> > + BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
> > + {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
> > + &double_jacobian_secp256k1,
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > + &mod_sqrt_default,
> > +#endif
> > + &x_side_secp256k1,
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + &vli_mmod_fast_secp256k1
> > +#endif
> > +};
> >
> > -struct mg_str mg_str_s(const char *s) {
> > - struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
> > - return str;
> > +MG_UECC_Curve mg_uecc_secp256k1(void) {
> > + return &curve_secp256k1;
> > }
> >
> > -struct mg_str mg_str_n(const char *s, size_t n) {
> > - struct mg_str str = {s, n};
> > - return str;
> > -}
> > +/* Double in place */
> > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, 
> mg_uecc_word_t *Y1,
> > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> > + /* t1 = X, t2 = Y, t3 = Z */
> > + mg_uecc_word_t t4[num_words_secp256k1];
> > + mg_uecc_word_t t5[num_words_secp256k1];
> >
> > -int mg_lower(const char *s) {
> > - int c = *s;
> > - if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> > - return c;
> > -}
> > + if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
> > + return;
> > + }
> >
> > -int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
> > - int diff = 0;
> > - if (len > 0) do {
> > - diff = mg_lower(s1++) - mg_lower(s2++);
> > - } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> > - return diff;
> > + mg_uecc_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */
> > + mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */
> > + mg_uecc_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */
> > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */
> > + mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
> > +
> > + mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
> > + num_words_secp256k1); /* t2 = 2*x1^2 */
> > + mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
> > + num_words_secp256k1); /* t2 = 3*x1^2 */
> > + if (mg_uecc_vli_testBit(Y1, 0)) {
> > + mg_uecc_word_t carry =
> > + mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
> > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> > + Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
> > + } else {
> > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> > + }
> > + /* t2 = 3/2*(x1^2) = B */
> > +
> > + mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
> > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> > + num_words_secp256k1); /* t1 = B^2 - A */
> > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> > + num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
> > +
> > + mg_uecc_vli_modSub(t4, t4, X1, curve->p,
> > + num_words_secp256k1); /* t4 = A - x3 */
> > + mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */
> > + mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
> > + num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
> > +}
> > +
> > +/* Computes result = x^3 + b. result must not overlap x. */
> > +static void x_side_secp256k1(mg_uecc_word_t *result, const 
> mg_uecc_word_t *x,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
> > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
> > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> > + num_words_secp256k1); /* r = x^3 + b */
> > +}
> > +
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
> > +static void omega_mult_secp256k1(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *right);
> > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> > + mg_uecc_word_t *product) {
> > + mg_uecc_word_t tmp[2 * num_words_secp256k1];
> > + mg_uecc_word_t carry;
> > +
> > + mg_uecc_vli_clear(tmp, num_words_secp256k1);
> > + mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
> > +
> > + omega_mult_secp256k1(tmp,
> > + product + num_words_secp256k1); /* (Rq, q) = q * c */
> > +
> > + carry = mg_uecc_vli_add(result, product, tmp,
> > + num_words_secp256k1); /* (C, r) = r + q */
> > + mg_uecc_vli_clear(product, num_words_secp256k1);
> > + omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */
> > + carry += mg_uecc_vli_add(result, result, product,
> > + num_words_secp256k1); /* (C1, r) = r + Rq*c */
> > +
> > + while (carry > 0) {
> > + --carry;
> > + mg_uecc_vli_sub(result, result, curve_secp256k1.p, 
> num_words_secp256k1);
> > + }
> > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p, 
> num_words_secp256k1) >
> > + 0) {
> > + mg_uecc_vli_sub(result, result, curve_secp256k1.p, 
> num_words_secp256k1);
> > + }
> > }
> >
> > -int mg_casecmp(const char *s1, const char *s2) {
> > - return mg_ncasecmp(s1, s2, (size_t) ~0);
> > +#if MG_UECC_WORD_SIZE == 1
> > +static void omega_mult_secp256k1(uint8_t *result, const uint8_t *right) 
> {
> > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> > + mg_uecc_word_t r0 = 0;
> > + mg_uecc_word_t r1 = 0;
> > + mg_uecc_word_t r2 = 0;
> > + wordcount_t k;
> > +
> > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> > + muladd(0xD1, right[0], &r0, &r1, &r2);
> > + result[0] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + /* r2 is still 0 */
> > +
> > + for (k = 1; k < num_words_secp256k1; ++k) {
> > + muladd(0x03, right[k - 1], &r0, &r1, &r2);
> > + muladd(0xD1, right[k], &r0, &r1, &r2);
> > + result[k] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + r2 = 0;
> > + }
> > + muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
> > + result[num_words_secp256k1] = r0;
> > + result[num_words_secp256k1 + 1] = r1;
> > + /* add the 2^32 multiple */
> > + result[4 + num_words_secp256k1] =
> > + mg_uecc_vli_add(result + 4, result + 4, right, num_words_secp256k1);
> > +}
> > +#elif MG_UECC_WORD_SIZE == 4
> > +static void omega_mult_secp256k1(uint32_t *result, const uint32_t 
> *right) {
> > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> > + uint32_t carry = 0;
> > + wordcount_t k;
> > +
> > + for (k = 0; k < num_words_secp256k1; ++k) {
> > + uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
> > + result[k] = (uint32_t) p;
> > + carry = p >> 32;
> > + }
> > + result[num_words_secp256k1] = carry;
> > + /* add the 2^32 multiple */
> > + result[1 + num_words_secp256k1] =
> > + mg_uecc_vli_add(result + 1, result + 1, right, num_words_secp256k1);
> > }
> > -
> > -int mg_vcmp(const struct mg_str *s1, const char *s2) {
> > - size_t n2 = strlen(s2), n1 = s1->len;
> > - int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
> > - if (r == 0) return (int) (n1 - n2);
> > - return r;
> > +#else
> > +static void omega_mult_secp256k1(uint64_t *result, const uint64_t 
> *right) {
> > + mg_uecc_word_t r0 = 0;
> > + mg_uecc_word_t r1 = 0;
> > + mg_uecc_word_t r2 = 0;
> > + wordcount_t k;
> > +
> > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> > + for (k = 0; k < num_words_secp256k1; ++k) {
> > + muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
> > + result[k] = r0;
> > + r0 = r1;
> > + r1 = r2;
> > + r2 = 0;
> > + }
> > + result[num_words_secp256k1] = r0;
> > }
> > +#endif /* MG_UECC_WORD_SIZE */
> > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && && 
> !asm_mmod_fast_secp256k1) */
> >
> > -int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
> > - size_t n2 = strlen(str2), n1 = str1->len;
> > - int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
> > - if (r == 0) return (int) (n1 - n2);
> > - return r;
> > +#endif /* MG_UECC_SUPPORTS_secp256k1 */
> > +
> > +#endif /* _UECC_CURVE_SPECIFIC_H_ */
> > +
> > +/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
> > +#define EccPoint_isZero(point, curve) \
> > + mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words * 2))
> > +
> > +/* Point multiplication algorithm using Montgomery's ladder with co-Z
> > +coordinates. From http://eprint.iacr.org/2011/338.pdf
> > +*/
> > +
> > +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
> > +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> > + const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
> > + mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
> > +
> > + mg_uecc_vli_modSquare_fast(t1, Z, curve); /* z^2 */
> > + mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
> > + mg_uecc_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */
> > + mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
> > }
> >
> > -struct mg_str mg_strdup(const struct mg_str s) {
> > - struct mg_str r = {NULL, 0};
> > - if (s.len > 0 && s.ptr != NULL) {
> > - char *sc = (char *) calloc(1, s.len + 1);
> > - if (sc != NULL) {
> > - memcpy(sc, s.ptr, s.len);
> > - sc[s.len] = '\0';
> > - r.ptr = sc;
> > - r.len = s.len;
> > - }
> > +/* P = (x1, y1) => 2P, (x2, y2) => P' */
> > +static void XYcZ_initial_double(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> > + const mg_uecc_word_t *const initial_Z,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> > + wordcount_t num_words = curve->num_words;
> > + if (initial_Z) {
> > + mg_uecc_vli_set(z, initial_Z, num_words);
> > + } else {
> > + mg_uecc_vli_clear(z, num_words);
> > + z[0] = 1;
> > }
> > - return r;
> > -}
> >
> > -int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> > - size_t i = 0;
> > - while (i < str1.len && i < str2.len) {
> > - int c1 = str1.ptr[i];
> > - int c2 = str2.ptr[i];
> > - if (c1 < c2) return -1;
> > - if (c1 > c2) return 1;
> > - i++;
> > + mg_uecc_vli_set(X2, X1, num_words);
> > + mg_uecc_vli_set(Y2, Y1, num_words);
> > +
> > + apply_z(X1, Y1, z, curve);
> > + curve->double_jacobian(X1, Y1, z, curve);
> > + apply_z(X2, Y2, z, curve);
> > +}
> > +
> > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> > + Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
> > + or P => P', Q => P + Q
> > +*/
> > +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, 
> mg_uecc_word_t *X2,
> > + mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
> > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> > + wordcount_t num_words = curve->num_words;
> > +
> > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
> > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */
> > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
> > + mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */
> > +
> > + mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */
> > + mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C 
> = x3 */
> > + mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */
> > + mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */
> > + mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */
> > + mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - 
> x3) */
> > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
> > +
> > + mg_uecc_vli_set(X2, t5, num_words);
> > +}
> > +
> > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> > + Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
> > + or P => P - Q, Q => P + Q
> > +*/
> > +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> > + MG_UECC_Curve curve) {
> > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> > + mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
> > + wordcount_t num_words = curve->num_words;
> > +
> > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
> > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */
> > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> > + mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */
> > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
> > +
> > + mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */
> > + mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E 
> */
> > + mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */
> > + mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */
> > + mg_uecc_vli_modSub(X2, X2, t6, curve->p,
> > + num_words); /* t3 = D - (B + C) = x3 */
> > +
> > + mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */
> > + mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - 
> x3) */
> > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
> > + num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
> > +
> > + mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */
> > + mg_uecc_vli_modSub(t7, t7, t6, curve->p,
> > + num_words); /* t7 = F - (B + C) = x3' */
> > + mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */
> > + mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) 
> */
> > + mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
> > + num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
> > +
> > + mg_uecc_vli_set(X1, t7, num_words);
> > +}
> > +
> > +/* result may overlap point. */
> > +static void EccPoint_mult(mg_uecc_word_t *result, const mg_uecc_word_t 
> *point,
> > + const mg_uecc_word_t *scalar,
> > + const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
> > + MG_UECC_Curve curve) {
> > + /* R0 and R1 */
> > + mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> > + bitcount_t i;
> > + mg_uecc_word_t nb;
> > + wordcount_t num_words = curve->num_words;
> > +
> > + mg_uecc_vli_set(Rx[1], point, num_words);
> > + mg_uecc_vli_set(Ry[1], point + num_words, num_words);
> > +
> > + XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
> > +
> > + for (i = num_bits - 2; i > 0; --i) {
> > + nb = !mg_uecc_vli_testBit(scalar, i);
> > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> > + }
> > +
> > + nb = !mg_uecc_vli_testBit(scalar, 0);
> > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> > +
> > + /* Find final 1/Z value. */
> > + mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */
> > + mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1 - X0) */
> > + mg_uecc_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1 - X0) */
> > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - 
> X0)) */
> > + /* yP / (xP * Yb * (X1 - X0)) */
> > + mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
> > + mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
> > + curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
> > + /* End 1/Z calculation */
> > +
> > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> > + apply_z(Rx[0], Ry[0], z, curve);
> > +
> > + mg_uecc_vli_set(result, Rx[0], num_words);
> > + mg_uecc_vli_set(result + num_words, Ry[0], num_words);
> > +}
> > +
> > +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
> > + mg_uecc_word_t *k0, mg_uecc_word_t *k1,
> > + MG_UECC_Curve curve) {
> > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> > + bitcount_t num_n_bits = curve->num_n_bits;
> > + mg_uecc_word_t carry =
> > + mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
> > + (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8) &&
> > + mg_uecc_vli_testBit(k0, num_n_bits));
> > + mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
> > + return carry;
> > +}
> > +
> > +/* Generates a random integer in the range 0 < random < top.
> > + Both random and top have num_words words. */
> > +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random,
> > + const mg_uecc_word_t *top,
> > + wordcount_t num_words) {
> > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> > + mg_uecc_word_t tries;
> > + bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
> > +
> > + if (!g_rng_function) {
> > + return 0;
> > + }
> > +
> > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> > + if (!g_rng_function((uint8_t *) random,
> > + (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
> > + return 0;
> > + }
> > + random[num_words - 1] &=
> > + mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits));
> > + if (!mg_uecc_vli_isZero(random, num_words) &&
> > + mg_uecc_vli_cmp(top, random, num_words) == 1) {
> > + return 1;
> > + }
> > }
> > - if (i < str1.len) return 1;
> > - if (i < str2.len) return -1;
> > return 0;
> > }
> >
> > -const char *mg_strstr(const struct mg_str haystack,
> > - const struct mg_str needle) {
> > - size_t i;
> > - if (needle.len > haystack.len) return NULL;
> > - for (i = 0; i <= haystack.len - needle.len; i++) {
> > - if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
> > - return haystack.ptr + i;
> > +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t 
> *result,
> > + mg_uecc_word_t *private_key,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> > + mg_uecc_word_t *initial_Z = 0;
> > + mg_uecc_word_t carry;
> > +
> > + /* Regularize the bitcount for the private key so that attackers 
> cannot use a
> > + side channel attack to learn the number of leading zeros. */
> > + carry = regularize_k(private_key, tmp1, tmp2, curve);
> > +
> > + /* If an RNG function was specified, try to get a random initial Z 
> value to
> > + improve protection against side-channel attacks. */
> > + if (g_rng_function) {
> > + if (!mg_uecc_generate_random_int(p2[carry], curve->p, 
> curve->num_words)) {
> > + return 0;
> > }
> > + initial_Z = p2[carry];
> > }
> > - return NULL;
> > + EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
> > + (bitcount_t) (curve->num_n_bits + 1), curve);
> > +
> > + if (EccPoint_isZero(result, curve)) {
> > + return 0;
> > + }
> > + return 1;
> > }
> >
> > -static bool is_space(int c) {
> > - return c == ' ' || c == '\r' || c == '\n' || c == '\t';
> > +#if MG_UECC_WORD_SIZE == 1
> > +
> > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int 
> num_bytes,
> > + const uint8_t *native) {
> > + wordcount_t i;
> > + for (i = 0; i < num_bytes; ++i) {
> > + bytes[i] = native[(num_bytes - 1) - i];
> > + }
> > }
> >
> > -struct mg_str mg_strstrip(struct mg_str s) {
> > - while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
> > - while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
> > - return s;
> > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
> > + const uint8_t *bytes,
> > + int num_bytes) {
> > + mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
> > }
> >
> > -bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
> > - size_t i = 0, j = 0, ni = 0, nj = 0;
> > - if (caps) caps->ptr = NULL, caps->len = 0;
> > - while (i < p.len || j < s.len) {
> > - if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == 
> p.ptr[i])) {
> > - if (caps == NULL) {
> > - } else if (p.ptr[i] == '?') {
> > - caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap
> > - caps++, caps->ptr = NULL, caps->len = 0; // Init next cap
> > - } else if (caps->ptr != NULL && caps->len == 0) {
> > - caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize current cap
> > - caps++, caps->len = 0, caps->ptr = NULL; // Init next cap
> > - }
> > - i++, j++;
> > - } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
> > - if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j]; // Init 
> cap
> > - ni = i++, nj = j + 1;
> > - } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != 
> '/')) {
> > - i = ni, j = nj;
> > - if (caps && caps->ptr == NULL && caps->len == 0) {
> > - caps--, caps->len = 0; // Restart previous cap
> > - }
> > - } else {
> > - return false;
> > - }
> > - }
> > - if (caps && caps->ptr && caps->len == 0) {
> > - caps->len = (size_t) (&s.ptr[j] - caps->ptr);
> > +#else
> > +
> > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int 
> num_bytes,
> > + const mg_uecc_word_t *native) {
> > + int i;
> > + for (i = 0; i < num_bytes; ++i) {
> > + unsigned b = (unsigned) (num_bytes - 1 - i);
> > + bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
> > + (8 * (b % MG_UECC_WORD_SIZE)));
> > }
> > - return true;
> > }
> >
> > -bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) 
> {
> > - return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
> > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native,
> > + const uint8_t *bytes,
> > + int num_bytes) {
> > + int i;
> > + mg_uecc_vli_clear(native,
> > + (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
> > + MG_UECC_WORD_SIZE));
> > + for (i = 0; i < num_bytes; ++i) {
> > + unsigned b = (unsigned) (num_bytes - 1 - i);
> > + native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
> > + << (8 * (b % MG_UECC_WORD_SIZE));
> > + }
> > }
> >
> > -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff,
> > - size_t *klen, size_t *voff, size_t *vlen, char delim) {
> > - size_t kvlen, kl;
> > - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;) kvlen++;
> > - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
> > - if (koff != NULL) *koff = ofs;
> > - if (klen != NULL) *klen = kl;
> > - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
> > - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
> > - ofs += kvlen + 1;
> > - return ofs > n ? n : ofs;
> > +#endif /* MG_UECC_WORD_SIZE */
> > +
> > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> > + MG_UECC_Curve curve) {
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> > +#else
> > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> > +#endif
> > + mg_uecc_word_t tries;
> > +
> > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> > + if (!mg_uecc_generate_random_int(_private, curve->n,
> > + BITS_TO_WORDS(curve->num_n_bits))) {
> > + return 0;
> > + }
> > +
> > + if (EccPoint_compute_public_key(_public, _private, curve)) {
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_nativeToBytes(private_key, 
> BITS_TO_BYTES(curve->num_n_bits),
> > + _private);
> > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, 
> curve->num_bytes,
> > + _public + curve->num_words);
> > +#endif
> > + return 1;
> > + }
> > + }
> > + return 0;
> > }
> >
> > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, 
> char sep) {
> > - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
> > - if (s->ptr == NULL || s->len == 0) return 0;
> > - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
> > - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
> > - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
> > - *s = mg_str_n(s->ptr + off, s->len - off);
> > - return off > 0;
> > +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t 
> *private_key,
> > + uint8_t *secret, MG_UECC_Curve curve) {
> > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> > +
> > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t *p2[2] = {_private, tmp};
> > + mg_uecc_word_t *initial_Z = 0;
> > + mg_uecc_word_t carry;
> > + wordcount_t num_words = curve->num_words;
> > + wordcount_t num_bytes = curve->num_bytes;
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) _private, private_key, num_bytes);
> > + bcopy((uint8_t *) _public, public_key, num_bytes * 2);
> > +#else
> > + mg_uecc_vli_bytesToNative(_private, private_key,
> > + BITS_TO_BYTES(curve->num_n_bits));
> > + mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
> > + mg_uecc_vli_bytesToNative(_public + num_words, public_key + num_bytes,
> > + num_bytes);
> > +#endif
> > +
> > + /* Regularize the bitcount for the private key so that attackers 
> cannot use a
> > + side channel attack to learn the number of leading zeros. */
> > + carry = regularize_k(_private, _private, tmp, curve);
> > +
> > + /* If an RNG function was specified, try to get a random initial Z 
> value to
> > + improve protection against side-channel attacks. */
> > + if (g_rng_function) {
> > + if (!mg_uecc_generate_random_int(p2[carry], curve->p, num_words)) {
> > + return 0;
> > + }
> > + initial_Z = p2[carry];
> > + }
> > +
> > + EccPoint_mult(_public, _public, p2[!carry], initial_Z,
> > + (bitcount_t) (curve->num_n_bits + 1), curve);
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
> > +#else
> > + mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
> > +#endif
> > + return !EccPoint_isZero(_public, curve);
> > }
> >
> > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) 
> {
> > - return mg_split(s, k, v, ',');
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
> > + MG_UECC_Curve curve) {
> > + wordcount_t i;
> > + for (i = 0; i < curve->num_bytes; ++i) {
> > + compressed[i + 1] = public_key[i];
> > + }
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
> > +#else
> > + compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
> > +#endif
> > }
> >
> > -char *mg_hex(const void *buf, size_t len, char *to) {
> > - const unsigned char *p = (const unsigned char *) buf;
> > - const char *hex = "0123456789abcdef";
> > - size_t i = 0;
> > - for (; len--; p++) {
> > - to[i++] = hex[p[0] >> 4];
> > - to[i++] = hex[p[0] & 0x0f];
> > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
> > + MG_UECC_Curve curve) {
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
> > +#else
> > + mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
> > +#endif
> > + mg_uecc_word_t *y = point + curve->num_words;
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy(public_key, compressed + 1, curve->num_bytes);
> > +#else
> > + mg_uecc_vli_bytesToNative(point, compressed + 1, curve->num_bytes);
> > +#endif
> > + curve->x_side(y, point, curve);
> > + curve->mod_sqrt(y, curve);
> > +
> > + if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
> > + mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
> > }
> > - to[i] = '\0';
> > - return to;
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
> > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, 
> curve->num_bytes, y);
> > +#endif
> > }
> > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> > +
> > +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t *point,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> > + wordcount_t num_words = curve->num_words;
> > +
> > + /* The point at infinity is invalid. */
> > + if (EccPoint_isZero(point, curve)) {
> > + return 0;
> > + }
> >
> > -static unsigned char mg_unhex_nimble(unsigned char c) {
> > - return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
> > - : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
> > - : (unsigned char) (c - 'W');
> > + /* x and y must be smaller than p. */
> > + if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
> > + mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) {
> > + return 0;
> > + }
> > +
> > + mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
> > + curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
> > +
> > + /* Make sure that y^2 == x^3 + ax + b */
> > + return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
> > }
> >
> > -unsigned long mg_unhexn(const char *s, size_t len) {
> > - unsigned long i = 0, v = 0;
> > - for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) 
> s)[i]);
> > - return v;
> > +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve 
> curve) {
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> > +#else
> > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> > +#endif
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> > + mg_uecc_vli_bytesToNative(_public + curve->num_words,
> > + public_key + curve->num_bytes, curve->num_bytes);
> > +#endif
> > + return mg_uecc_valid_point(_public, curve);
> > }
> >
> > -void mg_unhex(const char *buf, size_t len, unsigned char *to) {
> > - size_t i;
> > - for (i = 0; i < len; i += 2) {
> > - to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
> > +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t 
> *public_key,
> > + MG_UECC_Curve curve) {
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> > +#else
> > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> > +#endif
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_bytesToNative(_private, private_key,
> > + BITS_TO_BYTES(curve->num_n_bits));
> > +#endif
> > +
> > + /* Make sure the private key is in the range [1, n-1]. */
> > + if (mg_uecc_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) {
> > + return 0;
> > }
> > -}
> >
> > -uint64_t mg_tou64(struct mg_str str) {
> > - uint64_t result = 0;
> > - size_t i = 0;
> > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
> > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> > - result *= 10;
> > - result += (unsigned) (str.ptr[i] - '0');
> > - i++;
> > + if (mg_uecc_vli_cmp(curve->n, _private, 
> BITS_TO_WORDS(curve->num_n_bits)) !=
> > + 1) {
> > + return 0;
> > }
> > - return result;
> > +
> > + /* Compute public key. */
> > + if (!EccPoint_compute_public_key(_public, _private, curve)) {
> > + return 0;
> > + }
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, 
> curve->num_bytes,
> > + _public + curve->num_words);
> > +#endif
> > + return 1;
> > }
> >
> > -int64_t mg_to64(struct mg_str str) {
> > - int64_t result = 0, neg = 1, max = 922337203685477570 /* 
> INT64_MAX/10-10 */;
> > - size_t i = 0;
> > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
> > - if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
> > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> > - if (result > max) return 0;
> > - result *= 10;
> > - result += (str.ptr[i] - '0');
> > - i++;
> > +/* -------- ECDSA code -------- */
> > +
> > +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
> > + unsigned bits_size, MG_UECC_Curve curve) {
> > + unsigned num_n_bytes = (unsigned) BITS_TO_BYTES(curve->num_n_bits);
> > + unsigned num_n_words = (unsigned) BITS_TO_WORDS(curve->num_n_bits);
> > + int shift;
> > + mg_uecc_word_t carry;
> > + mg_uecc_word_t *ptr;
> > +
> > + if (bits_size > num_n_bytes) {
> > + bits_size = num_n_bytes;
> > + }
> > +
> > + mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) native, bits, bits_size);
> > +#else
> > + mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
> > +#endif
> > + if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
> > + return;
> > + }
> > + shift = (int) bits_size * 8 - curve->num_n_bits;
> > + carry = 0;
> > + ptr = native + num_n_words;
> > + while (ptr-- > native) {
> > + mg_uecc_word_t temp = *ptr;
> > + *ptr = (temp >> shift) | carry;
> > + carry = temp << (MG_UECC_WORD_BITS - shift);
> > + }
> > +
> > + /* Reduce mod curve_n */
> > + if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t) 
> num_n_words) !=
> > + 1) {
> > + mg_uecc_vli_sub(native, native, curve->n, (wordcount_t) num_n_words);
> > }
> > - return result * neg;
> > }
> >
> > -char *mg_remove_double_dots(char *s) {
> > - char *saved = s, *p = s;
> > - while (*s != '\0') {
> > - *p++ = *s++;
> > - if (s[-1] == '/' || s[-1] == '\\') {
> > - while (s[0] != '\0') {
> > - if (s[0] == '/' || s[0] == '\\') {
> > - s++;
> > - } else if (s[0] == '.' && s[1] == '.' &&
> > - (s[2] == '/' || s[2] == '\\')) {
> > - s += 2;
> > - } else {
> > - break;
> > - }
> > - }
> > +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
> > + const uint8_t *message_hash,
> > + unsigned hash_size, mg_uecc_word_t *k,
> > + uint8_t *signature,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t s[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t *k2[2] = {tmp, s};
> > + mg_uecc_word_t *initial_Z = 0;
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
> > +#else
> > + mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
> > +#endif
> > + mg_uecc_word_t carry;
> > + wordcount_t num_words = curve->num_words;
> > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> > + bitcount_t num_n_bits = curve->num_n_bits;
> > +
> > + /* Make sure 0 < k < curve_n */
> > + if (mg_uecc_vli_isZero(k, num_words) ||
> > + mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
> > + return 0;
> > + }
> > +
> > + carry = regularize_k(k, tmp, s, curve);
> > + /* If an RNG function was specified, try to get a random initial Z 
> value to
> > + improve protection against side-channel attacks. */
> > + if (g_rng_function) {
> > + if (!mg_uecc_generate_random_int(k2[carry], curve->p, num_words)) {
> > + return 0;
> > }
> > + initial_Z = k2[carry];
> > + }
> > + EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
> > + (bitcount_t) (num_n_bits + 1), curve);
> > + if (mg_uecc_vli_isZero(p, num_words)) {
> > + return 0;
> > }
> > - *p = '\0';
> > - return saved;
> > -}
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/timer.c"
> > + /* If an RNG function was specified, get a random number
> > + to prevent side channel analysis of k. */
> > + if (!g_rng_function) {
> > + mg_uecc_vli_clear(tmp, num_n_words);
> > + tmp[0] = 1;
> > + } else if (!mg_uecc_generate_random_int(tmp, curve->n, num_n_words)) {
> > + return 0;
> > + }
> > +
> > + /* Prevent side channel analysis of mg_uecc_vli_modInv() to determine
> > + bits of k / the private key by premultiplying by a random number */
> > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * 
> k */
> > + mg_uecc_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */
> > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */
> > +
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> > + mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r 
> */
> > #endif
> >
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits));
> > +#else
> > + mg_uecc_vli_bytesToNative(tmp, private_key,
> > + BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
> > +#endif
> >
> > + s[num_n_words - 1] = 0;
> > + mg_uecc_vli_set(s, p, num_words);
> > + mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */
> >
> > -#define MG_TIMER_CALLED 4
> > + bits2int(tmp, message_hash, hash_size, curve);
> > + mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */
> > + mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) 
> / k */
> > + if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t) 
> curve->num_bytes * 8) {
> > + return 0;
> > + }
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
> > + curve->num_bytes);
> > +#else
> > + mg_uecc_vli_nativeToBytes(signature + curve->num_bytes, 
> curve->num_bytes, s);
> > +#endif
> > + return 1;
> > +}
> >
> > -void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t 
> ms,
> > - unsigned flags, void (*fn)(void *), void *arg) {
> > - t->id = 0, t->period_ms = ms, t->expire = 0;
> > - t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> > - *head = t;
> > +#if 0
> > +/* For testing - sign with an explicitly specified k value */
> > +int mg_uecc_sign_with_k(const uint8_t *private_key, const uint8_t 
> *message_hash,
> > + unsigned hash_size, const uint8_t *k, uint8_t *signature,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
> > + bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits), curve);
> > + return mg_uecc_sign_with_k_internal(private_key, message_hash, 
> hash_size, k2,
> > + signature, curve);
> > }
> > +#endif
> >
> > -void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> > - while (*head && *head != t) head = &(*head)->next;
> > - if (*head) *head = t->next;
> > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t 
> *message_hash,
> > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
> > + mg_uecc_word_t k[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tries;
> > +
> > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> > + if (!mg_uecc_generate_random_int(k, curve->n,
> > + BITS_TO_WORDS(curve->num_n_bits))) {
> > + return 0;
> > + }
> > +
> > + if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, 
> k,
> > + signature, curve)) {
> > + return 1;
> > + }
> > + }
> > + return 0;
> > }
> >
> > -// t: expiration time, prd: period, now: current time. Return true if 
> expired
> > -bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> > - if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
> > - if (*t == 0) *t = now + prd; // Firt poll? Set expiration
> > - if (*t > now) return false; // Not expired yet, return
> > - *t = (now - *t) > prd ? now + prd : *t + prd; // Next expiration time
> > - return true; // Expired, return true
> > +/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is 
> always
> > + the same size as the hash result size. */
> > +static void HMAC_init(const MG_UECC_HashContext *hash_context,
> > + const uint8_t *K) {
> > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> > + unsigned i;
> > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x36;
> > + for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
> > +
> > + hash_context->init_hash(hash_context);
> > + hash_context->update_hash(hash_context, pad, hash_context->block_size);
> > }
> >
> > -void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> > - struct mg_timer *t, *tmp;
> > - for (t = *head; t != NULL; t = tmp) {
> > - bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> > - !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
> > - bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> > - tmp = t->next;
> > - if (!once && !expired) continue;
> > - if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
> > - t->fn(t->arg);
> > +static void HMAC_update(const MG_UECC_HashContext *hash_context,
> > + const uint8_t *message, unsigned message_size) {
> > + hash_context->update_hash(hash_context, message, message_size);
> > +}
> > +
> > +static void HMAC_finish(const MG_UECC_HashContext *hash_context,
> > + const uint8_t *K, uint8_t *result) {
> > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> > + unsigned i;
> > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x5c;
> > + for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
> > +
> > + hash_context->finish_hash(hash_context, result);
> > +
> > + hash_context->init_hash(hash_context);
> > + hash_context->update_hash(hash_context, pad, hash_context->block_size);
> > + hash_context->update_hash(hash_context, result, 
> hash_context->result_size);
> > + hash_context->finish_hash(hash_context, result);
> > +}
> > +
> > +/* V = HMAC_K(V) */
> > +static void update_V(const MG_UECC_HashContext *hash_context, uint8_t 
> *K,
> > + uint8_t *V) {
> > + HMAC_init(hash_context, K);
> > + HMAC_update(hash_context, V, hash_context->result_size);
> > + HMAC_finish(hash_context, K, V);
> > +}
> > +
> > +/* Deterministic signing, similar to RFC 6979. Differences are:
> > + * We just use H(m) directly rather than bits2octets(H(m))
> > + (it is not reduced modulo curve_n).
> > + * We generate a value for k (aka T) directly rather than converting
> > + endianness.
> > +
> > + Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped 0x00 or 
> 0x01) /
> > + <HMAC pad> */
> > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> > + const uint8_t *message_hash, unsigned hash_size,
> > + const MG_UECC_HashContext *hash_context,
> > + uint8_t *signature, MG_UECC_Curve curve) {
> > + uint8_t *K = hash_context->tmp;
> > + uint8_t *V = K + hash_context->result_size;
> > + wordcount_t num_bytes = curve->num_bytes;
> > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> > + bitcount_t num_n_bits = curve->num_n_bits;
> > + mg_uecc_word_t tries;
> > + unsigned i;
> > + for (i = 0; i < hash_context->result_size; ++i) {
> > + V[i] = 0x01;
> > + K[i] = 0;
> > + }
> > +
> > + /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
> > + HMAC_init(hash_context, K);
> > + V[hash_context->result_size] = 0x00;
> > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> > + HMAC_update(hash_context, message_hash, hash_size);
> > + HMAC_finish(hash_context, K, K);
> > +
> > + update_V(hash_context, K, V);
> > +
> > + /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
> > + HMAC_init(hash_context, K);
> > + V[hash_context->result_size] = 0x01;
> > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> > + HMAC_update(hash_context, message_hash, hash_size);
> > + HMAC_finish(hash_context, K, K);
> > +
> > + update_V(hash_context, K, V);
> > +
> > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> > + mg_uecc_word_t T[MG_UECC_MAX_WORDS];
> > + uint8_t *T_ptr = (uint8_t *) T;
> > + wordcount_t T_bytes = 0;
> > + for (;;) {
> > + update_V(hash_context, K, V);
> > + for (i = 0; i < hash_context->result_size; ++i) {
> > + T_ptr[T_bytes++] = V[i];
> > + if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
> > + goto filled;
> > + }
> > + }
> > + }
> > + filled:
> > + if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 > num_n_bits) {
> > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> > + T[num_n_words - 1] &=
> > + mask >>
> > + ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
> > + }
> > +
> > + if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, 
> T,
> > + signature, curve)) {
> > + return 1;
> > }
> > - t->flags |= MG_TIMER_CALLED;
> > +
> > + /* K = HMAC_K(V || 0x00) */
> > + HMAC_init(hash_context, K);
> > + V[hash_context->result_size] = 0x00;
> > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> > + HMAC_finish(hash_context, K, K);
> > +
> > + update_V(hash_context, K, V);
> > }
> > + return 0;
> > }
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/tls_dummy.c"
> > +static bitcount_t smax(bitcount_t a, bitcount_t b) {
> > + return (a > b ? a : b);
> > +}
> > +
> > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t 
> *message_hash,
> > + unsigned hash_size, const uint8_t *signature,
> > + MG_UECC_Curve curve) {
> > + mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
> > + mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
> > + const mg_uecc_word_t *points[4];
> > + const mg_uecc_word_t *point;
> > + bitcount_t num_bits;
> > + bitcount_t i;
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> > +#else
> > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> > #endif
> > + mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
> > + wordcount_t num_words = curve->num_words;
> > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> >
> > + rx[num_n_words - 1] = 0;
> > + r[num_n_words - 1] = 0;
> > + s[num_n_words - 1] = 0;
> >
> > -#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL && !MG_ENABLE_CUSTOM_TLS
> > -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > - (void) opts;
> > - mg_error(c, "TLS is not enabled");
> > -}
> > -void mg_tls_handshake(struct mg_connection *c) {
> > - (void) c;
> > -}
> > -void mg_tls_free(struct mg_connection *c) {
> > - (void) c;
> > -}
> > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> > -}
> > -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> > -}
> > -size_t mg_tls_pending(struct mg_connection *c) {
> > - (void) c;
> > - return 0;
> > -}
> > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > + bcopy((uint8_t *) r, signature, curve->num_bytes);
> > + bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes);
> > +#else
> > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> > + mg_uecc_vli_bytesToNative(_public + num_words, public_key + 
> curve->num_bytes,
> > + curve->num_bytes);
> > + mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
> > + mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes, 
> curve->num_bytes);
> > #endif
> >
> > -#ifdef MG_ENABLE_LINES
> > -#line 1 "src/tls_mbed.c"
> > -#endif
> > + /* r, s must not be 0. */
> > + if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s, 
> num_words)) {
> > + return 0;
> > + }
> >
> > + /* r, s must be < n. */
> > + if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
> > + mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
> > + return 0;
> > + }
> >
> > + /* Calculate u1 and u2. */
> > + mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
> > + u1[num_n_words - 1] = 0;
> > + bits2int(u1, message_hash, hash_size, curve);
> > + mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */
> > + mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */
> > +
> > + /* Calculate sum = G + Q. */
> > + mg_uecc_vli_set(sum, _public, num_words);
> > + mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
> > + mg_uecc_vli_set(tx, curve->G, num_words);
> > + mg_uecc_vli_set(ty, curve->G + num_words, num_words);
> > + mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */
> > + XYcZ_add(tx, ty, sum, sum + num_words, curve);
> > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
> > + apply_z(sum, sum + num_words, z, curve);
> > +
> > + /* Use Shamir's trick to calculate u1*G + u2*Q */
> > + points[0] = 0;
> > + points[1] = curve->G;
> > + points[2] = _public;
> > + points[3] = sum;
> > + num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
> > + mg_uecc_vli_numBits(u2, num_n_words));
> > + point =
> > + points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
> > + ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
> > + mg_uecc_vli_set(rx, point, num_words);
> > + mg_uecc_vli_set(ry, point + num_words, num_words);
> > + mg_uecc_vli_clear(z, num_words);
> > + z[0] = 1;
> > +
> > + for (i = num_bits - 2; i >= 0; --i) {
> > + mg_uecc_word_t index;
> > + curve->double_jacobian(rx, ry, z, curve);
> > +
> > + index = (!!mg_uecc_vli_testBit(u1, i)) |
> > + (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
> > + point = points[index];
> > + if (point) {
> > + mg_uecc_vli_set(tx, point, num_words);
> > + mg_uecc_vli_set(ty, point + num_words, num_words);
> > + apply_z(tx, ty, z, curve);
> > + mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */
> > + XYcZ_add(tx, ty, rx, ry, curve);
> > + mg_uecc_vli_modMult_fast(z, z, tz, curve);
> > + }
> > + }
> >
> > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
> > + apply_z(rx, ry, z, curve);
> >
> > -#if MG_ENABLE_MBEDTLS
> > + /* v = x1 (mod n) */
> > + if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
> > + mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
> > + }
> >
> > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 
> 0x03000000
> > -#define MGRNG , rng_get, NULL
> > -#else
> > -#define MGRNG
> > -#endif
> > + /* Accept only if v == r. */
> > + return (int) (mg_uecc_vli_equal(rx, r, num_words));
> > +}
> >
> > -void mg_tls_free(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - if (tls != NULL) {
> > - free(tls->cafile);
> > - mbedtls_ssl_free(&tls->ssl);
> > - mbedtls_pk_free(&tls->pk);
> > - mbedtls_x509_crt_free(&tls->ca);
> > - mbedtls_x509_crt_free(&tls->cert);
> > - mbedtls_ssl_config_free(&tls->conf);
> > - free(tls);
> > - c->tls = NULL;
> > - }
> > +#if MG_UECC_ENABLE_VLI_API
> > +
> > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
> > + return curve->num_words;
> > }
> >
> > -static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) 
> {
> > - long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> > - return (int) n;
> > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
> > + return curve->num_bytes;
> > }
> >
> > -static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> > - long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> > - return (int) n;
> > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
> > + return curve->num_bytes * 8;
> > }
> >
> > -void mg_tls_handshake(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - int rc = mbedtls_ssl_handshake(&tls->ssl);
> > - if (rc == 0) { // Success
> > - MG_DEBUG(("%lu success", c->id));
> > - c->is_tls_hs = 0;
> > - mg_call(c, MG_EV_TLS_HS, NULL);
> > - } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> > - rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
> > - MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
> > - c->is_tls_hs, rc, -rc));
> > - } else {
> > - mg_error(c, "TLS handshake: -%#x", -rc); // Error
> > - }
> > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
> > + return BITS_TO_WORDS(curve->num_n_bits);
> > }
> >
> > -static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> > - mg_random(buf, len);
> > - (void) ctx;
> > - return 0;
> > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
> > + return BITS_TO_BYTES(curve->num_n_bits);
> > }
> >
> > -static void debug_cb(void *c, int lev, const char *s, int n, const char 
> *s2) {
> > - n = (int) strlen(s2) - 1;
> > - MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, 
> s2));
> > - (void) s;
> > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
> > + return curve->num_n_bits;
> > }
> >
> > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 
> 0x03000000
> > -static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
> > - (void) p_rng;
> > - mg_random(buf, len);
> > - return 0;
> > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
> > + return curve->p;
> > }
> > -#endif
> >
> > -static struct mg_str mg_loadfile(struct mg_fs *fs, const char *path) {
> > - size_t n = 0;
> > - if (path[0] == '-') return mg_str(path);
> > - char *p = mg_file_read(fs, path, &n);
> > - return mg_str_n(p, n);
> > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
> > + return curve->n;
> > }
> >
> > -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> > - int rc = 0;
> > - c->tls = tls;
> > - if (c->tls == NULL) {
> > - mg_error(c, "TLS OOM");
> > - goto fail;
> > - }
> > - MG_DEBUG(("%lu Setting TLS", c->id));
> > - mbedtls_ssl_init(&tls->ssl);
> > - mbedtls_ssl_config_init(&tls->conf);
> > - mbedtls_x509_crt_init(&tls->ca);
> > - mbedtls_x509_crt_init(&tls->cert);
> > - mbedtls_pk_init(&tls->pk);
> > - mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> > -#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> > - mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
> > + return curve->G;
> > +}
> > +
> > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
> > + return curve->b;
> > +}
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> > + curve->mod_sqrt(a, curve);
> > +}
> > #endif
> > - if ((rc = mbedtls_ssl_config_defaults(
> > - &tls->conf,
> > - c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> > - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> > - mg_error(c, "tls defaults %#x", -rc);
> > - goto fail;
> > - }
> > - mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
> > - if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
> > - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> > - } else if (opts->ca != NULL && opts->ca[0] != '\0') {
> > -#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
> > - tls->cafile = strdup(opts->ca);
> > - rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL);
> > - if (rc != 0) {
> > - mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
> > - goto fail;
> > - }
> > +
> > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t 
> *product,
> > + MG_UECC_Curve curve) {
> > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> > + curve->mmod_fast(result, product);
> > #else
> > - struct mg_str s = mg_loadfile(fs, opts->ca);
> > - rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len + 1);
> > - if (opts->ca[0] != '-') free((char *) s.ptr);
> > - if (rc != 0) {
> > - mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
> > - goto fail;
> > - }
> > - mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> > #endif
> > - if (opts->srvname.len > 0) {
> > - char *x = mg_mprintf("%.*s", (int) opts->srvname.len, 
> opts->srvname.ptr);
> > - mbedtls_ssl_set_hostname(&tls->ssl, x);
> > - free(x);
> > - }
> > - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
> > - }
> > - if (opts->cert != NULL && opts->cert[0] != '\0') {
> > - struct mg_str s = mg_loadfile(fs, opts->cert);
> > - const char *key = opts->certkey == NULL ? opts->cert : opts->certkey;
> > - rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr, s.len + 1);
> > - if (opts->cert[0] != '-') free((char *) s.ptr);
> > - if (rc != 0) {
> > - mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
> > - goto fail;
> > - }
> > - s = mg_loadfile(fs, key);
> > - rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len + 1, NULL,
> > - 0 MGRNG);
> > - if (key[0] != '-') free((char *) s.ptr);
> > - if (rc != 0) {
> > - mg_error(c, "tls key(%s) %#x", key, -rc);
> > - goto fail;
> > - }
> > - rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
> > - if (rc != 0) {
> > - mg_error(c, "own cert %#x", -rc);
> > - goto fail;
> > - }
> > - }
> > - if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> > - mg_error(c, "setup err %#x", -rc);
> > - goto fail;
> > - }
> > - c->tls = tls;
> > - c->is_tls = 1;
> > - c->is_tls_hs = 1;
> > - mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> > - if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> > - mg_tls_handshake(c);
> > - }
> > - return;
> > -fail:
> > - mg_tls_free(c);
> > }
> >
> > -size_t mg_tls_pending(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> > -}
> > +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t 
> *point,
> > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
> > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> > + mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
> >
> > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> > - return MG_IO_WAIT;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > + EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, 
> curve);
> > }
> >
> > -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
> > - return MG_IO_WAIT;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > -}
> > -#endif
> > +#endif /* MG_UECC_ENABLE_VLI_API */
> > +#endif // MG_TLS_BUILTIN
> > +// End of uecc BSD-2
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "src/tls_openssl.c"
> > +#line 1 "src/tls_x25519.c"
> > #endif
> > +/**
> > + * Adapted from STROBE: https://strobe.sourceforge.io/
> > + * Copyright (c) 2015-2016 Cryptography Research, Inc.
> > + * Author: Mike Hamburg
> > + * License: MIT License
> > + */
> >
> >
> >
> > -#if MG_ENABLE_OPENSSL
> > -static int mg_tls_err(struct mg_tls *tls, int res) {
> > - int err = SSL_get_error(tls->ssl, res);
> > - // We've just fetched the last error from the queue.
> > - // Now we need to clear the error queue. If we do not, then the 
> following
> > - // can happen (actually reported):
> > - // - A new connection is accept()-ed with cert error (e.g. self-signed 
> cert)
> > - // - Since all accept()-ed connections share listener's context,
> > - // - *ALL* SSL accepted connection report read error on the next poll 
> cycle.
> > - // Thus a single errored connection can close all the rest, unrelated 
> ones.
> > - // Clearing the error keeps the shared SSL_CTX in an OK state.
> > +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
> >
> > - if (err != 0) ERR_print_errors_fp(stderr);
> > - ERR_clear_error();
> > - if (err == SSL_ERROR_WANT_READ) return 0;
> > - if (err == SSL_ERROR_WANT_WRITE) return 0;
> > - return err;
> > -}
> > +#define X25519_WBITS 32
> >
> > -void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts 
> *opts) {
> > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> > - const char *id = "mongoose";
> > - static unsigned char s_initialised = 0;
> > - int rc;
> > +typedef uint32_t limb_t;
> > +typedef uint64_t dlimb_t;
> > +typedef int64_t sdlimb_t;
> >
> > - if (tls == NULL) {
> > - mg_error(c, "TLS OOM");
> > - goto fail;
> > - }
> > +#define NLIMBS (256 / X25519_WBITS)
> > +typedef limb_t mg_fe[NLIMBS];
> >
> > - if (!s_initialised) {
> > - SSL_library_init();
> > - s_initialised++;
> > - }
> > - MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
> > - opts->ca == NULL ? "null" : opts->ca,
> > - opts->cert == NULL ? "null" : opts->cert,
> > - opts->certkey == NULL ? "null" : opts->certkey));
> > - tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> > - : SSL_CTX_new(SSLv23_server_method());
> > - if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> > - mg_error(c, "SSL_new");
> > - goto fail;
> > - }
> > - SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> > - (unsigned) strlen(id));
> > - // Disable deprecated protocols
> > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> > -#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> > - SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> > -#endif
> > -#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> > - SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> > -#endif
> > +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t 
> mier) {
> > + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
> > + *carry = (limb_t) (tmp >> X25519_WBITS);
> > + return (limb_t) tmp;
> > +}
> >
> > - if (opts->ca != NULL && opts->ca[0] != '\0') {
> > - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | 
> SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> > - NULL);
> > - if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 
> 1) {
> > - mg_error(c, "load('%s') %d err %d", opts->ca, rc, mg_tls_err(tls, rc));
> > - goto fail;
> > - }
> > - }
> > - if (opts->cert != NULL && opts->cert[0] != '\0') {
> > - const char *key = opts->certkey;
> > - if (key == NULL) key = opts->cert;
> > - if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) != 1) {
> > - mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
> > - goto fail;
> > - } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) != 1) {
> > - mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
> > - goto fail;
> > -#if OPENSSL_VERSION_NUMBER > 0x10100000L
> > - } else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) 
> !=
> > - 1) {
> > - mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
> > - goto fail;
> > -#endif
> > - } else {
> > - SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> > -#if OPENSSL_VERSION_NUMBER > 0x10002000L
> > - SSL_set_ecdh_auto(tls->ssl, 1);
> > -#endif
> > - }
> > - }
> > - if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, 
> opts->ciphers);
> > -#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> > - if (opts->srvname.len > 0) {
> > - char *s = mg_mprintf("%.*s", (int) opts->srvname.len, 
> opts->srvname.ptr);
> > - SSL_set1_host(tls->ssl, s);
> > - free(s);
> > - }
> > -#endif
> > - c->tls = tls;
> > - c->is_tls = 1;
> > - c->is_tls_hs = 1;
> > - if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
> > - mg_tls_handshake(c);
> > - }
> > - MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : 
> "client"));
> > - return;
> > -fail:
> > - c->is_closing = 1;
> > - free(tls);
> > +// These functions are implemented in terms of umaal on ARM
> > +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
> > + dlimb_t total = (dlimb_t) *carry + acc + mand;
> > + *carry = (limb_t) (total >> X25519_WBITS);
> > + return (limb_t) total;
> > +}
> > +
> > +static limb_t adc0(limb_t *carry, limb_t acc) {
> > + dlimb_t total = (dlimb_t) *carry + acc;
> > + *carry = (limb_t) (total >> X25519_WBITS);
> > + return (limb_t) total;
> > +}
> > +
> > +// - Precondition: carry is small.
> > +// - Invariant: result of propagate is < 2^255 + 1 word
> > +// - In particular, always less than 2p.
> > +// - Also, output x >= min(x,19)
> > +static void propagate(mg_fe x, limb_t over) {
> > + unsigned i;
> > + limb_t carry;
> > + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
> > + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
> > +
> > + carry = over * 19;
> > + for (i = 0; i < NLIMBS; i++) {
> > + x[i] = adc0(&carry, x[i]);
> > + }
> > }
> >
> > -void mg_tls_handshake(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - int rc;
> > - SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
> > - rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
> > - if (rc == 1) {
> > - MG_DEBUG(("%lu success", c->id));
> > - c->is_tls_hs = 0;
> > - mg_call(c, MG_EV_TLS_HS, NULL);
> > - } else {
> > - int code = mg_tls_err(tls, rc);
> > - if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
> > +static void add(mg_fe out, const mg_fe a, const mg_fe b) {
> > + unsigned i;
> > + limb_t carry = 0;
> > + for (i = 0; i < NLIMBS; i++) {
> > + out[i] = adc(&carry, a[i], b[i]);
> > }
> > + propagate(out, carry);
> > }
> >
> > -void mg_tls_free(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - if (tls == NULL) return;
> > - SSL_free(tls->ssl);
> > - SSL_CTX_free(tls->ctx);
> > - free(tls);
> > - c->tls = NULL;
> > +static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
> > + unsigned i;
> > + sdlimb_t carry = -38;
> > + for (i = 0; i < NLIMBS; i++) {
> > + carry = carry + a[i] - b[i];
> > + out[i] = (limb_t) carry;
> > + carry >>= X25519_WBITS;
> > + }
> > + propagate(out, (limb_t) (1 + carry));
> > +}
> > +
> > +// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of 
> `mg_fe`
> > +// to avoid build warnings
> > +static void mul(mg_fe out, const mg_fe a, const limb_t *b, unsigned nb) 
> {
> > + limb_t accum[2 * NLIMBS] = {0};
> > + unsigned i, j;
> > +
> > + limb_t carry2;
> > + for (i = 0; i < nb; i++) {
> > + limb_t mand = b[i];
> > + carry2 = 0;
> > + for (j = 0; j < NLIMBS; j++) {
> > + limb_t tmp; // "a" may be misaligned
> > + memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy
> > + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
> > + }
> > + accum[i + j] = carry2;
> > + }
> > +
> > + carry2 = 0;
> > + for (j = 0; j < NLIMBS; j++) {
> > + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
> > + }
> > + propagate(out, carry2);
> > }
> >
> > -size_t mg_tls_pending(struct mg_connection *c) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> > +static void sqr(mg_fe out, const mg_fe a) {
> > + mul(out, a, a, NLIMBS);
> > +}
> > +static void mul1(mg_fe out, const mg_fe a) {
> > + mul(out, a, out, NLIMBS);
> > +}
> > +static void sqr1(mg_fe a) {
> > + mul1(a, a);
> > }
> >
> > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - int n = SSL_read(tls->ssl, buf, (int) len);
> > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
> > + limb_t doswap) {
> > + unsigned i;
> > + for (i = 0; i < 2 * NLIMBS; i++) {
> > + limb_t xor_ab = (a[i] ^ b[i]) & doswap;
> > + a[i] ^= xor_ab;
> > + b[i] ^= xor_ab;
> > + }
> > }
> >
> > -long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
> > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> > - int n = SSL_write(tls->ssl, buf, (int) len);
> > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> > - if (n <= 0) return MG_IO_ERR;
> > - return n;
> > +// Canonicalize a field element x, reducing it to the least residue 
> which is
> > +// congruent to it mod 2^255-19
> > +// - Precondition: x < 2^255 + 1 word
> > +static limb_t canon(mg_fe x) {
> > + // First, add 19.
> > + unsigned i;
> > + limb_t carry0 = 19;
> > + limb_t res;
> > + sdlimb_t carry;
> > + for (i = 0; i < NLIMBS; i++) {
> > + x[i] = adc0(&carry0, x[i]);
> > + }
> > + propagate(x, carry0);
> > +
> > + // Here, 19 <= x2 < 2^255
> > + // - This is because we added 19, so before propagate it can't be less
> > + // than 19. After propagate, it still can't be less than 19, because if
> > + // propagate does anything it adds 19.
> > + // - We know that the high bit must be clear, because either the input 
> was ~
> > + // 2^255 + one word + 19 (in which case it propagates to at most 2 
> words) or
> > + // it was < 2^255. So now, if we subtract 19, we will get back to 
> something in
> > + // [0,2^255-19).
> > + carry = -19;
> > + res = 0;
> > + for (i = 0; i < NLIMBS; i++) {
> > + carry += x[i];
> > + res |= x[i] = (limb_t) carry;
> > + carry >>= X25519_WBITS;
> > + }
> > + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
> > +}
> > +
> > +static const limb_t a24[1] = {121665};
> > +
> > +static void ladder_part1(mg_fe xs[5]) {
> > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
> > + add(t1, x2, z2); // t1 = A
> > + sub(z2, x2, z2); // z2 = B
> > + add(x2, x3, z3); // x2 = C
> > + sub(z3, x3, z3); // z3 = D
> > + mul1(z3, t1); // z3 = DA
> > + mul1(x2, z2); // x3 = BC
> > + add(x3, z3, x2); // x3 = DA+CB
> > + sub(z3, z3, x2); // z3 = DA-CB
> > + sqr1(t1); // t1 = AA
> > + sqr1(z2); // z2 = BB
> > + sub(x2, t1, z2); // x2 = E = AA-BB
> > + mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24
> > + add(z2, z2, t1); // z2 = E*a24 + AA
> > +}
> > +
> > +static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
> > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
> > + sqr1(z3); // z3 = (DA-CB)^2
> > + mul1(z3, x1); // z3 = x1 * (DA-CB)^2
> > + sqr1(x3); // x3 = (DA+CB)^2
> > + mul1(z2, x2); // z2 = AA*(E*a24+AA)
> > + sub(x2, t1, x2); // x2 = BB again
> > + mul1(x2, t1); // x2 = AA*BB
> > +}
> > +
> > +static void x25519_core(mg_fe xs[5], const uint8_t scalar[X25519_BYTES],
> > + const uint8_t *x1, int clamp) {
> > + int i;
> > + mg_fe x1_limbs;
> > + limb_t swap = 0;
> > + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
> > + memset(xs, 0, 4 * sizeof(mg_fe));
> > + x2[0] = z3[0] = 1;
> > + for (i = 0; i < NLIMBS; i++) {
> > + x3[i] = x1_limbs[i] =
> > + MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
> > + }
> > +
> > + for (i = 255; i >= 0; i--) {
> > + uint8_t bytei = scalar[i / 8];
> > + limb_t doswap;
> > + if (clamp) {
> > + if (i / 8 == 0) {
> > + bytei &= (uint8_t) ~7U;
> > + } else if (i / 8 == X25519_BYTES - 1) {
> > + bytei &= 0x7F;
> > + bytei |= 0x40;
> > + }
> > + }
> > + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
> > + condswap(x2, x3, swap ^ doswap);
> > + swap = doswap;
> > +
> > + ladder_part1(xs);
> > + ladder_part2(xs, (const limb_t *) x1_limbs);
> > + }
> > + condswap(x2, x3, swap);
> > +}
> > +
> > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t 
> scalar[X25519_BYTES],
> > + const uint8_t x1[X25519_BYTES], int clamp) {
> > + int i, ret;
> > + mg_fe xs[5], out_limbs;
> > + limb_t *x2, *z2, *z3, *prev;
> > + static const struct {
> > + uint8_t a, c, n;
> > + } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3, 1, 1},
> > + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
> > + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}};
> > + x25519_core(xs, scalar, x1, clamp);
> > +
> > + // Precomputed inversion chain
> > + x2 = xs[0];
> > + z2 = xs[1];
> > + z3 = xs[3];
> > +
> > + prev = z2;
> > + for (i = 0; i < 13; i++) {
> > + int j;
> > + limb_t *a = xs[steps[i].a];
> > + for (j = steps[i].n; j > 0; j--) {
> > + sqr(a, prev);
> > + prev = a;
> > + }
> > + mul1(a, xs[steps[i].c]);
> > + }
> > +
> > + // Here prev = z3
> > + // x2 /= z2
> > + mul(out_limbs, x2, z3, NLIMBS);
> > + ret = (int) canon(out_limbs);
> > + if (!clamp) ret = 0;
> > + for (i = 0; i < NLIMBS; i++) {
> > + uint32_t n = out_limbs[i];
> > + out[i * 4] = (uint8_t) (n & 0xff);
> > + out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
> > + out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
> > + out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
> > + }
> > + return ret;
> > }
> > -#endif
> >
> > #ifdef MG_ENABLE_LINES
> > #line 1 "src/url.c"
> > @@ -5382,7 +14797,7 @@ struct url {
> > int mg_url_is_ssl(const char *url) {
> > return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 ||
> > strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
> > - strncmp(url, "tls:", 4) == 0;
> > + strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
> > }
> >
> > static struct url urlparse(const char *url) {
> > @@ -5464,6 +14879,14 @@ struct mg_str mg_url_pass(const char *url) {
> > #endif
> >
> >
> > +// Not using memset for zeroing memory, cause it can be dropped by 
> compiler
> > +// See https://github.com/cesanta/mongoose/pull/1265
> > +void mg_bzero(volatile unsigned char *buf, size_t len) {
> > + if (buf != NULL) {
> > + while (len--) *buf++ = 0;
> > + }
> > +}
> > +
> > #if MG_ENABLE_CUSTOM_RANDOM
> > #else
> > void mg_random(void *buf, size_t len) {
> > @@ -5512,11 +14935,16 @@ uint16_t mg_ntohs(uint16_t net) {
> > }
> >
> > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
> > - int i;
> > + static const uint32_t crclut[16] = {
> > + // table for polynomial 0xEDB88320 (reflected)
> > + 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4,
> > + 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
> > + 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
> > crc = ~crc;
> > while (len--) {
> > - crc ^= *(unsigned char *) buf++;
> > - for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc 
> >> 1;
> > + uint8_t b = *(uint8_t *) buf++;
> > + crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
> > + crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
> > }
> > return ~crc;
> > }
> > @@ -5539,18 +14967,36 @@ static int parse_net(const char *spec, uint32_t 
> *net, uint32_t *mask) {
> > return len;
> > }
> >
> > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
> > - struct mg_str k, v;
> > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
> > + struct mg_str entry;
> > int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set, deny by 
> default
> > - while (mg_commalist(&acl, &k, &v)) {
> > - uint32_t net, mask;
> > - if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
> > - if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
> > - if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
> > + uint32_t remote_ip4;
> > + if (remote_ip->is_ip6) {
> > + return -1; // TODO(): handle IPv6 ACL and addresses
> > + } else { // IPv4
> > + memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
> > + while (mg_span(acl, &entry, &acl, ',')) {
> > + uint32_t net, mask;
> > + if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
> > + if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
> > + if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
> > + }
> > }
> > return allowed == '+';
> > }
> >
> > +bool mg_path_is_sane(const struct mg_str path) {
> > + const char *s = path.buf;
> > + size_t n = path.len;
> > + if (path.buf[0] == '.' && path.buf[1] == '.') return false; // Starts 
> with ..
> > + for (; s[0] != '\0' && n > 0; s++, n--) {
> > + if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir?
> > + if (s[1] == '.' && s[2] == '.') return false; // Starts with ..
> > + }
> > + }
> > + return true;
> > +}
> > +
> > #if MG_ENABLE_CUSTOM_MILLIS
> > #else
> > uint64_t mg_millis(void) {
> > @@ -5558,9 +15004,8 @@ uint64_t mg_millis(void) {
> > return GetTickCount();
> > #elif MG_ARCH == MG_ARCH_RP2040
> > return time_us_64() / 1000;
> > -#elif MG_ARCH == MG_ARCH_ESP32
> > - return esp_timer_get_time() / 1000;
> > -#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
> > +#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
> > + MG_ARCH == MG_ARCH_FREERTOS
> > return xTaskGetTickCount() * portTICK_PERIOD_MS;
> > #elif MG_ARCH == MG_ARCH_AZURERTOS
> > return tx_time_get() * (1000 /* MS per SEC */ / 
> TX_TIMER_TICKS_PER_SECOND);
> > @@ -5568,6 +15013,12 @@ uint64_t mg_millis(void) {
> > return (uint64_t) Clock_getTicks();
> > #elif MG_ARCH == MG_ARCH_ZEPHYR
> > return (uint64_t) k_uptime_get();
> > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
> > + return (uint64_t) rt_time_get();
> > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> > + return (uint64_t) ((osKernelGetTickCount() * 1000) / 
> osKernelGetTickFreq());
> > +#elif MG_ARCH == MG_ARCH_RTTHREAD
> > + return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
> > #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
> > // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on linux
> > // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on linux
> > @@ -5595,7 +15046,6 @@ uint64_t mg_millis(void) {
> > }
> > #endif
> >
> > -
> > #ifdef MG_ENABLE_LINES
> > #line 1 "src/ws.c"
> > #endif
> > @@ -5609,6 +15059,7 @@ uint64_t mg_millis(void) {
> >
> >
> >
> > +
> > struct ws_msg {
> > uint8_t flags;
> > size_t header_len;
> > @@ -5640,10 +15091,10 @@ static void ws_handshake(struct mg_connection 
> *c, const struct mg_str *wskey,
> >
> > mg_sha1_ctx sha_ctx;
> > mg_sha1_init(&sha_ctx);
> > - mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len);
> > + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf, wskey->len);
> > mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
> > mg_sha1_final(sha, &sha_ctx);
> > - mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
> > + mg_base64_encode(sha, sizeof(sha), (char *) b64_sha, sizeof(b64_sha));
> > mg_xprintf(mg_pfn_iobuf, &c->send,
> > "HTTP/1.1 101 Switching Protocols\r\n"
> > "Upgrade: websocket\r\n"
> > @@ -5653,7 +15104,7 @@ static void ws_handshake(struct mg_connection *c, 
> const struct mg_str *wskey,
> > if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> > if (wsproto != NULL) {
> > mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
> > - wsproto->ptr);
> > + wsproto->buf);
> > }
> > mg_send(c, "\r\n", 2);
> > }
> > @@ -5746,12 +15197,15 @@ static bool mg_ws_client_handshake(struct 
> mg_connection *c) {
> > mg_error(c, "not http"); // Some just, not an HTTP request
> > } else if (n > 0) {
> > if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
> > - mg_error(c, "handshake error");
> > + mg_error(c, "ws handshake error");
> > } else {
> > struct mg_http_message hm;
> > - mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> > - c->is_websocket = 1;
> > - mg_call(c, MG_EV_WS_OPEN, &hm);
> > + if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
> > + c->is_websocket = 1;
> > + mg_call(c, MG_EV_WS_OPEN, &hm);
> > + } else {
> > + mg_error(c, "ws handshake error");
> > + }
> > }
> > mg_iobuf_del(&c->recv, 0, (size_t) n);
> > } else {
> > @@ -5760,8 +15214,7 @@ static bool mg_ws_client_handshake(struct 
> mg_connection *c) {
> > return false; // Continue event handler
> > }
> >
> > -static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
> > - void *fn_data) {
> > +static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) {
> > struct ws_msg msg;
> > size_t ofs = (size_t) c->pfn_data;
> >
> > @@ -5775,7 +15228,7 @@ static void mg_ws_cb(struct mg_connection *c, int 
> ev, void *ev_data,
> > size_t len = msg.header_len + msg.data_len;
> > uint8_t final = msg.flags & 128, op = msg.flags & 15;
> > // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
> > - // (int) m.data.len, (int) m.data.len, m.data.ptr));
> > + // (int) m.data.len, (int) m.data.len, m.data.buf));
> > switch (op) {
> > case WEBSOCKET_OP_CONTINUE:
> > mg_call(c, MG_EV_WS_CTL, &m);
> > @@ -5796,7 +15249,7 @@ static void mg_ws_cb(struct mg_connection *c, int 
> ev, void *ev_data,
> > MG_DEBUG(("%lu WS CLOSE", c->id));
> > mg_call(c, MG_EV_WS_CTL, &m);
> > // Echo the payload of the received CLOSE message back to the sender
> > - mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
> > + mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
> > c->is_draining = 1;
> > break;
> > default:
> > @@ -5827,7 +15280,6 @@ static void mg_ws_cb(struct mg_connection *c, int 
> ev, void *ev_data,
> > }
> > }
> > }
> > - (void) fn_data;
> > (void) ev_data;
> > }
> >
> > @@ -5839,7 +15291,7 @@ struct mg_connection *mg_ws_connect(struct mg_mgr 
> *mgr, const char *url,
> > char nonce[16], key[30];
> > struct mg_str host = mg_url_host(url);
> > mg_random(nonce, sizeof(nonce));
> > - mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
> > + mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key, 
> sizeof(key));
> > mg_xprintf(mg_pfn_iobuf, &c->send,
> > "GET %s HTTP/1.1\r\n"
> > "Upgrade: websocket\r\n"
> > @@ -5847,7 +15299,7 @@ struct mg_connection *mg_ws_connect(struct mg_mgr 
> *mgr, const char *url,
> > "Connection: Upgrade\r\n"
> > "Sec-WebSocket-Version: 13\r\n"
> > "Sec-WebSocket-Key: %s\r\n",
> > - mg_url_uri(url), (int) host.len, host.ptr, key);
> > + mg_url_uri(url), (int) host.len, host.buf, key);
> > if (fmt != NULL) {
> > va_list ap;
> > va_start(ap, fmt);
> > @@ -5896,1591 +15348,2247 @@ size_t mg_ws_wrap(struct mg_connection *c, 
> size_t len, int op) {
> > }
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "mip/driver_stm32.c"
> > +#line 1 "src/drivers/cmsis.c"
> > #endif
> > +// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
> >
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && 
> MG_ENABLE_DRIVER_CMSIS
> >
> > -#if MG_ENABLE_MIP && \
> > - (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
> > -struct stm32_eth {
> > - volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, 
> MACFCR,
> > - MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, 
> MACSR,
> > - MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
> > - MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> > - RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> > - RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> > - RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
> > - PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, 
> RESERVED9[564],
> > - DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
> > - DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> > - DMACHRBAR;
> > -};
> > -#undef ETH
> > -#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
> >
> > -#undef BIT
> > -#define BIT(x) ((uint32_t) 1 << (x))
> > -#define ETH_PKT_SIZE 1540 // Max frame size
> > -#define ETH_DESC_CNT 4 // Descriptors count
> > -#define ETH_DS 4 // Descriptor size (words)
> >
> > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet 
> buffers
> > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet 
> buffers
> > -static struct mip_if *s_ifp; // MIP interface
> > -enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 }; // PHY constants
> >
> > -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > - ETH->MACMIIAR &= (7 << 2);
> > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> > - ETH->MACMIIAR |= BIT(0);
> > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
> > - return ETH->MACMIIDR;
> > -}
> >
> > -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> > - ETH->MACMIIDR = val;
> > - ETH->MACMIIAR &= (7 << 2);
> > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | 
> BIT(1);
> > - ETH->MACMIIAR |= BIT(0);
> > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
> > -}
> > +extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
> > +extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
> >
> > -static uint32_t get_hclk(void) {
> > - struct rcc {
> > - volatile uint32_t CR, PLLCFGR, CFGR;
> > - } *rcc = (struct rcc *) 0x40023800;
> > - uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz 
> */;
> > +static struct mg_tcpip_if *s_ifp;
> >
> > - if (rcc->CFGR & (1 << 2)) {
> > - clk = hse;
> > - } else if (rcc->CFGR & (1 << 3)) {
> > - uint32_t vco, m, n, p;
> > - m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> > - n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> > - p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> > - clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> > - vco = (uint32_t) ((uint64_t) clk * n / m);
> > - clk = vco / p;
> > - } else {
> > - clk = hsi;
> > +static void mac_cb(uint32_t);
> > +static bool cmsis_init(struct mg_tcpip_if *);
> > +static bool cmsis_up(struct mg_tcpip_if *);
> > +static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
> > +static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
> > +
> > +struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init, cmsis_tx, 
> NULL,
> > + cmsis_up};
> > +
> > +static bool cmsis_init(struct mg_tcpip_if *ifp) {
> > + ARM_ETH_MAC_ADDR addr;
> > + s_ifp = ifp;
> > +
> > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> > + ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
> > + if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
> > + if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) != ARM_DRIVER_OK)
> > + return false;
> > + if (cap.event_rx_frame == 0) // polled mode driver
> > + mg_tcpip_driver_cmsis.rx = cmsis_rx;
> > + mac->PowerControl(ARM_POWER_FULL);
> > + if (cap.mac_address) { // driver provides MAC address
> > + mac->GetMacAddress(&addr);
> > + memcpy(ifp->mac, &addr, sizeof(ifp->mac));
> > + } else { // we provide MAC address
> > + memcpy(&addr, ifp->mac, sizeof(addr));
> > + mac->SetMacAddress(&addr);
> > }
> > - uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> > - if (hpre < 8) return clk;
> > + phy->PowerControl(ARM_POWER_FULL);
> > + phy->SetInterface(cap.media_interface);
> > + phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
> > + return true;
> > +}
> >
> > - uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> > - return ((uint32_t) clk) >> ahbptab[hpre - 8];
> > +static size_t cmsis_tx(const void *buf, size_t len, struct mg_tcpip_if 
> *ifp) {
> > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> > + if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
> > + ifp->nerr++;
> > + return 0;
> > + }
> > + ifp->nsent++;
> > + return len;
> > }
> >
> > -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 
> 802.3,
> > -// it must not exceed 2.5MHz As the AHB clock can be (and usually is) 
> derived
> > -// from the HSI (internal RC), and it can go above specs, the datasheets
> > -// specify a range of frequencies and activate one of a series of 
> dividers to
> > -// keep the MDC clock safely below 2.5MHz. We guess a divider setting 
> based on
> > -// HCLK with a +5% drift. If the user uses a different clock from our
> > -// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
> > -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
> > -static int guess_mdc_cr(void) {
> > - uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
> > - uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers
> > - uint32_t hclk = get_hclk(); // Guess system HCLK
> > - int result = -1; // Invalid CR value
> > - if (hclk < 25000000) {
> > - MG_ERROR(("HCLK too low"));
> > - } else {
> > - for (int i = 0; i < 6; i++) {
> > - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > - result = crs[i];
> > - break;
> > +static bool cmsis_up(struct mg_tcpip_if *ifp) {
> > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> > + bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0; // link 
> state
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // just went up
> > + ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
> > + mac->Control(ARM_ETH_MAC_CONFIGURE,
> > + (st.speed << ARM_ETH_MAC_SPEED_Pos) |
> > + (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
> > + ARM_ETH_MAC_ADDRESS_BROADCAST);
> > + MG_DEBUG(("Link is %uM %s-duplex",
> > + (st.speed == 2) ? 1000
> > + : st.speed ? 100
> > + : 10,
> > + st.duplex ? "full" : "half"));
> > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
> > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
> > + } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) { // just went 
> down
> > + mac->Control(ARM_ETH_MAC_FLUSH,
> > + ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
> > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
> > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
> > + }
> > + return up;
> > +}
> > +
> > +static void mac_cb(uint32_t ev) {
> > + if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
> > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
> > + if (len >= 60 && len <= 1518) { // proper frame
> > + char *p;
> > + if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) { // have room
> > + if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) { // copy succeeds
> > + mg_queue_add(&s_ifp->recv_queue, len);
> > + s_ifp->nrecv++;
> > }
> > + return;
> > }
> > - if (result < 0) MG_ERROR(("HCLK too high"));
> > + s_ifp->ndrop++;
> > }
> > - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> > - return result;
> > + mac->ReadFrame(NULL, 0); // otherwise, discard
> > +}
> > +
> > +static size_t cmsis_rx(void *buf, size_t buflen, struct mg_tcpip_if 
> *ifp) {
> > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
> > + if (len >= 60 && len <= 1518 &&
> > + ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
> > + return len;
> > + if (len > 0) mac->ReadFrame(NULL, 0); // discard bad frames
> > + (void) ifp;
> > + return 0;
> > }
> >
> > -static bool mip_driver_stm32_init(struct mip_if *ifp) {
> > - struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data *) 
> ifp->driver_data;
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/imxrt.c"
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && 
> MG_ENABLE_DRIVER_IMXRT
> > +struct imxrt_enet {
> > + volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR, 
> RESERVED2[3],
> > + ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC, RESERVED5[7], RCR,
> > + RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0, TXIC1, TXIC2,
> > + RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR, GALR,
> > + RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL, RSEM,
> > + RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC, RACC,
> > + RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
> > + RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
> > + RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127, RMON_T_P128TO255,
> > + RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048, 
> RMON_T_GTE2048,
> > + RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL, IEEE_T_MCOL,
> > + IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR, IEEE_T_CSERR,
> > + IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3], 
> RMON_R_PACKETS,
> > + RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
> > + RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
> > + RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511, RMON_R_P512TO1023,
> > + RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
> > + IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR, IEEE_R_FDXFC,
> > + IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER, ATCOR, 
> ATINC,
> > + ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1, TCSR2, 
> TCCR2,
> > + TCSR3;
> > +};
> > +
> > +#undef ENET
> > +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
> > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
> > +#define ETH_DESC_CNT 5 // Descriptors count
> > +#else
> > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#endif
> > +
> > +#define ETH_PKT_SIZE 1536 // Max frame size, 64-bit aligned
> > +
> > +struct enet_desc {
> > + uint16_t length; // Data length
> > + uint16_t control; // Control and status
> > + uint32_t *buffer; // Data ptr
> > +};
> > +
> > +// TODO(): handle these in a portable compiler-independent 
> CMSIS-friendly way
> > +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
> > +
> > +// Descriptors: in non-cached area (TODO(scaprile)), (37.5.1.22.2 
> 37.5.1.23.2)
> > +// Buffers: 64-byte aligned (37.3.14)
> > +static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT] 
> MG_64BYTE_ALIGNED;
> > +static volatile struct enet_desc s_txdesc[ETH_DESC_CNT] 
> MG_64BYTE_ALIGNED;
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +
> > +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
> > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
> > + ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18) | (2 
> << 16);
> > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> > + return ENET->MMFR & 0xffff;
> > +}
> > +
> > +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
> > + ENET->MMFR =
> > + (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16) | val;
> > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> > +}
> > +
> > +// MDC clock is generated from IPS Bus clock (ipg_clk); as per 802.3,
> > +// it must not exceed 2.5MHz
> > +// The PHY receives the PLL6-generated 50MHz clock
> > +static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_imxrt_data *d =
> > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
> > s_ifp = ifp;
> >
> > // Init RX descriptors
> > for (int i = 0; i < ETH_DESC_CNT; i++) {
> > - s_rxdesc[i][0] = BIT(31); // Own
> > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address chained
> > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data 
> buffer
> > - s_rxdesc[i][3] =
> > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + s_rxdesc[i].control = MG_BIT(15); // Own (E)
> > + s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i]; // Point to data buffer
> > }
> > + s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last 
> descriptor
> >
> > // Init TX descriptors
> > for (int i = 0; i < ETH_DESC_CNT; i++) {
> > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> > - s_txdesc[i][3] =
> > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + // s_txdesc[i].control = MG_BIT(10); // Own (TC)
> > + s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
> > }
> > -
> > - ETH->DMABMR |= BIT(0); // Software reset
> > - while ((ETH->DMABMR & BIT(0)) != 0) (void) 0; // Wait until done
> > -
> > - // Set MDC clock divider. If user told us the value, use it. 
> Otherwise, guess
> > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> > - ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
> > -
> > - // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> > - // hardware checksum. Therefore, descriptor size is 4, not 8
> > - // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
> > - ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT
> > - ETH->MACFCR = BIT(7); // Disable zero quarta pause
> > - // ETH->MACFFR = BIT(31); // Receive all
> > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY
> > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12)); // Set autonegotiation
> > - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> > - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
> > - ETH->DMAIER = BIT(6) | BIT(16); // RIE, NISE
> > - ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE, Duplex, 
> Fast
> > - ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
> > -
> > - // MAC address filtering
> > - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> > - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> > - ((uint32_t) ifp->mac[2] << 16) |
> > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
> > + s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last 
> descriptor
> > +
> > + ENET->ECR = MG_BIT(0); // Software reset, disable
> > + while ((ENET->ECR & MG_BIT(0))) (void) 0; // Wait until done
> > +
> > + // Set MDC clock divider. If user told us the value, use it.
> > + // TODO(): Otherwise, guess (currently assuming max freq)
> > + int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
> > + ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1); // HOLDTIME 2 clks
> > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
> > + mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC clocks 
> PHY
> > + // Select RMII mode, 100M, keep CRC, set max rx length, disable loop
> > + ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
> > + // ENET->RCR |= MG_BIT(3); // Receive all
> > + ENET->TCR = MG_BIT(2); // Full-duplex
> > + ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
> > + ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
> > + ENET->MRBR[0] = ETH_PKT_SIZE; // Same size for RX/TX buffers
> > + // MAC address filtering (bytes in reversed order)
> > + ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t) ifp->mac[5] 
> << 16U;
> > + ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
> > + ((uint32_t) ifp->mac[1] << 16U) |
> > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> > + ENET->ECR = MG_BIT(8) | MG_BIT(1); // Little-endian CPU, Enable
> > + ENET->EIMR = MG_BIT(25); // Set interrupt mask
> > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
> > + ENET->TDAR = MG_BIT(24); // Transmit Descriptors have changed
> > + // ENET->OPD = 0x10014;
> > return true;
> > }
> >
> > -static uint32_t s_txno;
> > -static size_t mip_driver_stm32_tx(const void *buf, size_t len, struct 
> mip_if *ifp) {
> > - if (len > sizeof(s_txbuf[s_txno])) {
> > +// Transmit frame
> > +static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + static int s_txno; // Current descriptor index
> > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
> > MG_ERROR(("Frame too big, %ld", (long) len));
> > - len = 0; // Frame is too big
> > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> > - MG_ERROR(("No free descriptors"));
> > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
> > - len = 0; // All descriptors are busy, fail
> > + len = (size_t) -1; // fail
> > + } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
> > + ifp->nerr++;
> > + MG_ERROR(("No descriptors available"));
> > + len = 0; // retry later
> > } else {
> > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> > - s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30); // 
> Chain,FS,LS
> > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno].length = (uint16_t) len; // Set data len
> > + // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
> > + s_txdesc[s_txno].control |=
> > + (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
> > + ENET->TDAR = MG_BIT(24); // Descriptor ring updated
> > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > }
> > - ETH->DMASR = BIT(2) | BIT(5); // Clear any prior TBUS/TUS
> > - ETH->DMATPDR = 0; // and resume
> > - return len;
> > (void) ifp;
> > + return len;
> > }
> >
> > -static bool mip_driver_stm32_up(struct mip_if *ifp) {
> > - uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
> > - (void) ifp;
> > - return bsr & BIT(2) ? 1 : 0;
> > +static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_imxrt_data *d =
> > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
> > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + // tmp = reg with flags set to the most likely situation: 100M 
> full-duplex
> > + // if(link is slow or half) set flags otherwise
> > + // reg = tmp
> > + uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex
> > + uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M
> > + if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9); // 10M
> > + if (full_duplex == false) tcr &= ~MG_BIT(2); // Half-duplex
> > + ENET->TCR = tcr; // IRQ handler does not fiddle with these registers
> > + ENET->RCR = rcr;
> > + MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
> > + tcr & MG_BIT(2) ? "full" : "half"));
> > + }
> > + return up;
> > }
> >
> > -void ETH_IRQHandler(void);
> > +void ENET_IRQHandler(void);
> > static uint32_t s_rxno;
> > -void ETH_IRQHandler(void) {
> > - qp_mark(QP_IRQTRIGGERED, 0);
> > - if (ETH->DMASR & BIT(6)) { // Frame received, loop
> > - ETH->DMASR = BIT(16) | BIT(6); // Clear flag
> > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
> > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
> > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
> > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> > - // ETH->DMASR);
> > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > - }
> > - s_rxdesc[s_rxno][0] = BIT(31);
> > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > +void ENET_IRQHandler(void) {
> > + ENET->EIR = MG_BIT(25); // Ack IRQ
> > + // Frame received, loop
> > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > + uint32_t r = s_rxdesc[s_rxno].control;
> > + if (r & MG_BIT(15)) break; // exit when done
> > + // skip partial/errored frames (Table 37-32)
> > + if ((r & MG_BIT(11)) &&
> > + !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) | MG_BIT(0)))) {
> > + size_t len = s_rxdesc[s_rxno].length;
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > }
> > + s_rxdesc[s_rxno].control |= MG_BIT(15);
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > }
> > - ETH->DMASR = BIT(7); // Clear possible RBUS while processing
> > - ETH->DMARPDR = 0; // and resume RX
> > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
> > + // If b24 == 0, descriptors were exhausted and probably frames were 
> dropped
> > }
> >
> > -struct mip_driver mip_driver_stm32 = {
> > - mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx, 
> mip_driver_stm32_up};
> > +struct mg_tcpip_driver mg_tcpip_driver_imxrt = 
> {mg_tcpip_driver_imxrt_init,
> > + mg_tcpip_driver_imxrt_tx, NULL,
> > + mg_tcpip_driver_imxrt_up};
> > +
> > #endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "mip/driver_tm4c.c"
> > +#line 1 "src/drivers/phy.c"
> > #endif
> >
> >
> > -#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) && 
> MG_ENABLE_DRIVER_TM4C
> > -struct tm4c_emac {
> > - volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
> > - EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, 
> EMACSTATUS,
> > - EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
> > - EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
> > - EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> > - EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> > - EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
> > - EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], 
> EMACRXCNTCRCERR,
> > - EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> > - EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, 
> EMACSUBSECINC,
> > - EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> > - EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> > - RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> > - EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
> > - EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> > - RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
> > - RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> > - EMACEPHYIM, EMACEPHYIMSC;
> > +enum { // ID1 ID2
> > + MG_PHY_KSZ8x = 0x22, // 0022 1561 - KSZ8081RNB
> > + MG_PHY_DP83x = 0x2000, // 2000 a140 - TI DP83825I
> > + MG_PHY_DP83867 = 0xa231, // 2000 a231 - TI DP83867I
> > + MG_PHY_LAN87x = 0x7, // 0007 c0fx - LAN8720
> > + MG_PHY_RTL8201 = 0x1C // 001c c816 - RTL8201
> > +};
> > +
> > +enum {
> > + MG_PHY_REG_BCR = 0,
> > + MG_PHY_REG_BSR = 1,
> > + MG_PHY_REG_ID1 = 2,
> > + MG_PHY_REG_ID2 = 3,
> > + MG_PHY_DP83x_REG_PHYSTS = 16,
> > + MG_PHY_DP83867_REG_PHYSTS = 17,
> > + MG_PHY_DP83x_REG_RCSR = 23,
> > + MG_PHY_DP83x_REG_LEDCR = 24,
> > + MG_PHY_KSZ8x_REG_PC1R = 30,
> > + MG_PHY_KSZ8x_REG_PC2R = 31,
> > + MG_PHY_LAN87x_REG_SCSR = 31,
> > + MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
> > + MG_PHY_RTL8201_REG_PAGESEL = 31
> > +};
> > +
> > +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
> > + switch (id1) {
> > + case MG_PHY_DP83x:
> > + switch (id2) {
> > + case MG_PHY_DP83867:
> > + return "DP83867";
> > + default:
> > + return "DP83x";
> > + }
> > + case MG_PHY_KSZ8x:
> > + return "KSZ8x";
> > + case MG_PHY_LAN87x:
> > + return "LAN87x";
> > + case MG_PHY_RTL8201:
> > + return "RTL8201";
> > + default:
> > + return "unknown";
> > + }
> > + (void) id2;
> > +}
> > +
> > +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) {
> > + uint16_t id1, id2;
> > + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15)); // Reset PHY
> > + while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15)) (void) 0;
> > + // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw says 
> otherwise
> > +
> > + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> > + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> > + MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, 
> id2)));
> > +
> > + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
> > + phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to IO_MUX_CFG 
> (0x0170)
> > + phy->write_reg(phy_addr, 0x0e, 0x170);
> > + phy->write_reg(phy_addr, 0x0d, 0x401f);
> > + phy->write_reg(phy_addr, 0x0e, 0x10d);
> > + }
> > +
> > + if (config & MG_PHY_CLOCKS_MAC) {
> > + // Use PHY crystal oscillator (preserve defaults)
> > + // nothing to do
> > + } else { // MAC clocks PHY, PHY has no xtal
> > + // Enable 50 MHz external ref clock at XI (preserve defaults)
> > + if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
> > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) | MG_BIT(0));
> > + } else if (id1 == MG_PHY_KSZ8x) {
> > + phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
> > + MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
> > + } else if (id1 == MG_PHY_LAN87x) {
> > + // nothing to do
> > + } else if (id1 == MG_PHY_RTL8201) {
> > + // assume PHY has been hardware strapped properly
> > +#if 0
> > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7); // Select 
> page 7
> > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
> > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0); // Select 
> page 0
> > +#endif
> > + }
> > + }
> > +
> > + if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
> > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
> > + MG_BIT(9) | MG_BIT(7)); // LED status, active high
> > + } // Other PHYs do not support this feature
> > +}
> > +
> > +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex,
> > + uint8_t *speed) {
> > + bool up = false;
> > + uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
> > + if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2))) // some PHYs latch down 
> events
> > + bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); // read again
> > + up = bsr & MG_BIT(2);
> > + if (up && full_duplex != NULL && speed != NULL) {
> > + uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> > + if (id1 == MG_PHY_DP83x) {
> > + uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> > + if (id2 == MG_PHY_DP83867) {
> > + uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83867_REG_PHYSTS);
> > + *full_duplex = physts & MG_BIT(13);
> > + *speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
> > + : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
> > + : MG_PHY_SPEED_10M;
> > + } else {
> > + uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83x_REG_PHYSTS);
> > + *full_duplex = physts & MG_BIT(2);
> > + *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
> > + }
> > + } else if (id1 == MG_PHY_KSZ8x) {
> > + uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
> > + *full_duplex = pc1r & MG_BIT(2);
> > + *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
> > + } else if (id1 == MG_PHY_LAN87x) {
> > + uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
> > + *full_duplex = scsr & MG_BIT(4);
> > + *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
> > + } else if (id1 == MG_PHY_RTL8201) {
> > + uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
> > + *full_duplex = bcr & MG_BIT(8);
> > + *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
> > + }
> > + }
> > + return up;
> > +}
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/ra.c"
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && 
> MG_ENABLE_DRIVER_RA
> > +struct ra_etherc {
> > + volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR, RESERVED2, 
> ECSIPR,
> > + RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR, RESERVED6[3], 
> IPGR,
> > + APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR, RESERVED8[20], 
> MAHR,
> > + RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR, RESERVED11, 
> CEFCR,
> > + FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
> > };
> > -#undef EMAC
> > -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
> >
> > -#undef BIT
> > -#define BIT(x) ((uint32_t) 1 << (x))
> > -#define ETH_PKT_SIZE 1540 // Max frame size
> > +struct ra_edmac {
> > + volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR, RESERVED2, 
> TDLAR,
> > + RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR, RESERVED6, 
> TRSCER,
> > + RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10, RMCR,
> > + RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR, TRIMD,
> > + RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
> > +};
> > +
> > +#undef ETHERC
> > +#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
> > +#undef EDMAC
> > +#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
> > +#undef RASYSC
> > +#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
> > +#undef ICU_IELSR
> > +#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
> > +
> > +#define ETH_PKT_SIZE 1536 // Max frame size, multiple of 32
> > #define ETH_DESC_CNT 4 // Descriptors count
> > -#define ETH_DS 4 // Descriptor size (words)
> >
> > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet 
> buffers
> > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet 
> buffers
> > -static struct mip_if *s_ifp; // MIP interface
> > -enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 }; // PHY constants
> > +// TODO(): handle these in a portable compiler-independent 
> CMSIS-friendly way
> > +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
> > +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
> >
> > -static inline void tm4cspin(volatile uint32_t count) {
> > +// Descriptors: 16-byte aligned
> > +// Buffers: 32-byte aligned (27.3.1)
> > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
> > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +
> > +// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or equivalent
> > +static inline void raspin(volatile uint32_t count) {
> > while (count--) (void) 0;
> > }
> > -
> > -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> > - EMAC->EMACMIIADDR &= (0xf << 2);
> > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> > - EMAC->EMACMIIADDR |= BIT(0);
> > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> > - return EMAC->EMACMIIDATA;
> > +// count to get the 200ns SMC semi-cycle period (2.5MHz) calling 
> raspin():
> > +// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
> > +static uint32_t s_smispin;
> > +
> > +// Bit-banged SMI
> > +static void smi_preamble(void) {
> > + unsigned int i = 32;
> > + uint32_t pir = MG_BIT(1) | MG_BIT(2); // write, mdio = 1, mdc = 0
> > + ETHERC->PIR = pir;
> > + while (i--) {
> > + pir &= ~MG_BIT(0); // mdc = 0
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + pir |= MG_BIT(0); // mdc = 1
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + }
> > }
> > -
> > -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> > - EMAC->EMACMIIDATA = val;
> > - EMAC->EMACMIIADDR &= (0xf << 2);
> > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | 
> BIT(1);
> > - EMAC->EMACMIIADDR |= BIT(0);
> > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> > +static void smi_wr(uint16_t header, uint16_t data) {
> > + uint32_t word = (header << 16) | data;
> > + smi_preamble();
> > + unsigned int i = 32;
> > + while (i--) {
> > + uint32_t pir = MG_BIT(1) |
> > + (word & 0x80000000 ? MG_BIT(2) : 0); // write, mdc = 0, data
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + pir |= MG_BIT(0); // mdc = 1
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + word <<= 1;
> > + }
> > }
> > -
> > -static uint32_t get_sysclk(void) {
> > - struct sysctl {
> > - volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
> > - PLLFREQ1;
> > - } *sysctl = (struct sysctl *) 0x400FE000;
> > - uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 
> 25MHz */;
> > - if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
> > - uint32_t fin, vco, mdiv, n, q, psysdiv;
> > - uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> > - if (pllsrc == 0) {
> > - clk = piosc;
> > - } else if (pllsrc == 3) {
> > - clk = mosc;
> > - } else {
> > - MG_ERROR(("Unsupported clock source"));
> > - }
> > - q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> > - n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> > - fin = clk / ((q + 1) * (n + 1));
> > - mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> > - 0; // mint + (mfrac / 1024); MFRAC not supported
> > - psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> > - vco = (uint32_t) ((uint64_t) fin * mdiv);
> > - return vco / (psysdiv + 1);
> > +static uint16_t smi_rd(uint16_t header) {
> > + smi_preamble();
> > + unsigned int i = 16; // 2 LSb as turnaround
> > + uint32_t pir;
> > + while (i--) {
> > + pir = (i > 1 ? MG_BIT(1) : 0) |
> > + (header & 0x8000
> > + ? MG_BIT(2)
> > + : 0); // mdc = 0, header, set read direction at turnaround
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + pir |= MG_BIT(0); // mdc = 1
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > + header <<= 1;
> > }
> > - uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> > - if (oscsrc == 0) {
> > - clk = piosc;
> > - } else if (oscsrc == 3) {
> > - clk = mosc;
> > - } else {
> > - MG_ERROR(("Unsupported clock source"));
> > + i = 16;
> > + uint16_t data = 0;
> > + while (i--) {
> > + data <<= 1;
> > + pir = 0; // read, mdc = 0
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin / 2); // 1/4 clock period, 300ns max access time
> > + data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0); // read mdio
> > + raspin(s_smispin / 2); // 1/4 clock period
> > + pir |= MG_BIT(0); // mdc = 1
> > + ETHERC->PIR = pir;
> > + raspin(s_smispin);
> > }
> > - uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> > - return clk / (osysdiv + 1);
> > + return data;
> > }
> >
> > -// Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as 
> per
> > -// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock 
> can be
> > -// derived from the PIOSC (internal RC), and it can go above specs, the
> > -// datasheets specify a range of frequencies and activate one of a 
> series of
> > -// dividers to keep the MDC clock safely below 2.5MHz. We guess a 
> divider
> > -// setting based on SYSCLK with a +5% drift. If the user uses a 
> different clock
> > -// from our defaults, needs to set the macros on top Valid for TM4C129x 
> (20.7)
> > -// (4.5% worst case drift)
> > -// The PHY receives the main oscillator (MOSC) (20.3.1)
> > -static int guess_mdc_cr(void) {
> > - uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
> > - uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
> > - uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
> > - int result = -1; // Invalid CR value
> > - if (sysclk < 25000000) {
> > - MG_ERROR(("SYSCLK too low"));
> > - } else {
> > - for (int i = 0; i < 4; i++) {
> > - if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > - result = crs[i];
> > - break;
> > - }
> > - }
> > - if (result < 0) MG_ERROR(("SYSCLK too high"));
> > - }
> > - MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> > - return result;
> > +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
> > + return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) | (reg << 
> 2) | (2 << 0)));
> > }
> >
> > -static bool mip_driver_tm4c_init(struct mip_if *ifp) {
> > - struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data *) 
> ifp->driver_data;
> > +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg << 2) | 
> (2 << 0)), val);
> > +}
> > +
> > +// MDC clock is generated manually; as per 802.3, it must not exceed 
> 2.5MHz
> > +static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_ra_data *d =
> > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
> > s_ifp = ifp;
> >
> > + // Init SMI clock timing. If user told us the clock value, use it.
> > + // TODO(): Otherwise, guess
> > + s_smispin = d->clock / 15000000;
> > +
> > // Init RX descriptors
> > for (int i = 0; i < ETH_DESC_CNT; i++) {
> > - s_rxdesc[i][0] = BIT(31); // Own
> > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address chained
> > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data 
> buffer
> > - s_rxdesc[i][3] =
> > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > - // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> > + s_rxdesc[i][0] = MG_BIT(31); // RACT
> > + s_rxdesc[i][1] = ETH_PKT_SIZE << 16; // RBL
> > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; // Point to data buffer
> > }
> > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last descriptor
> >
> > // Init TX descriptors
> > for (int i = 0; i < ETH_DESC_CNT; i++) {
> > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> > - s_txdesc[i][3] =
> > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + // TACT = 0
> > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
> > }
> > -
> > - EMAC->EMACDMABUSMOD |= BIT(0); // Software reset
> > - while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1); // Wait until 
> done
> > -
> > - // Set MDC clock divider. If user told us the value, use it. 
> Otherwise, guess
> > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> > - EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> > -
> > - // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> > - // hardware checksum. Therefore, descriptor size is 4, not 8
> > - // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23) | 
> BIT(25);
> > - EMAC->EMACIM = BIT(3) | BIT(9); // Mask timestamp & PMT IT
> > - EMAC->EMACFLOWCTL = BIT(7); // Disable zero-quanta pause
> > - // EMAC->EMACFRAMEFLTR = BIT(31); // Receive all
> > - // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15)); // Reset internal PHY 
> (EPHY)
> > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12)); // Set autonegotiation
> > - EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> > - EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX descriptors
> > - EMAC->EMACDMAIM = BIT(6) | BIT(16); // RIE, NIE
> > - EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE, 
> Duplex, Fast
> > - EMAC->EMACDMAOPMODE =
> > - BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
> > - EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> > - EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> > - ((uint32_t) ifp->mac[2] << 16) |
> > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> > - // NOTE(scaprile) There are 3 additional slots for filtering, disabled 
> by
> > - // default. This also applies to the STM32 driver (at least for F7)
> > -
> > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
> > + s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last descriptor
> > +
> > + EDMAC->EDMR = MG_BIT(0); // Software reset, wait 64 PCLKA clocks 
> (27.2.1)
> > + uint32_t sckdivcr = RASYSC[8]; // get divisors from SCKDIVCR (8.2.2)
> > + uint32_t ick = 1 << ((sckdivcr >> 24) & 7); // sys_clock div
> > + uint32_t pcka = 1 << ((sckdivcr >> 12) & 7); // pclka div
> > + raspin((64U * pcka) / (3U * ick));
> > + EDMAC->EDMR = MG_BIT(6); // Initialize, little-endian (27.2.1)
> > +
> > + MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
> > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> > + mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
> > +
> > + // Select RMII mode,
> > + ETHERC->ECMR = MG_BIT(2) | MG_BIT(1); // 100M, Full-duplex, CRC
> > + // ETHERC->ECMR |= MG_BIT(0); // Receive all
> > + ETHERC->RFLR = 1518; // Set max rx length
> > +
> > + EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
> > + EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
> > + // MAC address filtering (bytes in reversed order)
> > + ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
> > + ((uint32_t) ifp->mac[1] << 16U) |
> > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> > + ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
> > +
> > + EDMAC->TFTR = 0; // Store and forward (27.2.10)
> > + EDMAC->FDR = 0x070f; // (27.2.11)
> > + EDMAC->RMCR = MG_BIT(0); // (27.2.12)
> > + ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5); // TE RE
> > + EDMAC->EESIPR = MG_BIT(18); // Enable Rx IRQ
> > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
> > + EDMAC->EDTRR = MG_BIT(0); // Transmit Descriptors have changed
> > return true;
> > }
> >
> > -static uint32_t s_txno;
> > -static size_t mip_driver_tm4c_tx(const void *buf, size_t len, struct 
> mip_if *ifp) {
> > - if (len > sizeof(s_txbuf[s_txno])) {
> > +// Transmit frame
> > +static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + static int s_txno; // Current descriptor index
> > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
> > MG_ERROR(("Frame too big, %ld", (long) len));
> > - len = 0; // fail
> > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> > + len = (size_t) -1; // fail
> > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> > + ifp->nerr++;
> > MG_ERROR(("No descriptors available"));
> > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> > - // EMAC->EMACDMARIS);
> > - len = 0; // fail
> > + len = 0; // retry later
> > } else {
> > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> > - s_txdesc[s_txno][0] =
> > - BIT(20) | BIT(28) | BIT(29) | BIT(30); // Chain,FS,LS,IC
> > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno][1] = len << 16; // Set data len
> > + s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28; // (27.3.1.1) mark valid
> > + EDMAC->EDTRR = MG_BIT(0); // Transmit request
> > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > }
> > - EMAC->EMACDMARIS = BIT(2) | BIT(5); // Clear any prior TU/UNF
> > - EMAC->EMACTXPOLLD = 0; // and resume
> > return len;
> > - (void) ifp;
> > }
> >
> > -static bool mip_driver_tm4c_up(struct mip_if *ifp) {
> > - uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> > - (void) ifp;
> > - return (bmsr & BIT(2)) ? 1 : 0;
> > +static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_ra_data *d =
> > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + // tmp = reg with flags set to the most likely situation: 100M 
> full-duplex
> > + // if(link is slow or half) set flags otherwise
> > + // reg = tmp
> > + uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1); // 100M 
> Full-duplex
> > + if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2); // 10M
> > + if (full_duplex == false) ecmr &= ~MG_BIT(1); // Half-duplex
> > + ETHERC->ECMR = ecmr; // IRQ handler does not fiddle with these 
> registers
> > + MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
> > + ecmr & MG_BIT(1) ? "full" : "half"));
> > + }
> > + return up;
> > }
> >
> > -void EMAC0_IRQHandler(void);
> > +void EDMAC_IRQHandler(void);
> > static uint32_t s_rxno;
> > -void EMAC0_IRQHandler(void) {
> > - qp_mark(QP_IRQTRIGGERED, 0);
> > - if (EMAC->EMACDMARIS & BIT(6)) { // Frame received, loop
> > - EMAC->EMACDMARIS = BIT(16) | BIT(6); // Clear flag
> > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
> > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
> > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
> > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> > - // EMAC->EMACDMARIS);
> > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > - }
> > - s_rxdesc[s_rxno][0] = BIT(31);
> > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > +void EDMAC_IRQHandler(void) {
> > + struct mg_tcpip_driver_ra_data *d =
> > + (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
> > + EDMAC->EESR = MG_BIT(18); // Ack IRQ in EDMAC 1st
> > + ICU_IELSR[d->irqno] &= ~MG_BIT(16); // Ack IRQ in ICU last
> > + // Frame received, loop
> > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > + uint32_t r = s_rxdesc[s_rxno][0];
> > + if (r & MG_BIT(31)) break; // exit when done
> > + // skip partial/errored frames (27.3.1.2)
> > + if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
> > + size_t len = s_rxdesc[s_rxno][1] & 0xffff;
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); // CRC already stripped
> > }
> > + s_rxdesc[s_rxno][0] |= MG_BIT(31);
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > }
> > - EMAC->EMACDMARIS = BIT(7); // Clear possible RU while processing
> > - EMAC->EMACRXPOLLD = 0; // and resume RX
> > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
> > + // If b0 == 0, descriptors were exhausted and probably frames were 
> dropped,
> > + // (27.2.9 RMFCR counts them)
> > }
> >
> > -struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init, 
> mip_driver_tm4c_tx,
> > - mip_driver_rx, mip_driver_tm4c_up};
> > +struct mg_tcpip_driver mg_tcpip_driver_ra = {mg_tcpip_driver_ra_init,
> > + mg_tcpip_driver_ra_tx, NULL,
> > + mg_tcpip_driver_ra_up};
> > +
> > #endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "mip/driver_w5500.c"
> > +#line 1 "src/drivers/same54.c"
> > #endif
> >
> >
> > -#if MG_ENABLE_MIP
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && 
> MG_ENABLE_DRIVER_SAME54
> >
> > -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> > +#include <sam.h>
> >
> > -static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t addr, 
> bool wr,
> > - void *buf, size_t len) {
> > - uint8_t *p = (uint8_t *) buf;
> > - uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> > - (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> > - s->begin(s->spi);
> > - for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> > - for (size_t i = 0; i < len; i++) {
> > - uint8_t r = s->txn(s->spi, p[i]);
> > - if (!wr) p[i] = r;
> > - }
> > - s->end(s->spi);
> > +#define ETH_PKT_SIZE 1536 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 2 // Descriptor size (words)
> > +
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > +static uint8_t s_txno; // Current TX descriptor
> > +static uint8_t s_rxno; // Current RX descriptor
> > +
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> > +
> > +#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
> > +#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
> > +#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
> > +
> > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
> > + GMAC_MAN_OP(2) | // Setting the read operation
> > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
> > + GMAC_MAN_REGA(reg); // Setting the register
> > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
> > + return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk; // Getting the read 
> value
> > }
> >
> > -// clang-format off
> > -static void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t addr, 
> void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
> > -static void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t addr, 
> uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> > -static void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t addr, 
> uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 
> 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
> > -static void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t addr, 
> void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
> > -static uint8_t w5500_r1(struct mip_spi *s, uint8_t block, uint16_t 
> addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
> > -static uint16_t w5500_r2(struct mip_spi *s, uint8_t block, uint16_t 
> addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, 
> sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> > -// clang-format on
> > +#if 0
> > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) | // Setting 
> the write operation
> > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
> > + GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val); // Setting the register
> > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting until 
> the write op is complete
> > +}
> > +#endif
> >
> > -static size_t w5500_rx(void *buf, size_t buflen, struct mip_if *ifp) {
> > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> > - uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len
> > - while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until it is 
> stable
> > - // printf("RSR: %d\n", (int) n);
> > - if (n > 0) {
> > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
> > - n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
> > - if (n <= len + 2 && n > 1) {
> > - r = (uint16_t) (n - 2);
> > - w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
> > +int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
> > + if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
> > + return d->mdc_cr;
> > + } else {
> > + // get MCLK from GCLK_GENERATOR 0
> > + uint32_t div = 512;
> > + uint32_t mclk;
> > + if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
> > + div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
> > + if (div == 0) div = 1;
> > }
> > - w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance read 
> pointer
> > - w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
> > - // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
> > + switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
> > + case GCLK_GENCTRL_SRC_XOSC0_Val:
> > + mclk = 32000000UL; /* 32MHz */
> > + break;
> > + case GCLK_GENCTRL_SRC_XOSC1_Val:
> > + mclk = 32000000UL; /* 32MHz */
> > + break;
> > + case GCLK_GENCTRL_SRC_OSCULP32K_Val:
> > + mclk = 32000UL;
> > + break;
> > + case GCLK_GENCTRL_SRC_XOSC32K_Val:
> > + mclk = 32000UL;
> > + break;
> > + case GCLK_GENCTRL_SRC_DFLL_Val:
> > + mclk = 48000000UL; /* 48MHz */
> > + break;
> > + case GCLK_GENCTRL_SRC_DPLL0_Val:
> > + mclk = 200000000UL; /* 200MHz */
> > + break;
> > + case GCLK_GENCTRL_SRC_DPLL1_Val:
> > + mclk = 200000000UL; /* 200MHz */
> > + break;
> > + default:
> > + mclk = 200000000UL; /* 200MHz */
> > + }
> > +
> > + mclk /= div;
> > + uint8_t crs[] = {0, 1, 2, 3, 4, 5}; // GMAC->NCFGR::CLK values
> > + uint8_t dividers[] = {8, 16, 32, 48, 64, 96}; // Respective CLK 
> dividers
> > + for (int i = 0; i < 6; i++) {
> > + if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > + return crs[i];
> > + }
> > + }
> > +
> > + return 5;
> > }
> > - return r;
> > }
> >
> > -static size_t w5500_tx(const void *buf, size_t buflen, struct mip_if 
> *ifp) {
> > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> > - uint16_t n = 0, len = (uint16_t) buflen;
> > - while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
> > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
> > - w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
> > - w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance write 
> pointer
> > - w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
> > - for (int i = 0; i < 40; i++) {
> > - uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
> > - if (ir == 0) continue;
> > - // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, 
> ptr);
> > - w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
> > - if (ir & 8) len = 0; // Timeout. Report error
> > - if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
> > +static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_same54_data *d =
> > + (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
> > + s_ifp = ifp;
> > +
> > + MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
> > + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
> > + GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d)); // Set MDC 
> divider
> > + GMAC_REGS->GMAC_NCR = 0; // Disable RX & TX
> > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk; // Enable MDC & MDIO
> > +
> > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init TX descriptors
> > + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; // Point to data buffer
> > + s_txdesc[i][1] = MG_BIT(31); // OWN bit
> > }
> > - return len;
> > + s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30); // Last tx descriptor - 
> wrap
> > +
> > + GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18) // DMA recv buf 1536
> > + | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
> > + GMAC_DCFGR_TXPBMS(1); // See #2487
> > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init RX descriptors
> > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; // Address of the data buffer
> > + s_rxdesc[i][1] = 0; // Clear status
> > + }
> > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1); // Last rx descriptor - 
> wrap
> > +
> > + GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc; // about the descriptor 
> addresses
> > + GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc; // Let the controller know
> > +
> > + GMAC_REGS->SA[0].GMAC_SAB =
> > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> > + GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
> > +
> > + GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk; // Disable MII, use RMII
> > + GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk | GMAC_NCFGR_MTIHEN_Msk |
> > + GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
> > + GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
> > + GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
> > + GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
> > + GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
> > + GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
> > + GMAC_REGS->GMAC_IDR = ~0U; // Disable interrupts, then enable required
> > + GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
> > + GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
> > + GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
> > + GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
> > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
> > + NVIC_EnableIRQ(GMAC_IRQn);
> > +
> > + return true;
> > }
> >
> > -static bool w5500_init(struct mip_if *ifp) {
> > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> > - s->end(s->spi);
> > - w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
> > - w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
> > - w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
> > - // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
> > - w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
> > - w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
> > - w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
> > - w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
> > - return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
> > +static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // Frame is too big
> > + } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
> > + ifp->nerr++;
> > + MG_ERROR(("No free descriptors"));
> > + len = 0; // All descriptors are busy, fail
> > + } else {
> > + uint32_t status = len | MG_BIT(15); // Frame length, last chunk
> > + if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30); // wrap
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno][1] = status;
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > + }
> > + __DSB(); // Ensure descriptors have been written
> > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk; // Enable transmission
> > + return len;
> > }
> >
> > -static bool w5500_up(struct mip_if *ifp) {
> > - struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
> > - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> > - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> > +static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
> > + uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
> > + bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
> > +
> > + // If PHY is ready, update NCFGR accordingly
> > + if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
> > + uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
> > + bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
> > + bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
> > + GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
> > + ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
> > + GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
> > + }
> > +
> > + return up;
> > +}
> > +
> > +void GMAC_Handler(void);
> > +void GMAC_Handler(void) {
> > + uint32_t isr = GMAC_REGS->GMAC_ISR;
> > + uint32_t rsr = GMAC_REGS->GMAC_RSR;
> > + uint32_t tsr = GMAC_REGS->GMAC_TSR;
> > + if (isr & GMAC_ISR_RCOMP_Msk) {
> > + if (rsr & GMAC_ISR_RCOMP_Msk) {
> > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> > + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
> > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // Disown
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > + }
> > + }
> > + }
> > +
> > + if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk | 
> GMAC_TSR_TXCOMP_Msk |
> > + GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
> > + // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
> > + if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= 
> MG_BIT(31);
> > + }
> > +
> > + GMAC_REGS->GMAC_RSR = rsr;
> > + GMAC_REGS->GMAC_TSR = tsr;
> > }
> >
> > -struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, 
> w5500_up};
> > +struct mg_tcpip_driver mg_tcpip_driver_same54 = {
> > + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
> > + mg_tcpip_driver_same54_up};
> > #endif
> >
> > #ifdef MG_ENABLE_LINES
> > -#line 1 "mip/mip.c"
> > +#line 1 "src/drivers/stm32f.c"
> > #endif
> >
> >
> > -#if MG_ENABLE_MIP
> > -
> > -#define MIP_ETHEMERAL_PORT 49152
> > -#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
> > -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> > -
> > -#ifndef MIP_QSIZE
> > -#define MIP_QSIZE (16 * 1024) // Queue size
> > -#endif
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> > + MG_ENABLE_DRIVER_STM32F
> > +struct stm32f_eth {
> > + volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, 
> MACFCR,
> > + MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, 
> MACSR,
> > + MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
> > + MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> > + RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> > + RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> > + RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
> > + PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, 
> RESERVED9[564],
> > + DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
> > + DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> > + DMACHRBAR;
> > +};
> > +#undef ETH
> > +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
> >
> > -#ifndef MIP_TCP_KEEPALIVE_MS
> > -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
> > -#endif
> > +#define ETH_PKT_SIZE 1540 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 4 // Descriptor size (words)
> >
> > -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
> > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet 
> buffers
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet 
> buffers
> > +static uint8_t s_txno; // Current TX descriptor
> > +static uint8_t s_rxno; // Current RX descriptor
> >
> > -struct connstate {
> > - uint32_t seq, ack; // TCP seq/ack counters
> > - uint64_t timer; // TCP keep-alive / ACK timer
> > - uint8_t mac[6]; // Peer MAC address
> > - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
> > -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send 
> keepalive
> > -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon
> > - uint8_t tmiss; // Number of keep-alive misses
> > - struct mg_iobuf raw; // For TLS only. Incoming raw data
> > -};
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >
> > -#pragma pack(push, 1)
> > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > + ETH->MACMIIAR &= (7 << 2);
> > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> > + ETH->MACMIIAR |= MG_BIT(0);
> > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> > + return ETH->MACMIIDR & 0xffff;
> > +}
> >
> > -struct lcp {
> > - uint8_t addr, ctrl, proto[2], code, id, len[2];
> > -};
> > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + ETH->MACMIIDR = val;
> > + ETH->MACMIIAR &= (7 << 2);
> > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | 
> MG_BIT(1);
> > + ETH->MACMIIAR |= MG_BIT(0);
> > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> > +}
> >
> > -struct eth {
> > - uint8_t dst[6]; // Destination MAC address
> > - uint8_t src[6]; // Source MAC address
> > - uint16_t type; // Ethernet type
> > -};
> > +static uint32_t get_hclk(void) {
> > + struct rcc {
> > + volatile uint32_t CR, PLLCFGR, CFGR;
> > + } *rcc = (struct rcc *) 0x40023800;
> > + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz 
> */;
> >
> > -struct ip {
> > - uint8_t ver; // Version
> > - uint8_t tos; // Unused
> > - uint16_t len; // Length
> > - uint16_t id; // Unused
> > - uint16_t frag; // Fragmentation
> > - uint8_t ttl; // Time to live
> > - uint8_t proto; // Upper level protocol
> > - uint16_t csum; // Checksum
> > - uint32_t src; // Source IP
> > - uint32_t dst; // Destination IP
> > -};
> > + if (rcc->CFGR & (1 << 2)) {
> > + clk = hse;
> > + } else if (rcc->CFGR & (1 << 3)) {
> > + uint32_t vco, m, n, p;
> > + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> > + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> > + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> > + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> > + vco = (uint32_t) ((uint64_t) clk * n / m);
> > + clk = vco / p;
> > + } else {
> > + clk = hsi;
> > + }
> > + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> > + if (hpre < 8) return clk;
> >
> > -struct ip6 {
> > - uint8_t ver; // Version
> > - uint8_t opts[3]; // Options
> > - uint16_t len; // Length
> > - uint8_t proto; // Upper level protocol
> > - uint8_t ttl; // Time to live
> > - uint8_t src[16]; // Source IP
> > - uint8_t dst[16]; // Destination IP
> > -};
> > + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> > + return ((uint32_t) clk) >> ahbptab[hpre - 8];
> > +}
> >
> > -struct icmp {
> > - uint8_t type;
> > - uint8_t code;
> > - uint16_t csum;
> > -};
> > +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 
> 802.3,
> > +// it must not exceed 2.5MHz As the AHB clock can be (and usually is) 
> derived
> > +// from the HSI (internal RC), and it can go above specs, the datasheets
> > +// specify a range of frequencies and activate one of a series of 
> dividers to
> > +// keep the MDC clock safely below 2.5MHz. We guess a divider setting 
> based on
> > +// HCLK with a +5% drift. If the user uses a different clock from our
> > +// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
> > +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
> > +static int guess_mdc_cr(void) {
> > + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
> > + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers
> > + uint32_t hclk = get_hclk(); // Guess system HCLK
> > + int result = -1; // Invalid CR value
> > + if (hclk < 25000000) {
> > + MG_ERROR(("HCLK too low"));
> > + } else {
> > + for (int i = 0; i < 6; i++) {
> > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > + result = crs[i];
> > + break;
> > + }
> > + }
> > + if (result < 0) MG_ERROR(("HCLK too high"));
> > + }
> > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> > + return result;
> > +}
> >
> > -struct arp {
> > - uint16_t fmt; // Format of hardware address
> > - uint16_t pro; // Format of protocol address
> > - uint8_t hlen; // Length of hardware address
> > - uint8_t plen; // Length of protocol address
> > - uint16_t op; // Operation
> > - uint8_t sha[6]; // Sender hardware address
> > - uint32_t spa; // Sender protocol address
> > - uint8_t tha[6]; // Target hardware address
> > - uint32_t tpa; // Target protocol address
> > -};
> > +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_stm32f_data *d =
> > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> > + s_ifp = ifp;
> >
> > -struct tcp {
> > - uint16_t sport; // Source port
> > - uint16_t dport; // Destination port
> > - uint32_t seq; // Sequence number
> > - uint32_t ack; // Acknowledgement number
> > - uint8_t off; // Data offset
> > - uint8_t flags; // TCP flags
> > -#define TH_FIN 0x01
> > -#define TH_SYN 0x02
> > -#define TH_RST 0x04
> > -#define TH_PUSH 0x08
> > -#define TH_ACK 0x10
> > -#define TH_URG 0x20
> > -#define TH_ECE 0x40
> > -#define TH_CWR 0x80
> > - uint16_t win; // Window
> > - uint16_t csum; // Checksum
> > - uint16_t urp; // Urgent pointer
> > -};
> > + // Init RX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_rxdesc[i][0] = MG_BIT(31); // Own
> > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address 
> chained
> > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data 
> buffer
> > + s_rxdesc[i][3] =
> > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + }
> >
> > -struct udp {
> > - uint16_t sport; // Source port
> > - uint16_t dport; // Destination port
> > - uint16_t len; // UDP length
> > - uint16_t csum; // UDP checksum
> > -};
> > + // Init TX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> > + s_txdesc[i][3] =
> > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + }
> >
> > -struct dhcp {
> > - uint8_t op, htype, hlen, hops;
> > - uint32_t xid;
> > - uint16_t secs, flags;
> > - uint32_t ciaddr, yiaddr, siaddr, giaddr;
> > - uint8_t hwaddr[208];
> > - uint32_t magic;
> > - uint8_t options[32];
> > -};
> > + ETH->DMABMR |= MG_BIT(0); // Software reset
> > + while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until done
> >
> > -#pragma pack(pop)
> > + // Set MDC clock divider. If user told us the value, use it. 
> Otherwise, guess
> > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> > + ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
> >
> > -struct pkt {
> > - struct mg_str raw; // Raw packet data
> > - struct mg_str pay; // Payload data
> > - struct eth *eth;
> > - struct llc *llc;
> > - struct arp *arp;
> > - struct ip *ip;
> > - struct ip6 *ip6;
> > - struct icmp *icmp;
> > - struct tcp *tcp;
> > - struct udp *udp;
> > - struct dhcp *dhcp;
> > -};
> > + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> > + // hardware checksum. Therefore, descriptor size is 4, not 8
> > + // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) |
> > + // MG_BIT(25);
> > + ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
> > + ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause
> > + // ETH->MACFFR = MG_BIT(31); // Receive all
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
> > + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> > + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
> > + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE
> > + ETH->MACCR =
> > + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, 
> Fast
> > + ETH->DMAOMR =
> > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF
> >
> > -static void q_copyin(struct queue *q, const uint8_t *buf, size_t len,
> > - size_t head) {
> > - size_t left = q->len - head;
> > - memcpy(&q->buf[head], buf, left < len ? left : len);
> > - if (left < len) memcpy(q->buf, &buf[left], len - left);
> > + // MAC address filtering
> > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> > + ((uint32_t) ifp->mac[2] << 16) |
> > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> > + return true;
> > }
> >
> > -static void q_copyout(struct queue *q, uint8_t *buf, size_t len, size_t 
> tail) {
> > - size_t left = q->len - tail;
> > - memcpy(buf, &q->buf[tail], left < len ? left : len);
> > - if (left < len) memcpy(&buf[left], q->buf, len - left);
> > +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // Frame is too big
> > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> > + ifp->nerr++;
> > + MG_ERROR(("No free descriptors"));
> > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
> > + len = 0; // All descriptors are busy, fail
> > + } else {
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> > + s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); // 
> Chain,FS,LS
> > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > + }
> > + MG_DSB(); // ensure descriptors have been written
> > + ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS
> > + ETH->DMATPDR = 0; // and resume
> > + return len;
> > }
> >
> > -static bool q_write(struct queue *q, const void *buf, size_t len) {
> > - bool success = false;
> > - size_t left = (q->len - q->head + q->tail - 1) % q->len;
> > - if (len + sizeof(size_t) <= left) {
> > - q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
> > - q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) % q->len);
> > - q->head = (q->head + sizeof(len) + len) % q->len;
> > - success = true;
> > +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_stm32f_data *d =
> > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + // tmp = reg with flags set to the most likely situation: 100M 
> full-duplex
> > + // if(link is slow or half) set flags otherwise
> > + // reg = tmp
> > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M, 
> Full-duplex
> > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
> > + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex
> > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this register
> > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> > + maccr & MG_BIT(11) ? "full" : "half"));
> > }
> > - return success;
> > + return up;
> > }
> >
> > -#ifdef MIP_QPROFILE
> > -static inline size_t q_space(struct queue *q) {
> > - return q->tail > q->head ? q->tail - q->head : q->tail + (q->len - 
> q->head);
> > +#ifdef __riscv
> > +__attribute__((interrupt())) // For RISCV CH32V307, which share the 
> same MAC
> > +#endif
> > +void ETH_IRQHandler(void);
> > +void ETH_IRQHandler(void) {
> > + if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop
> > + ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag
> > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
> > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
> > + (MG_BIT(8) | MG_BIT(9))) &&
> > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames
> > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> > + // ETH->DMASR);
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > + }
> > + s_rxdesc[s_rxno][0] = MG_BIT(31);
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > + }
> > + }
> > + // Cleanup flags
> > + ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary
> > + | MG_BIT(7); // Clear possible RBUS while processing
> > + ETH->DMARPDR = 0; // and resume RX
> > }
> > +
> > +struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
> > + mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
> > + mg_tcpip_driver_stm32f_up};
> > #endif
> >
> > -static inline size_t q_avail(struct queue *q) {
> > - size_t n = 0;
> > - if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n), 
> q->tail);
> > - return n;
> > -}
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/stm32h.c"
> > +#endif
> >
> > -static size_t q_read(struct queue *q, void *buf) {
> > - size_t n = q_avail(q);
> > - if (n > 0) {
> > - q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
> > - q->tail = (q->tail + sizeof(n) + n) % q->len;
> > - }
> > - return n;
> > -}
> >
> > -static struct mg_str mkstr(void *buf, size_t len) {
> > - struct mg_str str = {(char *) buf, len};
> > - return str;
> > -}
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> > + MG_ENABLE_DRIVER_STM32H
> > +struct stm32h_eth {
> > + volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
> > + RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR,
> > + RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR,
> > + MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], 
> MACLCSR,
> > + MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10,
> > + MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR,
> > + RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR,
> > + MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR,
> > + MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR,
> > + RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
> > + RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
> > + MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
> > + RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
> > + RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
> > + MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR,
> > + MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR,
> > + RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28,
> > + MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
> > + RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, 
> MACPPSIR,
> > + MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, 
> MACLMIR,
> > + RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], 
> MTLTQOMR,
> > + MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, 
> MTLRQDR,
> > + RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], 
> DMACCR,
> > + DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
> > + DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
> > + DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR,
> > + RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2],
> > + DMACMFCR;
> > +};
> > +#undef ETH
> > +#define ETH \
> > + ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 
> 0x8000UL))
> >
> > -static void mkpay(struct pkt *pkt, void *p) {
> > - pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) 
> p));
> > +#define ETH_PKT_SIZE 1540 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 4 // Descriptor size (words)
> > +
> > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX 
> descriptors
> > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX 
> descriptors
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet 
> buffers
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet 
> buffers
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +
> > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > + ETH->MACMDIOAR &= (0xF << 8);
> > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 
> << 2;
> > + ETH->MACMDIOAR |= MG_BIT(0);
> > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
> > + return (uint16_t) ETH->MACMDIODR;
> > }
> >
> > -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> > - const uint8_t *p = (const uint8_t *) buf;
> > - for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) 
> (p[i] << 8);
> > - return sum;
> > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + ETH->MACMDIODR = val;
> > + ETH->MACMDIOAR &= (0xF << 8);
> > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 
> << 2;
> > + ETH->MACMDIOAR |= MG_BIT(0);
> > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
> > }
> >
> > -static uint16_t csumfin(uint32_t sum) {
> > - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> > - return mg_htons(~sum & 0xffff);
> > +static uint32_t get_hclk(void) {
> > + struct rcc {
> > + volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR,
> > + D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR,
> > + PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R,
> > + D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR,
> > + RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR,
> > + APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9],
> > + RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR,
> > + APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
> > + AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR,
> > + RESERVED13[4];
> > + } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
> > + uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz 
> */,
> > + csi = 4000000 /* 4MHz */;
> > + unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
> > +
> > + if (sel == 1) {
> > + clk = csi;
> > + } else if (sel == 2) {
> > + clk = hse;
> > + } else if (sel == 3) {
> > + uint32_t vco, m, n, p;
> > + unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
> > + m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
> > + n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
> > + ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional mode
> > + p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
> > + if (src == 1) {
> > + clk = csi;
> > + } else if (src == 2) {
> > + clk = hse;
> > + } else {
> > + clk = hsi;
> > + clk >>= ((rcc->CR & 3) >> 3);
> > + }
> > + vco = (uint32_t) ((uint64_t) clk * n / m);
> > + clk = vco / p;
> > + } else {
> > + clk = hsi;
> > + clk >>= ((rcc->CR & 3) >> 3);
> > + }
> > + const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> > + uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
> > + if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
> > + MG_DEBUG(("D1 CLK: %u", clk));
> > + uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
> > + if (hpre < 8) return clk;
> > + return ((uint32_t) clk) >> cptab[hpre - 8];
> > +}
> > +
> > +// Guess CR from AHB1 clock. MDC clock is generated from the ETH 
> peripheral
> > +// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock 
> can
> > +// be derived from HSI or CSI (internal RC) clocks, and those can go 
> above
> > +// specs, the datasheets specify a range of frequencies and activate 
> one of a
> > +// series of dividers to keep the MDC clock safely below 2.5MHz. We 
> guess a
> > +// divider setting based on HCLK with some drift. If the user uses a 
> different
> > +// clock from our defaults, needs to set the macros on top. Valid for
> > +// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 
> 7.5 %
> > +// worst case drift @ max temp)
> > +static int guess_mdc_cr(void) {
> > + const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR values
> > + const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK 
> dividers
> > + uint32_t hclk = get_hclk(); // Guess system HCLK
> > + int result = -1; // Invalid CR value
> > + for (int i = 0; i < 6; i++) {
> > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > + result = crs[i];
> > + break;
> > + }
> > + }
> > + if (result < 0) MG_ERROR(("HCLK too high"));
> > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> > + return result;
> > }
> >
> > -static uint16_t ipcsum(const void *buf, size_t len) {
> > - uint32_t sum = csumup(0, buf, len);
> > - return csumfin(sum);
> > -}
> > +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_stm32h_data *d =
> > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> > + s_ifp = ifp;
> > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> > + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
> >
> > -// ARP cache is organised as a doubly linked list. A successful cache 
> lookup
> > -// moves an entry to the head of the list. New entries are added by 
> replacing
> > -// the last entry in the list with a new IP/MAC.
> > -// ARP cache format: | prev | next | Entry0 | Entry1 | .... | EntryN |
> > -// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
> > -// prev and next are 1-byte offsets in the cache, so cache size is max 
> 256 bytes
> > -// ARP entry size is 12 bytes
> > -static void arp_cache_init(uint8_t *p, int n, int size) {
> > - for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i - 1) * 
> size);
> > - for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i + 1) * 
> size);
> > - p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
> > - p[1] = p[3 + (n - 1) * size] = 2;
> > -}
> > + // Init RX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data 
> buffer
> > + s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, 
> BUF1V
> > + }
> >
> > -#if 0
> > -static inline void arp_cache_dump(const uint8_t *p) {
> > - MG_INFO(("ARP cache:"));
> > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
> > - MG_INFO((" %I -> %A", 4, &p[j + 2], &p[j + 6]));
> > + // Init TX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> > }
> > -}
> > -#endif
> >
> > -static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
> > + ETH->DMAMR |= MG_BIT(0); // Software reset
> > + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done
> >
> > -static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
> > - uint8_t *p = ifp->arp_cache;
> > - if (ip == 0) return NULL;
> > - // use broadcast MAC for local and global broadcast IP
> > - if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
> > - return (uint8_t *) bcastmac;
> > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
> > - if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
> > - p[1] = j, p[0] = p[j]; // Found entry! Point list head to us
> > - // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
> > - return p + j + 6; // And return MAC address
> > - }
> > - }
> > - return NULL;
> > -}
> > + // Set MDC clock divider. If user told us the value, use it. 
> Otherwise, guess
> > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> > + ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
> > +
> > + // NOTE(scaprile): We do not use timing facilities so the DMA engine 
> does not
> > + // re-write buffer address
> > + ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset value)
> > + ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this actually 
> needed
> > + ETH->MACIER = 0; // Do not enable additional irq sources (reset value)
> > + ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause
> > + // ETH->MACPFR = MG_BIT(31); // Receive all
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + mg_phy_init(&phy, phy_addr, phy_conf);
> > + ETH->DMACRDLAR =
> > + (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address
> > + ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length
> > + ETH->DMACRDTPR =
> > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
> > + 1]; // last valid descriptor address
> > + ETH->DMACTDLAR =
> > + (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address
> > + ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length
> > + ETH->DMACTDTPR =
> > + (uint32_t) (uintptr_t) s_txdesc; // first available descriptor address
> > + ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table) (reset value)
> > + ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE
> > + ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
> > + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved
> > + ETH->MTLTQOMR |= MG_BIT(1); // TSF
> > + ETH->MTLRQOMR |= MG_BIT(5); // RSF
> > + ETH->DMACTCR |= MG_BIT(0); // ST
> > + ETH->DMACRCR |= MG_BIT(0); // SR
> >
> > -static void arp_cache_add(struct mip_if *ifp, uint32_t ip, uint8_t 
> mac[6]) {
> > - uint8_t *p = ifp->arp_cache;
> > - if (ip == 0 || ip == ~0U) return; // Bad IP
> > - if (arp_cache_find(ifp, ip) != NULL) return; // Already exists, do 
> nothing
> > - memcpy(p + p[0] + 2, &ip, sizeof(ip)); // Replace last entry: IP 
> address
> > - memcpy(p + p[0] + 6, mac, 6); // And MAC address
> > - p[1] = p[0], p[0] = p[p[1]]; // Point list head to us
> > - MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
> > + // MAC address filtering
> > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> > + ((uint32_t) ifp->mac[2] << 16) |
> > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> > + return true;
> > }
> >
> > -static size_t ether_output(struct mip_if *ifp, size_t len) {
> > - // size_t min = 64; // Pad short frames to 64 bytes (minimum Ethernet 
> size)
> > - // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len = min;
> > - // mg_hexdump(ifp->tx.ptr, len);
> > - return ifp->driver->tx(ifp->tx.ptr, len, ifp);
> > +static uint32_t s_txno;
> > +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // Frame is too big
> > + } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
> > + ifp->nerr++;
> > + MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
> > + s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
> > + for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", 
> s_txdesc[i][3]));
> > + len = 0; // All descriptors are busy, fail
> > + } else {
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno][2] = (uint32_t) len; // Set data len
> > + s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD
> > + s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA take over
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > + }
> > + ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS
> > + ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; // and 
> resume
> > + return len;
> > + (void) ifp;
> > }
> >
> > -static void arp_ask(struct mip_if *ifp, uint32_t ip) {
> > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> > - struct arp *arp = (struct arp *) (eth + 1);
> > - memset(eth->dst, 255, sizeof(eth->dst));
> > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
> > - eth->type = mg_htons(0x806);
> > - memset(arp, 0, sizeof(*arp));
> > - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> > - arp->plen = 4;
> > - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> > - memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> > - ether_output(ifp, PDIFF(eth, arp + 1));
> > +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_stm32h_data *d =
> > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + // tmp = reg with flags set to the most likely situation: 100M 
> full-duplex
> > + // if(link is slow or half) set flags otherwise
> > + // reg = tmp
> > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M, 
> Full-duplex
> > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
> > + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex
> > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this register
> > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> > + maccr & MG_BIT(13) ? "full" : "half"));
> > + }
> > + return up;
> > }
> >
> > -static void onstatechange(struct mip_if *ifp) {
> > - if (ifp->state == MIP_STATE_READY) {
> > - MG_INFO(("READY, IP: %I", 4, &ifp->ip));
> > - MG_INFO((" GW: %I", 4, &ifp->gw));
> > - if (ifp->lease_expire > ifp->now) {
> > - MG_INFO(
> > - (" Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
> > +void ETH_IRQHandler(void);
> > +static uint32_t s_rxno;
> > +void ETH_IRQHandler(void) {
> > + if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop
> > + ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag
> > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > + if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done
> > + if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
> > + (MG_BIT(28) | MG_BIT(29))) &&
> > + !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored frames
> > + uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
> > + // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3],
> > + // ETH->DMACSR));
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > + }
> > + s_rxdesc[s_rxno][3] =
> > + MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > }
> > - arp_ask(ifp, ifp->gw);
> > - } else if (ifp->state == MIP_STATE_UP) {
> > - MG_ERROR(("Link up"));
> > - } else if (ifp->state == MIP_STATE_DOWN) {
> > - MG_ERROR(("Link down"));
> > }
> > + ETH->DMACSR =
> > + MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
> > + ETH->DMACRDTPR =
> > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and resume RX
> > }
> >
> > -static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto, uint32_t 
> ip_src,
> > - uint32_t ip_dst, size_t plen) {
> > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> > - struct ip *ip = (struct ip *) (eth + 1);
> > - uint8_t *mac = arp_cache_find(ifp, ip_dst); // Dst IP in ARP cache ?
> > - if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
> > - arp_ask(ifp, ip_dst); // Same net, lookup
> > - if (!mac) mac = arp_cache_find(ifp, ifp->gw); // Use gateway MAC
> > - if (!mac) arp_ask(ifp, ifp->gw); // Not found? lookup
> > - if (mac) memcpy(eth->dst, mac, sizeof(eth->dst)); // Found? Use it
> > - if (!mac) memset(eth->dst, 255, sizeof(eth->dst)); // No? Use broadcast
> > - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // TODO(cpq): ARP lookup
> > - eth->type = mg_htons(0x800);
> > - memset(ip, 0, sizeof(*ip));
> > - ip->ver = 0x45; // Version 4, header length 5 words
> > - ip->frag = 0x40; // Don't fragment
> > - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> > - ip->ttl = 64;
> > - ip->proto = proto;
> > - ip->src = ip_src;
> > - ip->dst = ip_dst;
> > - ip->csum = ipcsum(ip, sizeof(*ip));
> > - return ip;
> > -}
> > +struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
> > + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
> > + mg_tcpip_driver_stm32h_up};
> > +#endif
> >
> > -static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t sport,
> > - uint32_t ip_dst, uint16_t dport, const void *buf,
> > - size_t len) {
> > - struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len + sizeof(struct 
> udp));
> > - struct udp *udp = (struct udp *) (ip + 1);
> > - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> > - udp->sport = sport;
> > - udp->dport = dport;
> > - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> > - udp->csum = 0;
> > - uint32_t cs = csumup(0, udp, sizeof(*udp));
> > - cs = csumup(cs, buf, len);
> > - cs = csumup(cs, &ip->src, sizeof(ip->src));
> > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> > - cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> > - udp->csum = csumfin(cs);
> > - memmove(udp + 1, buf, len);
> > - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> > - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + 
> len);
> > -}
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/tm4c.c"
> > +#endif
> >
> > -static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
> > - uint8_t *opts, size_t optslen) {
> > - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> > - dhcp.magic = mg_htonl(0x63825363);
> > - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> > - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> > - memcpy(&dhcp.options, opts, optslen);
> > - tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp, sizeof(dhcp));
> > -}
> >
> > -static void tx_dhcp_request(struct mip_if *ifp, uint32_t src, uint32_t 
> dst) {
> > - uint8_t opts[] = {
> > - 53, 1, 3, // Type: DHCP request
> > - 55, 2, 1, 3, // GW and mask
> > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
> > - 54, 4, 0, 0, 0, 0, // DHCP server ID
> > - 50, 4, 0, 0, 0, 0, // Requested IP
> > - 255 // End of options
> > - };
> > - memcpy(opts + 14, &dst, sizeof(dst));
> > - memcpy(opts + 20, &src, sizeof(src));
> > - tx_dhcp(ifp, src, dst, opts, sizeof(opts));
> > -}
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && 
> MG_ENABLE_DRIVER_TM4C
> > +struct tm4c_emac {
> > + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
> > + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, 
> EMACSTATUS,
> > + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
> > + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
> > + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> > + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> > + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
> > + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], 
> EMACRXCNTCRCERR,
> > + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> > + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, 
> EMACSUBSECINC,
> > + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> > + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> > + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> > + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
> > + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> > + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
> > + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> > + EMACEPHYIM, EMACEPHYIMSC;
> > +};
> > +#undef EMAC
> > +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
> >
> > -static void tx_dhcp_discover(struct mip_if *ifp) {
> > - uint8_t opts[] = {
> > - 53, 1, 1, // Type: DHCP discover
> > - 55, 2, 1, 3, // Parameters: ip, mask
> > - 255 // End of options
> > - };
> > - tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
> > - MG_DEBUG(("DHCP discover sent"));
> > -}
> > +#define ETH_PKT_SIZE 1540 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 4 // Descriptor size (words)
> >
> > -static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
> > - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> > - // ARP request. Make a response, then send
> > - MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4, 
> &pkt->arp->spa, 4,
> > - &pkt->arp->tpa));
> > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> > - struct arp *arp = (struct arp *) (eth + 1);
> > - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
> > - eth->type = mg_htons(0x806);
> > - *arp = *pkt->arp;
> > - arp->op = mg_htons(2);
> > - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> > - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> > - arp->tpa = pkt->arp->spa;
> > - arp->spa = ifp->ip;
> > - MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
> > - ether_output(ifp, PDIFF(eth, arp + 1));
> > - } else if (pkt->arp->op == mg_htons(2)) {
> > - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) 
> return;
> > - // MG_INFO(("ARP RESPONSE"));
> > - arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
> > - }
> > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet 
> buffers
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet 
> buffers
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +enum {
> > + EPHY_ADDR = 0,
> > + EPHYBMCR = 0,
> > + EPHYBMSR = 1,
> > + EPHYSTS = 16
> > +}; // PHY constants
> > +
> > +static inline void tm4cspin(volatile uint32_t count) {
> > + while (count--) (void) 0;
> > }
> >
> > -static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
> > - // MG_DEBUG(("ICMP %d", (int) len));
> > - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == 
> ifp->ip) {
> > - size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct 
> icmp);
> > - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> > - if (plen > space) plen = space;
> > - struct ip *ip =
> > - tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
> > - struct icmp *icmp = (struct icmp *) (ip + 1);
> > - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
> > - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX
> > - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> > - ether_output(ifp, hlen + plen);
> > - }
> > +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> > + EMAC->EMACMIIADDR &= (0xf << 2);
> > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> > + EMAC->EMACMIIADDR |= MG_BIT(0);
> > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
> > + return EMAC->EMACMIIDATA;
> > }
> >
> > -static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
> > - uint32_t ip = 0, gw = 0, mask = 0;
> > - uint8_t *p = pkt->dhcp->options,
> > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> > - while (p + 1 < end && p[0] != 255) { // Parse options
> > - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { // Mask
> > - memcpy(&mask, p + 2, sizeof(mask));
> > - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW
> > - memcpy(&gw, p + 2, sizeof(gw));
> > - ip = pkt->dhcp->yiaddr;
> > - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
> > - uint32_t lease = 0;
> > - memcpy(&lease, p + 2, sizeof(lease));
> > - ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
> > - }
> > - p += p[1] + 2;
> > - }
> > - if (ip && mask && gw && ifp->ip == 0) {
> > - arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *) 
> pkt->raw.ptr)->src);
> > - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> > - ifp->state = MIP_STATE_READY;
> > - onstatechange(ifp);
> > - tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
> > - }
> > +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
> > + EMAC->EMACMIIDATA = val;
> > + EMAC->EMACMIIADDR &= (0xf << 2);
> > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | 
> MG_BIT(1);
> > + EMAC->EMACMIIADDR |= MG_BIT(0);
> > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
> > }
> >
> > -// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> > -static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
> > - uint8_t op = 0, *p = pkt->dhcp->options,
> > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> > - // struct dhcp *req = pkt->dhcp;
> > - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> > - res.yiaddr = ifp->ip;
> > - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
> > - while (p + 1 < end && p[0] != 255) { // Parse options
> > - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
> > - op = p[2];
> > +static uint32_t get_sysclk(void) {
> > + struct sysctl {
> > + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
> > + PLLFREQ1;
> > + } *sysctl = (struct sysctl *) 0x400FE000;
> > + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 
> 25MHz */;
> > + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
> > + uint32_t fin, vco, mdiv, n, q, psysdiv;
> > + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> > + if (pllsrc == 0) {
> > + clk = piosc;
> > + } else if (pllsrc == 3) {
> > + clk = mosc;
> > + } else {
> > + MG_ERROR(("Unsupported clock source"));
> > }
> > - p += p[1] + 2;
> > - }
> > - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
> > - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK
> > - uint8_t opts[] = {
> > - 53, 1, msg, // Message type
> > - 1, 4, 0, 0, 0, 0, // Subnet mask
> > - 54, 4, 0, 0, 0, 0, // Server ID
> > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
> > - 51, 4, 255, 255, 255, 255, // Lease time
> > - 255 // End of options
> > - };
> > - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> > - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> > - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> > - memcpy(&res.options, opts, sizeof(opts));
> > - res.magic = pkt->dhcp->magic;
> > - res.xid = pkt->dhcp->xid;
> > - arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
> > - tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr, 
> mg_htons(68),
> > - &res, sizeof(res));
> > + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> > + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> > + fin = clk / ((q + 1) * (n + 1));
> > + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> > + 0; // mint + (mfrac / 1024); MFRAC not supported
> > + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> > + vco = (uint32_t) ((uint64_t) fin * mdiv);
> > + return vco / (psysdiv + 1);
> > }
> > -}
> > -
> > -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt 
> *pkt,
> > - bool lsn) {
> > - struct mg_connection *c = NULL;
> > - for (c = mgr->conns; c != NULL; c = c->next) {
> > - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
> > - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> > - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> > - break;
> > + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> > + if (oscsrc == 0) {
> > + clk = piosc;
> > + } else if (oscsrc == 3) {
> > + clk = mosc;
> > + } else {
> > + MG_ERROR(("Unsupported clock source"));
> > }
> > - return c;
> > + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> > + return clk / (osysdiv + 1);
> > }
> >
> > -static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
> > - struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> > - if (c == NULL) {
> > - // No UDP listener on this port. Should send ICMP, but keep silent.
> > - } else if (c != NULL) {
> > - c->rem.port = pkt->udp->sport;
> > - c->rem.ip = pkt->ip->src;
> > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
> > - mg_error(c, "max_recv_buf_size reached");
> > - } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> > - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> > - mg_error(c, "oom");
> > - } else {
> > - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
> > - c->recv.len += pkt->pay.len;
> > - mg_call(c, MG_EV_READ, &pkt->pay.len);
> > +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as 
> per
> > +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock 
> can be
> > +// derived from the PIOSC (internal RC), and it can go above specs, the
> > +// datasheets specify a range of frequencies and activate one of a 
> series of
> > +// dividers to keep the MDC clock safely below 2.5MHz. We guess a 
> divider
> > +// setting based on SYSCLK with a +5% drift. If the user uses a 
> different clock
> > +// from our defaults, needs to set the macros on top Valid for TM4C129x 
> (20.7)
> > +// (4.5% worst case drift)
> > +// The PHY receives the main oscillator (MOSC) (20.3.1)
> > +static int guess_mdc_cr(void) {
> > + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
> > + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
> > + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
> > + int result = -1; // Invalid CR value
> > + if (sysclk < 25000000) {
> > + MG_ERROR(("SYSCLK too low"));
> > + } else {
> > + for (int i = 0; i < 4; i++) {
> > + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> > + result = crs[i];
> > + break;
> > + }
> > }
> > + if (result < 0) MG_ERROR(("SYSCLK too high"));
> > }
> > + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> > + return result;
> > }
> >
> > -static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip, uint8_t flags,
> > - uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
> > - const void *buf, size_t len) {
> > - struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct tcp) + 
> len);
> > - struct tcp *tcp = (struct tcp *) (ip + 1);
> > - memset(tcp, 0, sizeof(*tcp));
> > - if (buf != NULL && len) memmove(tcp + 1, buf, len);
> > - tcp->sport = sport;
> > - tcp->dport = dport;
> > - tcp->seq = seq;
> > - tcp->ack = ack;
> > - tcp->flags = flags;
> > - tcp->win = mg_htons(8192);
> > - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> > - uint32_t cs = 0;
> > - uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> > - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 
> 255)};
> > - cs = csumup(cs, tcp, n);
> > - cs = csumup(cs, &ip->src, sizeof(ip->src));
> > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> > - cs = csumup(cs, pseudo, sizeof(pseudo));
> > - tcp->csum = csumfin(cs);
> > - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
> > -}
> > +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_tm4c_data *d =
> > + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
> > + s_ifp = ifp;
> >
> > -static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt, uint8_t 
> flags,
> > - uint32_t seq, const void *buf, size_t len) {
> > - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> > - return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport, 
> pkt->tcp->sport, seq,
> > - mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
> > -}
> > + // Init RX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_rxdesc[i][0] = MG_BIT(31); // Own
> > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address 
> chained
> > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data 
> buffer
> > + s_rxdesc[i][3] =
> > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> > + }
> >
> > -static void settmout(struct mg_connection *c, uint8_t type) {
> > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS : 
> MIP_TCP_KEEPALIVE_MS;
> > - s->timer = ifp->now + n;
> > - s->ttype = type;
> > - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> > -}
> > + // Init TX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> > + s_txdesc[i][3] =
> > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> > + }
> >
> > -static struct mg_connection *accept_conn(struct mg_connection *lsn,
> > - struct pkt *pkt) {
> > - struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
> > - settmout(c, MIP_TTYPE_KEEPALIVE);
> > - c->rem.ip = pkt->ip->src;
> > - c->rem.port = pkt->tcp->sport;
> > - MG_DEBUG(
> > - ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip, mg_ntohs(c->rem.port)));
> > - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> > - c->is_accepted = 1;
> > - c->is_hexdumping = lsn->is_hexdumping;
> > - c->pfn = lsn->pfn;
> > - c->loc = lsn->loc;
> > - c->pfn_data = lsn->pfn_data;
> > - c->fn = lsn->fn;
> > - c->fn_data = lsn->fn_data;
> > - mg_call(c, MG_EV_OPEN, NULL);
> > - mg_call(c, MG_EV_ACCEPT, NULL);
> > - return c;
> > + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset
> > + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); // Wait 
> until done
> > +
> > + // Set MDC clock divider. If user told us the value, use it. 
> Otherwise, guess
> > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
> > + EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> > +
> > + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
> > + // hardware checksum. Therefore, descriptor size is 4, not 8
> > + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | 
> MG_BIT(23) | MG_BIT(25);
> > + EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
> > + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause
> > + // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all
> > + // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset internal PHY 
> (EPHY)
> > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set autonegotiation
> > + EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> > + EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX descriptors
> > + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE
> > + EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // 
> RE, TE, Duplex, Fast
> > + EMAC->EMACDMAOPMODE =
> > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF
> > + EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> > + EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> > + ((uint32_t) ifp->mac[2] << 16) |
> > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> > + // NOTE(scaprile) There are 3 additional slots for filtering, disabled 
> by
> > + // default. This also applies to the STM32 driver (at least for F7)
> > + return true;
> > }
> >
> > -long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
> > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
> > - if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len - 
> max_headers_len;
> > - if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
> > - mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
> > - s->seq += (uint32_t) len;
> > - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> > +static uint32_t s_txno;
> > +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // fail
> > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> > + ifp->nerr++;
> > + MG_ERROR(("No descriptors available"));
> > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> > + // EMAC->EMACDMARIS);
> > + len = 0; // fail
> > } else {
> > - return MG_IO_ERR;
> > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> > + s_txdesc[s_txno][0] =
> > + MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); // Chain,FS,LS,IC
> > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA take over
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > }
> > - return (long) len;
> > + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior TU/UNF
> > + EMAC->EMACTXPOLLD = 0; // and resume
> > + return len;
> > + (void) ifp;
> > }
> >
> > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - if (s->raw.len == 0) return MG_IO_WAIT;
> > - if (len > s->raw.len) len = s->raw.len;
> > - memcpy(buf, s->raw.buf, len);
> > - mg_iobuf_del(&s->raw, 0, len);
> > - MG_DEBUG(("%lu", len));
> > - return (long) len;
> > +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
> > + uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> > + bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
> > + // tmp = reg with flags set to the most likely situation: 100M 
> full-duplex
> > + // if(link is slow or half) set flags otherwise
> > + // reg = tmp
> > + uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); // 100M, 
> Full-duplex
> > + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M
> > + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex
> > + EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with this 
> register
> > + MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10,
> > + emaccfg & MG_BIT(11) ? "full" : "half"));
> > + }
> > + return up;
> > }
> >
> > -static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
> > - uint32_t seq = mg_ntohl(pkt->tcp->seq);
> > - s->raw.align = c->recv.align;
> > - if (pkt->tcp->flags & TH_FIN) {
> > - s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq = mg_htonl(pkt->tcp->ack);
> > - c->is_closing = 1;
> > - } else if (pkt->pay.len == 0) {
> > - // TODO(cpq): handle this peer's ACK
> > - } else if (seq != s->ack) {
> > - // TODO(cpq): peer sent us SEQ which we don't expect. Retransmit rather
> > - // than close this connection
> > - mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
> > - } else if (io->size - io->len < pkt->pay.len &&
> > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> > - mg_error(c, "oom");
> > - } else {
> > - // Copy TCP payload into the IO buffer. If the connection is plain 
> text, we
> > - // copy to c->recv. If the connection is TLS, this data is encrypted,
> > - // therefore we copy that encrypted data to the s->raw iobuffer 
> instead,
> > - // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
> > - // call back mg_io_recv() which grabs raw data from s->raw
> > - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
> > - io->len += pkt->pay.len;
> > -
> > - MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> > - // Advance ACK counter
> > - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> > -#if 0
> > - // Send ACK immediately
> > - MG_DEBUG((" imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> > - tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK, c->loc.port,
> > - c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> > -#else
> > - // if not already running, setup a timer to send an ACK later
> > - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> > -#endif
> > -
> > - if (c->is_tls) {
> > - // TLS connection. Make room for decrypted data in c->recv
> > - io = &c->recv;
> > - if (io->size - io->len < pkt->pay.len &&
> > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> > - mg_error(c, "oom");
> > - } else {
> > - // Decrypt data directly into c->recv
> > - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> > - if (n == MG_IO_ERR) {
> > - mg_error(c, "TLS recv error");
> > - } else if (n > 0) {
> > - // Decrypted successfully - trigger MG_EV_READ
> > - io->len += (size_t) n;
> > - mg_call(c, MG_EV_READ, &n);
> > - }
> > +void EMAC0_IRQHandler(void);
> > +static uint32_t s_rxno;
> > +void EMAC0_IRQHandler(void) {
> > + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop
> > + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag
> > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not 
> forever
> > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
> > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | 
> MG_BIT(9))) &&
> > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames
> > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> > + // EMAC->EMACDMARIS);
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> > }
> > - } else {
> > - // Plain text connection, data is already in c->recv, trigger 
> MG_EV_READ
> > - mg_call(c, MG_EV_READ, &pkt->pay.len);
> > + s_rxdesc[s_rxno][0] = MG_BIT(31);
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > }
> > }
> > + EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while processing
> > + EMAC->EMACRXPOLLD = 0; // and resume RX
> > }
> >
> > -static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
> > - struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> > - struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
> > -#if 0
> > - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) 
> pkt->pay.len));
> > +struct mg_tcpip_driver mg_tcpip_driver_tm4c = 
> {mg_tcpip_driver_tm4c_init,
> > + mg_tcpip_driver_tm4c_tx, NULL,
> > + mg_tcpip_driver_tm4c_up};
> > #endif
> > - if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN | 
> TH_ACK)) {
> > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
> > - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> > - c->is_connecting = 0; // Client connected
> > - settmout(c, MIP_TTYPE_KEEPALIVE);
> > - mg_call(c, MG_EV_CONNECT, NULL); // Let user know
> > - } else if (c != NULL && c->is_connecting) {
> > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > - } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> > - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> > - } else if (c != NULL) {
> > -#if 0
> > - MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
> > - 4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> > - 4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> > - mg_hexdump(pkt->pay.buf, pkt->pay.len);
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/w5500.c"
> > #endif
> > - s->tmiss = 0; // Reset missed keep-alive counter
> > - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
> > - settmout(c, MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is 
> pending
> > - read_conn(c, pkt); // Override timer with ACK timeout if needed
> > - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > - } else if (pkt->tcp->flags & TH_RST) {
> > - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> > - // ignore RST if not connected
> > - } else if (pkt->tcp->flags & TH_SYN) {
> > - // Use peer's source port as ISN, in order to recognise the handshake
> > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> > - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> > - } else if (pkt->tcp->flags & TH_FIN) {
> > - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> > - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
> > - accept_conn(c, pkt);
> > - } else if (!c->is_accepted ) { // no peer
> > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> > - } else {
> > - // MG_DEBUG(("dropped silently.."));
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && 
> MG_ENABLE_DRIVER_W5500
> > +
> > +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> > +
> > +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr,
> > + bool wr, void *buf, size_t len) {
> > + size_t i;
> > + uint8_t *p = (uint8_t *) buf;
> > + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> > + (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> > + s->begin(s->spi);
> > + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> > + for (i = 0; i < len; i++) {
> > + uint8_t r = s->txn(s->spi, p[i]);
> > + if (!wr) p[i] = r;
> > }
> > + s->end(s->spi);
> > }
> >
> > -static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
> > - // MG_DEBUG(("IP %d", (int) pkt->pay.len));
> > - if (pkt->ip->proto == 1) {
> > - pkt->icmp = (struct icmp *) (pkt->ip + 1);
> > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> > - mkpay(pkt, pkt->icmp + 1);
> > - rx_icmp(ifp, pkt);
> > - } else if (pkt->ip->proto == 17) {
> > - pkt->udp = (struct udp *) (pkt->ip + 1);
> > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
> > - mkpay(pkt, pkt->udp + 1);
> > - if (pkt->udp->dport == mg_htons(68)) {
> > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> > - mkpay(pkt, pkt->dhcp + 1);
> > - rx_dhcp_client(ifp, pkt);
> > - } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) 
> {
> > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> > - mkpay(pkt, pkt->dhcp + 1);
> > - rx_dhcp_server(ifp, pkt);
> > - } else {
> > - rx_udp(ifp, pkt);
> > +// clang-format off
> > +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
> > +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> > +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) 
> (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
> > +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
> > +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t 
> addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
> > +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, 
> uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, 
> sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> > +// clang-format on
> > +
> > +static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if 
> *ifp) {
> > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> > + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len
> > + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until it is 
> stable
> > + // printf("RSR: %d\n", (int) n);
> > + if (n > 0) {
> > + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
> > + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
> > + if (n <= len + 2 && n > 1) {
> > + r = (uint16_t) (n - 2);
> > + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
> > }
> > - } else if (pkt->ip->proto == 6) {
> > - pkt->tcp = (struct tcp *) (pkt->ip + 1);
> > - if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> > - mkpay(pkt, pkt->tcp + 1);
> > - uint16_t iplen = mg_ntohs(pkt->ip->len);
> > - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 
> 4U));
> > - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> > - rx_tcp(ifp, pkt);
> > + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance read 
> pointer
> > + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
> > + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
> > }
> > + return r;
> > }
> >
> > -static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
> > - // MG_DEBUG(("IP %d", (int) len));
> > - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> > - pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> > - mkpay(pkt, pkt->icmp + 1);
> > - rx_icmp(ifp, pkt);
> > - } else if (pkt->ip6->proto == 17) {
> > - pkt->udp = (struct udp *) (pkt->ip6 + 1);
> > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
> > - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
> > - // mg_htons(udp->dport)));
> > - mkpay(pkt, pkt->udp + 1);
> > +static size_t w5500_tx(const void *buf, size_t buflen,
> > + struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> > + uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
> > + while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
> > + ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
> > + w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
> > + w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance write 
> pointer
> > + w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
> > + for (i = 0; i < 40; i++) {
> > + uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
> > + if (ir == 0) continue;
> > + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, 
> ptr);
> > + w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
> > + if (ir & 8) len = 0; // Timeout. Report error
> > + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
> > }
> > + return len;
> > }
> >
> > -static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
> > - const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> > - struct pkt pkt;
> > - memset(&pkt, 0, sizeof(pkt));
> > - pkt.raw.ptr = (char *) buf;
> > - pkt.raw.len = len;
> > - pkt.eth = (struct eth *) buf;
> > - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
> > - if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> > - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
> > - // Not for us. Drop silently
> > - } else if (pkt.eth->type == mg_htons(0x806)) {
> > - pkt.arp = (struct arp *) (pkt.eth + 1);
> > - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // 
> Truncated
> > - rx_arp(ifp, &pkt);
> > - } else if (pkt.eth->type == mg_htons(0x86dd)) {
> > - pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // 
> Truncated
> > - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
> > - mkpay(&pkt, pkt.ip6 + 1);
> > - rx_ip6(ifp, &pkt);
> > - } else if (pkt.eth->type == mg_htons(0x800)) {
> > - pkt.ip = (struct ip *) (pkt.eth + 1);
> > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // 
> Truncated
> > - // Truncate frame to what IP header tells us
> > - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) 
> {
> > - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> > - }
> > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // 
> Truncated
> > - if ((pkt.ip->ver >> 4) != 4) return; // Not IP
> > - mkpay(&pkt, pkt.ip + 1);
> > - rx_ip(ifp, &pkt);
> > - } else {
> > - MG_DEBUG((" Unknown eth type %x", mg_htons(pkt.eth->type)));
> > +static bool w5500_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> > + s->end(s->spi);
> > + w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
> > + w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
> > + w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
> > + // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
> > + w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
> > + w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
> > + w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
> > + w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
> > + return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
> > +}
> > +
> > +static bool w5500_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data;
> > + uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> > + return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> > +}
> > +
> > +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, 
> w5500_rx,
> > + w5500_up};
> > +#endif
> > +
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/xmc.c"
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && 
> MG_ENABLE_DRIVER_XMC
> > +
> > +struct ETH_GLOBAL_TypeDef {
> > + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH,
> > + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, 
> VERSION,
> > + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2],
> > + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW,
> > + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, 
> MAC_ADDRESS2_LOW,
> > + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
> > + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, 
> MMC_RECEIVE_INTERRUPT_MASK,
> > + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
> > + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
> > + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30],
> > + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
> > + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
> > + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
> > + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
> > + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
> > + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
> > + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
> > + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
> > + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
> > + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
> > + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
> > + CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
> > + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
> > +};
> > +
> > +#undef ETH0
> > +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
> > +
> > +#define ETH_PKT_SIZE 1536 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 4 // Descriptor size (words)
> > +
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > +static uint8_t s_txno; // Current TX descriptor
> > +static uint8_t s_rxno; // Current RX descriptor
> > +
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> > +
> > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> > + ((uint32_t)addr << 11) |
> > + ((uint32_t)reg << 6) | 1;
> > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> > + return (uint16_t)(ETH0->GMII_DATA & 0xffff);
> > +}
> > +
> > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + ETH0->GMII_DATA = val;
> > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> > + ((uint32_t)addr << 11) |
> > + ((uint32_t)reg << 6) | 3;
> > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> > +}
> > +
> > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data *d) {
> > + if (d->mdc_cr == -1) {
> > + // assume ETH clock is 60MHz by default
> > + // then according to 13.2.8.1, we need to set value 3
> > + return 3;
> > }
> > +
> > + return d->mdc_cr;
> > }
> >
> > -static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
> > - if (ifp == NULL || ifp->driver == NULL) return;
> > - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, 
> uptime_ms);
> > - ifp->now = uptime_ms;
> > +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_xmc_data *d =
> > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> > + s_ifp = ifp;
> >
> > - // Handle physical interface up/down status
> > - if (expired_1000ms && ifp->driver->up) {
> > - bool up = ifp->driver->up(ifp);
> > - bool current = ifp->state != MIP_STATE_DOWN;
> > - if (up != current) {
> > - ifp->state = up == false ? MIP_STATE_DOWN
> > - : ifp->enable_dhcp_client ? MIP_STATE_UP
> > - : MIP_STATE_READY;
> > - if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> > - onstatechange(ifp);
> > + // reset MAC
> > + ETH0->BUS_MODE |= 1;
> > + while (ETH0->BUS_MODE & 1) (void) 0;
> > +
> > + // set clock rate
> > + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
> > +
> > + // init phy
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> > +
> > + // configure MAC: DO, DM, FES, TC
> > + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14) | 
> MG_BIT(24);
> > +
> > + // set the MAC address
> > + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
> > + ETH0->MAC_ADDRESS0_LOW =
> > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> > +
> > + // Configure the receive filter
> > + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
> > + // Disable flow control
> > + ETH0->FLOW_CONTROL = 0;
> > + // Enable store and forward mode
> > + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
> > +
> > + // Configure DMA bus mode (AAL, USP, RPBL, PBL)
> > + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8);
> > +
> > + // init RX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
> > + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
> > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
> > + if (i == ETH_DESC_CNT - 1) {
> > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
> > + } else {
> > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
> > }
> > }
> > - if (ifp->state == MIP_STATE_DOWN) return;
> > - // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
> > + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0];
> >
> > - if (ifp->ip == 0 && expired_1000ms) {
> > - tx_dhcp_discover(ifp); // If IP not configured, send DHCP
> > - } else if (ifp->enable_dhcp_client == false && expired_1000ms && 
> ifp->gw &&
> > - arp_cache_find(ifp, ifp->gw) == NULL) {
> > - arp_ask(ifp, ifp->gw); // If GW's MAC address in not in ARP cache
> > + // init TX descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
> > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
> > + if (i == ETH_DESC_CNT - 1) {
> > + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
> > + } else {
> > + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
> > + }
> > }
> > + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_txdesc[0][0];
> >
> > - // Read data from the network
> > - size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len, ifp);
> > - mip_rx(ifp, (void *) ifp->rx.ptr, len);
> > - qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
> > + // Clear interrupts
> > + ETH0->STATUS = 0xFFFFFFFF;
> >
> > - // Process timeouts
> > - for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c = 
> c->next) {
> > - if (c->is_udp || c->is_listening) continue;
> > - if (c->is_connecting || c->is_resolving) continue;
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - if (uptime_ms > s->timer) {
> > - if (s->ttype == MIP_TTYPE_ACK) {
> > - MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
> > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> > - mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> > - } else {
> > - if (s->tmiss++ > 2) {
> > - mg_error(c, "keepalive");
> > - } else {
> > - MG_DEBUG(("%lu keepalive", c->id));
> > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> > - mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
> > - }
> > - }
> > - settmout(c, MIP_TTYPE_KEEPALIVE);
> > - }
> > - }
> > -#ifdef MIP_QPROFILE
> > - qp_log();
> > -#endif
> > + // Disable MAC interrupts
> > + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
> > + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> > + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> > + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
> > +
> > + //Enable interrupts (NIE, RIE, TIE)
> > + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
> > +
> > + // Enable MAC transmission and reception (TE, RE)
> > + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
> > + // Enable DMA transmission and reception (ST, SR)
> > + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
> > + return true;
> > }
> >
> > -// This function executes in interrupt context, thus it should copy data
> > -// somewhere fast. Note that newlib's malloc is not thread safe, thus 
> use
> > -// our lock-free queue with preallocated buffer to copy data and return 
> asap
> > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
> > - if (q_write(&ifp->queue, buf, len)) {
> > - qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
> > +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // Frame is too big
> > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> > + ifp->nerr++;
> > + MG_ERROR(("No free descriptors"));
> > + len = 0; // All descriptors are busy, fail
> > } else {
> > - ifp->dropped++;
> > - qp_mark(QP_FRAMEDROPPED, ifp->dropped);
> > - MG_ERROR(("dropped %d", (int) len));
> > + memcpy(s_txbuf[s_txno], buf, len);
> > + s_txdesc[s_txno][1] = len;
> > + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
> > + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) | 
> MG_BIT(20);
> > + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control to DMA
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > }
> > -}
> >
> > -size_t mip_qread(void *buf, struct mip_if *ifp) {
> > - size_t len = q_read(&ifp->queue, buf);
> > - qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
> > + // Resume processing
> > + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
> > + ETH0->TRANSMIT_POLL_DEMAND = 0;
> > return len;
> > }
> >
> > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
> > - return mip_qread((void *) ifp->rx.ptr, ifp);
> > - (void) len, (void) buf;
> > -}
> > +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_xmc_data *d =
> > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 
> 100,
> > + full_duplex ? "full" : "half"));
> > + }
> > + return up;
> > +}
> > +
> > +void ETH0_IRQHandler(void);
> > +void ETH0_IRQHandler(void) {
> > + uint32_t irq_status = ETH0->STATUS;
> > +
> > + // check if a frame was received
> > + if (irq_status & MG_BIT(6)) {
> > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> > + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
> > + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> > + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to DMA
> > + // Resume processing
> > + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
> > + ETH0->RECEIVE_POLL_DEMAND = 0;
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > + }
> > + }
> > + ETH0->STATUS = MG_BIT(6);
> > + }
> >
> > -void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
> > - if (ifp->driver->init && !ifp->driver->init(ifp)) {
> > - MG_ERROR(("driver init failed"));
> > - } else {
> > - size_t maxpktsize = 1540;
> > - ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len = maxpktsize;
> > - ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len = maxpktsize;
> > - if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1, 
> ifp->queue.len);
> > - ifp->timer_1000ms = mg_millis();
> > - arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
> > - mgr->priv = ifp;
> > - ifp->mgr = mgr;
> > - mgr->extraconnsize = sizeof(struct connstate);
> > - if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> > -#ifdef MIP_QPROFILE
> > - qp_init();
> > -#endif
> > + // clear Successful transmission interrupt
> > + if (irq_status & 1) {
> > + ETH0->STATUS = 1;
> > }
> > -}
> >
> > -void mip_free(struct mip_if *ifp) {
> > - free((char *) ifp->rx.ptr);
> > - free((char *) ifp->tx.ptr);
> > + // clear normal interrupt
> > + if (irq_status & MG_BIT(16)) {
> > + ETH0->STATUS = MG_BIT(16);
> > + }
> > }
> >
> > -int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d, bool 
> udp) {
> > - (void) m, (void) fn, (void) d, (void) udp;
> > - MG_ERROR(("Not implemented"));
> > - return -1;
> > -}
> > +struct mg_tcpip_driver mg_tcpip_driver_xmc = {
> > + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
> > + mg_tcpip_driver_xmc_up};
> > +#endif
> >
> > -#if 0
> > -static uint16_t mkeport(void) {
> > - uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
> > - mg_random(&a, sizeof(a));
> > - c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
> > - return c;
> > -}
> > +#ifdef MG_ENABLE_LINES
> > +#line 1 "src/drivers/xmc7.c"
> > #endif
> >
> > -void mg_connect_resolved(struct mg_connection *c) {
> > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> > - c->is_resolving = 0;
> > - if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport = MIP_ETHEMERAL_PORT;
> > - c->loc.ip = ifp->ip;
> > - c->loc.port = mg_htons(ifp->eport++);
> > - MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip, 
> mg_ntohs(c->loc.port),
> > - 4, &c->rem.ip, mg_ntohs(c->rem.port)));
> > - mg_call(c, MG_EV_RESOLVE, NULL);
> > - if (c->is_udp) {
> > - mg_call(c, MG_EV_CONNECT, NULL);
> > - } else {
> > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> > - tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, 
> 0);
> > - c->is_connecting = 1;
> > - }
> > -}
> >
> > -bool mg_open_listener(struct mg_connection *c, const char *url) {
> > - c->loc.port = mg_htons(mg_url_port(url));
> > - return true;
> > -}
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && 
> MG_ENABLE_DRIVER_XMC7
> > +
> > +struct ETH_Type {
> > + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
> > + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
> > + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
> > + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT, 
> PAUSE_TIME,
> > + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU, JUMBO_MAX_LENGTH,
> > + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
> > + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM, HASH_TOP,
> > + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
> > + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
> > + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
> > + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
> > + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
> > + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
> > + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
> > + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
> > + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
> > + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
> > + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
> > + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
> > + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
> > + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
> > + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65, FRAMES_RXED_128,
> > + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024, FRAMES_RXED_1519,
> > + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
> > + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS, 
> RX_RESOURCE_ERRORS,
> > + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
> > + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC, 
> TSU_TIMER_MSB_SEC,
> > + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC, TSU_TIMER_SEC,
> > + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
> > + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
> > + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
> > + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
> > + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS, 
> RESERVED6[8],
> > + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3, RESERVED7,
> > + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
> > + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4, DESIGNCFG_DEBUG5,
> > + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8, DESIGNCFG_DEBUG9,
> > + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
> > + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
> > + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS, 
> RESERVED11,
> > + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
> > + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
> > + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
> > + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3, RESERVED16[3],
> > + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A, CBS_IDLESLOPE_Q_B,
> > + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL, 
> UPPER_RX_Q_BASE_ADDR,
> > + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
> > + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
> > +};
> >
> > -static void write_conn(struct mg_connection *c) {
> > - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> > - : mg_io_send(c, c->send.buf, c->send.len);
> > - if (len > 0) {
> > - mg_iobuf_del(&c->send, 0, (size_t) len);
> > - mg_call(c, MG_EV_WRITE, &len);
> > - }
> > +#define ETH0 ((struct ETH_Type *) 0x40490000)
> > +
> > +#define ETH_PKT_SIZE 1536 // Max frame size
> > +#define ETH_DESC_CNT 4 // Descriptors count
> > +#define ETH_DS 2 // Descriptor size (words)
> > +
> > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> > +static uint8_t s_txno; // Current TX descriptor
> > +static uint8_t s_rxno; // Current RX descriptor
> > +
> > +static struct mg_tcpip_if *s_ifp; // MIP interface
> > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> > +
> > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> > + // WRITE1, READ OPERATION, PHY, REG, WRITE10
> > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf) << 24) |
> > + ((reg & 0x1f) << 18) | MG_BIT(17);
> > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
> > + return ETH0->PHY_MANAGEMENT & 0xffff;
> > }
> >
> > -static void close_conn(struct mg_connection *c) {
> > - struct connstate *s = (struct connstate *) (c + 1);
> > - mg_iobuf_free(&s->raw); // For TLS connections, release raw data
> > - if (c->is_udp == false && c->is_listening == false) { // For TCP conns,
> > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv; // send TCP FIN
> > - tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
> > - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> > - }
> > - mg_close_conn(c);
> > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
> > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf) << 24) |
> > + ((reg & 0x1f) << 18) | MG_BIT(17) | val;
> > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
> > }
> >
> > -static bool can_write(struct mg_connection *c) {
> > - return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 
> 0 &&
> > - c->is_tls_hs == 0;
> > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) {
> > + // see ETH0 -> NETWORK_CONFIG register
> > + (void) d;
> > + return 3;
> > }
> >
> > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> > - struct mg_connection *c, *tmp;
> > - uint64_t now = mg_millis();
> > - mip_poll((struct mip_if *) mgr->priv, now);
> > - mg_timer_poll(&mgr->timers, now);
> > - for (c = mgr->conns; c != NULL; c = tmp) {
> > - tmp = c->next;
> > - mg_call(c, MG_EV_POLL, &now);
> > - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> > - if (c->is_tls_hs) mg_tls_handshake(c);
> > - if (can_write(c)) write_conn(c);
> > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> > - if (c->is_closing) close_conn(c);
> > +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_xmc7_data *d =
> > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> > + s_ifp = ifp;
> > +
> > + // enable controller, set RGMII mode
> > + ETH0->CTL = MG_BIT(31) | 2;
> > +
> > + uint32_t cr = get_clock_rate(d);
> > + // set NSP change, ignore RX FCS, data bus width, clock rate
> > + // frame length 1536, full duplex, speed
> > + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
> > + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
> > + MG_BIT(1) | MG_BIT(0);
> > +
> > + // config DMA settings: Force TX burst, Discard on Error, set RX 
> buffer size
> > + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
> > + ETH0->DMA_CONFIG =
> > + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8) | 4;
> > +
> > + // initialize descriptors
> > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
> > + if (i == ETH_DESC_CNT - 1) {
> > + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor
> > + }
> > +
> > + s_txdesc[i][0] = (uint32_t) s_txbuf[i];
> > + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor
> > + if (i == ETH_DESC_CNT - 1) {
> > + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor
> > + }
> > }
> > - (void) ms;
> > + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
> > + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
> > +
> > + // disable other queues
> > + ETH0->TRANSMIT_Q2_PTR = 1;
> > + ETH0->TRANSMIT_Q1_PTR = 1;
> > + ETH0->RECEIVE_Q2_PTR = 1;
> > + ETH0->RECEIVE_Q1_PTR = 1;
> > +
> > + // enable interrupts (TX and RX complete)
> > + ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
> > +
> > + // set MAC address
> > + ETH0->SPEC_ADD1_BOTTOM =
> > + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 | ifp->mac[0];
> > + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
> > +
> > + // enable MDIO, TX, RX
> > + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
> > +
> > + // start transmission
> > + ETH0->NETWORK_CONTROL |= MG_BIT(9);
> > +
> > + // init phy
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> > +
> > + (void) d;
> > + return true;
> > }
> >
> > -bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
> > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> > - bool res = false;
> > - if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
> > - mg_error(c, "net down");
> > - } else if (c->is_udp) {
> > - tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len);
> > - res = true;
> > +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
> > + struct mg_tcpip_if *ifp) {
> > + if (len > sizeof(s_txbuf[s_txno])) {
> > + MG_ERROR(("Frame too big, %ld", (long) len));
> > + len = 0; // Frame is too big
> > + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
> > + ifp->nerr++;
> > + MG_ERROR(("No free descriptors"));
> > + len = 0; // All descriptors are busy, fail
> > } else {
> > - res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> > - }
> > - return res;
> > -}
> > + memcpy(s_txbuf[s_txno], buf, len);
> > + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30) : 0) |
> > + MG_BIT(15) | len; // Last buffer and length
> >
> > -#ifdef MIP_QPROFILE
> > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
> > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> > + }
> >
> > -#pragma pack(push, 1)
> > -struct qpentry {
> > - uint32_t timestamp;
> > - uint16_t type;
> > - uint16_t len;
> > -};
> > -#pragma pack(pop)
> > + MG_DSB();
> > + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
> > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
> >
> > -static struct queue qp;
> > + return len;
> > +}
> >
> > -// This is called from IRQ and main contexts; two producers, single 
> consumer
> > -// TODO(scaprile): avoid concurrency issues (2 queues ?)
> > -void qp_mark(unsigned int type, int len) {
> > - static bool ovf = false;
> > - static uint16_t irq_ctr = 0, drop_ctr = 0;
> > - struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
> > - .type = (uint16_t) type,
> > - .len = (uint16_t) len};
> > - if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
> > - if (ovf) {
> > - e.type = (uint16_t) QP_QUEUEOVF;
> > - e.len = drop_ctr;
> > +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
> > + struct mg_tcpip_driver_xmc7_data *d =
> > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> > + uint8_t speed = MG_PHY_SPEED_10M;
> > + bool up = false, full_duplex = false;
> > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just 
> went up
> > + if (speed == MG_PHY_SPEED_1000M) {
> > + ETH0->NETWORK_CONFIG |= MG_BIT(10);
> > + }
> > + MG_DEBUG(("Link is %uM %s-duplex",
> > + speed == MG_PHY_SPEED_10M ? 10 :
> > + (speed == MG_PHY_SPEED_100M ? 100 : 1000),
> > + full_duplex ? "full" : "half"));
> > }
> > - ovf = !q_write(&qp, &e, sizeof(e));
> > + (void) d;
> > + return up;
> > }
> >
> > -void qp_log(void) {
> > - struct qpentry e;
> > - const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP", 
> "OVFL"};
> > - for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
> > - MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
> > +void ETH_IRQHandler(void) {
> > + uint32_t irq_status = ETH0->INT_STATUS;
> > + if (irq_status & MG_BIT(1)) {
> > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> > + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
> > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> > + //MG_INFO(("Receive complete: %ld bytes", len));
> > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control to DMA
> > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> > + }
> > + }
> > }
> > -}
> >
> > -void qp_init(void) {
> > - qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
> > - qp.buf = calloc(1, qp.len); // THERE IS NO FREE
> > + ETH0->INT_STATUS = irq_status;
> > }
> > -#endif // MIP_QPROFILE
> >
> > -#endif // MG_ENABLE_MIP
> > +struct mg_tcpip_driver mg_tcpip_driver_xmc7 = 
> {mg_tcpip_driver_xmc7_init,
> > + mg_tcpip_driver_xmc7_tx, NULL,
> > + mg_tcpip_driver_xmc7_up};
> > +#endif
> > diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
> > index 350aad9c..3a439a6e 100644
> > --- a/mongoose/mongoose.h
> > +++ b/mongoose/mongoose.h
> > @@ -1,5 +1,5 @@
> > // Copyright (c) 2004-2013 Sergey Lyubka
> > -// Copyright (c) 2013-2022 Cesanta Software Limited
> > +// Copyright (c) 2013-2024 Cesanta Software Limited
> > // All rights reserved
> > //
> > // This software is dual-licensed: you can redistribute it and/or modify
> > @@ -15,58 +15,48 @@
> > // Alternatively, you can license this software under a commercial
> > // license, as set out in https://www.mongoose.ws/licensing/
> > //
> > -// SPDX-License-Identifier: GPL-2.0-only
> > +// SPDX-License-Identifier: GPL-2.0-only or commercial
> >
> > #ifndef MONGOOSE_H
> > #define MONGOOSE_H
> >
> > -#define MG_VERSION "7.8"
> > +#define MG_VERSION "7.14"
> >
> > #ifdef __cplusplus
> > extern "C" {
> > #endif
> >
> >
> > -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
> > -#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
> > -#define MG_ARCH_WIN32 2 // Windows
> > -#define MG_ARCH_ESP32 3 // ESP32
> > -#define MG_ARCH_ESP8266 4 // ESP8266
> > -#define MG_ARCH_FREERTOS 5 // FreeRTOS
> > -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
> > -#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
> > -#define MG_ARCH_NEWLIB 8 // Bare metal ARM
> > -#define MG_ARCH_RTX 9 // Keil MDK RTX
> > -#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
> > -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
> > +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h
> > +#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
> > +#define MG_ARCH_WIN32 2 // Windows
> > +#define MG_ARCH_ESP32 3 // ESP32
> > +#define MG_ARCH_ESP8266 4 // ESP8266
> > +#define MG_ARCH_FREERTOS 5 // FreeRTOS
> > +#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
> > +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
> > +#define MG_ARCH_NEWLIB 8 // Bare metal ARM
> > +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
> > +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
> > +#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
> > +#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
> > +#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, 
> FreeRTOS)
> > +#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
> >
> > #if !defined(MG_ARCH)
> > #if defined(__unix__) || defined(__APPLE__)
> > #define MG_ARCH MG_ARCH_UNIX
> > #elif defined(_WIN32)
> > #define MG_ARCH MG_ARCH_WIN32
> > -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
> > -#define MG_ARCH MG_ARCH_ESP8266
> > -#elif defined(__ZEPHYR__)
> > -#define MG_ARCH MG_ARCH_ZEPHYR
> > -#elif defined(ESP_PLATFORM)
> > -#define MG_ARCH MG_ARCH_ESP32
> > -#elif defined(FREERTOS_IP_H)
> > -#define MG_ARCH MG_ARCH_FREERTOS
> > -#define MG_ENABLE_FREERTOS_TCP 1
> > -#elif defined(AZURE_RTOS_THREADX)
> > -#define MG_ARCH MG_ARCH_AZURERTOS
> > -#elif defined(PICO_TARGET_NAME)
> > -#define MG_ARCH MG_ARCH_RP2040
> > #endif
> > #endif // !defined(MG_ARCH)
> >
> > #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
> > -#include "mongoose_custom.h" // keep this include
> > +#include "mongoose_config.h" // keep this include
> > #endif
> >
> > #if !defined(MG_ARCH)
> > -#error "MG_ARCH is not specified and we couldn't guess it. Set -D 
> MG_ARCH=..."
> > +#error "MG_ARCH is not specified and we couldn't guess it. Define 
> MG_ARCH=... in your compiler"
> > #endif
> >
> > // http://esr.ibiblio.org/?p=5095
> > @@ -130,7 +120,8 @@ extern "C" {
> > #include <sys/types.h>
> > #include <time.h>
> >
> > -#include <esp_timer.h>
> > +#include <esp_ota_ops.h> // Use angle brackets to avoid
> > +#include <esp_timer.h> // amalgamation ditching them
> >
> > #define MG_PATH_MAX 128
> >
> > @@ -166,15 +157,24 @@ extern "C" {
> > #if MG_ARCH == MG_ARCH_FREERTOS
> >
> > #include <ctype.h>
> > -// #include <errno.h> // Cannot include errno - might conflict with 
> lwip!
> > +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
> > +#include <errno.h>
> > +#endif
> > #include <stdarg.h>
> > #include <stdbool.h>
> > #include <stddef.h>
> > #include <stdint.h>
> > #include <stdio.h>
> > -#include <stdlib.h> // rand(), strtol(), atoi()
> > +#include <stdlib.h> // rand(), strtol(), atoi()
> > #include <string.h>
> > +#if defined(__ARMCC_VERSION)
> > +#define mode_t size_t
> > +#include <alloca.h>
> > +#include <time.h>
> > +#elif defined(__CCRH__)
> > +#else
> > #include <sys/stat.h>
> > +#endif
> >
> > #include <FreeRTOS.h>
> > #include <task.h>
> > @@ -186,7 +186,7 @@ extern "C" {
> > #define calloc(a, b) mg_calloc(a, b)
> > #define free(a) vPortFree(a)
> > #define malloc(a) pvPortMalloc(a)
> > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> > +#define strdup(s) mg_mprintf("%s", s)
> >
> > // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
> > static inline void *mg_calloc(size_t cnt, size_t size) {
> > @@ -240,9 +240,34 @@ static inline int mg_mkdir(const char *path, mode_t 
> mode) {
> > #include <pico/stdlib.h>
> > int mkdir(const char *, mode_t);
> > #endif
> > -
> > -
> > -#if MG_ARCH == MG_ARCH_RTX
> > +
> >
> > +
> >
> > +#if MG_ARCH == MG_ARCH_RTTHREAD
> >
> > +
> >
> > +#include <rtthread.h>
> >
> > +#include <ctype.h>
> >
> > +#include <errno.h>
> >
> > +#include <fcntl.h>
> >
> > +#include <sys/socket.h>
> >
> > +#include <sys/select.h>
> >
> > +#include <stdarg.h>
> >
> > +#include <stdbool.h>
> >
> > +#include <stdint.h>
> >
> > +#include <stdio.h>
> >
> > +#include <stdlib.h>
> >
> > +#include <string.h>
> >
> > +#include <sys/types.h>
> >
> > +#include <time.h>
> >
> > +
> >
> > +#ifndef MG_IO_SIZE
> >
> > +#define MG_IO_SIZE 1460
> >
> > +#endif
> >
> > +
> >
> > +#endif // MG_ARCH == MG_ARCH_RTTHREAD
> >
> > +
> > +
> > +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
> > + MG_ARCH == MG_ARCH_CMSIS_RTOS2
> >
> > #include <ctype.h>
> > #include <errno.h>
> > @@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
> > #include <stdint.h>
> > #include <stdio.h>
> > #include <stdlib.h>
> > +#include <alloca.h>
> > #include <string.h>
> > #include <time.h>
> > +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
> > +#include "cmsis_os.h" // keep this include
> > +// https://developer.arm.com/documentation/ka003821/latest
> > +extern uint32_t rt_time_get(void);
> > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> > +#include "cmsis_os2.h" // keep this include
> > +#endif
> > +
> > +#define strdup(s) mg_mprintf("%s", s)
> > +
> > +#if defined(__ARMCC_VERSION)
> > +#define mode_t size_t
> > +#define mkdir(a, b) mg_mkdir(a, b)
> > +static inline int mg_mkdir(const char *path, mode_t mode) {
> > + (void) path, (void) mode;
> > + return -1;
> > +}
> > +#endif
> >
> > -#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || 
> !MG_ENABLE_LWIP)
> > +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) 
> && \
> > + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) 
> && \
> > + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
> > #define MG_ENABLE_RL 1
> > +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> > +#endif
> > #endif
> >
> > #endif
> > @@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
> > #define MG_INVALID_SOCKET INVALID_SOCKET
> > #define MG_SOCKET_TYPE SOCKET
> > typedef unsigned long nfds_t;
> > -#define MG_SOCKET_ERRNO WSAGetLastError()
> > #if defined(_MSC_VER)
> > #pragma comment(lib, "ws2_32.lib")
> > #ifndef alloca
> > @@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
> > #endif
> > #endif
> > #define poll(a, b, c) WSAPoll((a), (b), (c))
> > -#ifndef SO_EXCLUSIVEADDRUSE
> > -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
> > -#endif
> > #define closesocket(x) closesocket(x)
> >
> > typedef int socklen_t;
> > @@ -423,16 +468,24 @@ typedef int socklen_t;
> > #define MG_PATH_MAX FILENAME_MAX
> > #endif
> >
> > -#ifndef EINPROGRESS
> > -#define EINPROGRESS WSAEINPROGRESS
> > -#endif
> > -#ifndef EWOULDBLOCK
> > -#define EWOULDBLOCK WSAEWOULDBLOCK
> > +#ifndef SO_EXCLUSIVEADDRUSE
> > +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
> > #endif
> >
> > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0)
> > +
> > +#define MG_SOCK_PENDING(errcode) \
> > + (((errcode) < 0) && \
> > + (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS 
> || \
> > + WSAGetLastError() == WSAEWOULDBLOCK))
> > +
> > +#define MG_SOCK_RESET(errcode) \
> > + (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
> > +
> > #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
> > -#define sleep(x) Sleep(x)
> > +#define sleep(x) Sleep((x) *1000)
> > #define mkdir(a, b) _mkdir(a)
> > +#define timegm(x) _mkgmtime(x)
> >
> > #ifndef S_ISDIR
> > #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
> > @@ -442,6 +495,10 @@ typedef int socklen_t;
> > #define MG_ENABLE_DIRLIST 1
> > #endif
> >
> > +#ifndef SIGPIPE
> > +#define SIGPIPE 0
> > +#endif
> > +
> > #endif
> >
> >
> > @@ -451,8 +508,9 @@ typedef int socklen_t;
> >
> > #include <ctype.h>
> > #include <errno.h>
> > -#include <fcntl.h>
> > #include <zephyr/net/socket.h>
> > +#include <zephyr/posix/fcntl.h>
> > +#include <zephyr/posix/sys/select.h>
> > #include <stdarg.h>
> > #include <stdbool.h>
> > #include <stdint.h>
> > @@ -464,11 +522,18 @@ typedef int socklen_t;
> >
> > #define MG_PUTCHAR(x) printk("%c", x)
> > #ifndef strdup
> > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> > +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
> > #endif
> > #define strerror(x) zsock_gai_strerror(x)
> > +
> > +#ifndef FD_CLOEXEC
> > #define FD_CLOEXEC 0
> > +#endif
> > +
> > +#ifndef F_SETFD
> > #define F_SETFD 0
> > +#endif
> > +
> > #define MG_ENABLE_SSI 0
> >
> > int rand(void);
> > @@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
> >
> > #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
> >
> > -#include <ctype.h>
> > -#include <errno.h>
> > #include <limits.h>
> > -#include <stdarg.h>
> > -#include <stdbool.h>
> > -#include <stddef.h>
> > -#include <stdio.h>
> > -#include <stdlib.h>
> > -#include <string.h>
> > -#include <sys/stat.h>
> > -#include <time.h>
> > -
> > -#include <FreeRTOS.h>
> > #include <list.h>
> > -#include <task.h>
> >
> > #include <FreeRTOS_IP.h>
> > #include <FreeRTOS_Sockets.h>
> > +#include <FreeRTOS_errno_TCP.h> // contents to be moved and file 
> removed, some day
> >
> > #define MG_SOCKET_TYPE Socket_t
> > #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
> > @@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
> > #define SO_ERROR 0
> > #define SOL_SOCKET 0
> > #define SO_REUSEADDR 0
> > +
> > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> > +
> > +#define MG_SOCK_PENDING(errcode) \
> > + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
> > + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \
> > + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
> > + (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
> > +
> > +#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
> > +
> > +// actually only if optional timeout is enabled
> > +#define MG_SOCK_INTR(fd) (fd == NULL)
> > +
> > #define sockaddr_in freertos_sockaddr
> > #define sockaddr freertos_sockaddr
> > #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
> > @@ -543,8 +610,17 @@ static inline int mg_getpeername(MG_SOCKET_TYPE fd, 
> void *buf, socklen_t *len) {
> >
> >
> > #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
> > -#if defined(__GNUC__)
> > +
> > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
> > #include <sys/stat.h>
> > +#endif
> > +
> > +struct timeval;
> > +
> > +#include <lwip/sockets.h>
> > +
> > +#if !LWIP_TIMEVAL_PRIVATE
> > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both
> > #include <sys/time.h>
> > #else
> > struct timeval {
> > @@ -552,8 +628,7 @@ struct timeval {
> > long tv_usec;
> > };
> > #endif
> > -
> > -#include <lwip/sockets.h>
> > +#endif
> >
> > #if LWIP_SOCKET != 1
> > // Sockets support disabled in LWIP by default
> > @@ -565,16 +640,25 @@ struct timeval {
> > #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
> > #include <rl_net.h>
> >
> > -#define MG_ENABLE_CUSTOM_MILLIS 1
> > #define closesocket(x) closesocket(x)
> > -#define mkdir(a, b) (-1)
> > -#define EWOULDBLOCK BSD_EWOULDBLOCK
> > -#define EAGAIN BSD_EWOULDBLOCK
> > -#define EINPROGRESS BSD_EWOULDBLOCK
> > -#define EINTR BSD_EWOULDBLOCK
> > -#define ECONNRESET BSD_ECONNRESET
> > -#define EPIPE BSD_ECONNRESET
> > +
> > #define TCP_NODELAY SO_KEEPALIVE
> > +
> > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> > +
> > +#define MG_SOCK_PENDING(errcode) \
> > + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
> > + (errcode) == BSD_EINPROGRESS)
> > +
> > +#define MG_SOCK_RESET(errcode) \
> > + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
> > +
> > +// In blocking mode, which is enabled by default, accept() waits for a
> > +// connection request. In non blocking mode, you must call accept()
> > +// again if the error code BSD_EWOULDBLOCK is returned.
> > +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
> > +
> > +#define socklen_t int
> > #endif
> >
> >
> > @@ -582,8 +666,12 @@ struct timeval {
> > #define MG_ENABLE_LOG 1
> > #endif
> >
> > -#ifndef MG_ENABLE_MIP
> > -#define MG_ENABLE_MIP 0 // Mongoose built-in network stack
> > +#ifndef MG_ENABLE_CUSTOM_LOG
> > +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
> > +#endif
> > +
> > +#ifndef MG_ENABLE_TCPIP
> > +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack
> > #endif
> >
> > #ifndef MG_ENABLE_LWIP
> > @@ -599,7 +687,7 @@ struct timeval {
> > #endif
> >
> > #ifndef MG_ENABLE_SOCKET
> > -#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
> > +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
> > #endif
> >
> > #ifndef MG_ENABLE_POLL
> > @@ -614,18 +702,6 @@ struct timeval {
> > #define MG_ENABLE_FATFS 0
> > #endif
> >
> > -#ifndef MG_ENABLE_MBEDTLS
> > -#define MG_ENABLE_MBEDTLS 0
> > -#endif
> > -
> > -#ifndef MG_ENABLE_OPENSSL
> > -#define MG_ENABLE_OPENSSL 0
> > -#endif
> > -
> > -#ifndef MG_ENABLE_CUSTOM_TLS
> > -#define MG_ENABLE_CUSTOM_TLS 0
> > -#endif
> > -
> > #ifndef MG_ENABLE_SSI
> > #define MG_ENABLE_SSI 0
> > #endif
> > @@ -634,6 +710,10 @@ struct timeval {
> > #define MG_ENABLE_IPV6 0
> > #endif
> >
> > +#ifndef MG_IPV6_V6ONLY
> > +#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4 address
> > +#endif
> > +
> > #ifndef MG_ENABLE_MD5
> > #define MG_ENABLE_MD5 1
> > #endif
> > @@ -659,12 +739,16 @@ struct timeval {
> > #define MG_ENABLE_PACKED_FS 0
> > #endif
> >
> > +#ifndef MG_ENABLE_ASSERT
> > +#define MG_ENABLE_ASSERT 0
> > +#endif
> > +
> > #ifndef MG_IO_SIZE
> > #define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer growth
> > #endif
> >
> > #ifndef MG_MAX_RECV_SIZE
> > -#define MG_MAX_RECV_SIZE (3 * 1024 * 1024) // Maximum recv IO buffer 
> size
> > +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv IO 
> buffer size
> > #endif
> >
> > #ifndef MG_DATA_SIZE
> > @@ -688,18 +772,18 @@ struct timeval {
> > #endif
> >
> > #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> > -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
> > #endif
> >
> > #ifndef MG_DIRSEP
> > #define MG_DIRSEP '/'
> > #endif
> >
> > -#ifndef MG_ENABLE_FILE
> > +#ifndef MG_ENABLE_POSIX_FS
> > #if defined(FOPEN_MAX)
> > -#define MG_ENABLE_FILE 1
> > +#define MG_ENABLE_POSIX_FS 1
> > #else
> > -#define MG_ENABLE_FILE 0
> > +#define MG_ENABLE_POSIX_FS 0
> > #endif
> > #endif
> >
> > @@ -732,60 +816,112 @@ struct timeval {
> > #define MG_EPOLL_MOD(c, wr)
> > #endif
> >
> > +#ifndef MG_ENABLE_PROFILE
> > +#define MG_ENABLE_PROFILE 0
> > +#endif
> >
> > +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also 
> initialize
> > +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for
> > +#endif // Mongoose built-in network stack
> >
> > +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
> > +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP)
> > +#endif
> >
> > -struct mg_str {
> > - const char *ptr; // Pointer to string data
> > - size_t len; // String len
> > -};
> > +#ifndef MG_TCPIP_MASK
> > +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP)
> > +#endif
> > +
> > +#ifndef MG_TCPIP_GW
> > +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP)
> > +#endif
> > +
> > +#ifndef MG_SET_MAC_ADDRESS
> > +#define MG_SET_MAC_ADDRESS(mac)
> > +#endif
> >
> > -#define MG_NULL_STR \
> > - { NULL, 0 }
> > +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> > +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
> > +#endif
> >
> > -#define MG_C_STR(a) \
> > - { (a), sizeof(a) - 1 }
> > +
> > +
> > +
> > +// Describes an arbitrary chunk of memory
> > +struct mg_str {
> > + char *buf; // String data
> > + size_t len; // String length
> > +};
> >
> > // Using macro to avoid shadowing C++ struct constructor, see #1298
> > #define mg_str(s) mg_str_s(s)
> >
> > struct mg_str mg_str(const char *s);
> > struct mg_str mg_str_n(const char *s, size_t n);
> > -int mg_lower(const char *s);
> > -int mg_ncasecmp(const char *s1, const char *s2, size_t len);
> > int mg_casecmp(const char *s1, const char *s2);
> > -int mg_vcmp(const struct mg_str *s1, const char *s2);
> > -int mg_vcasecmp(const struct mg_str *str1, const char *str2);
> > int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
> > -struct mg_str mg_strstrip(struct mg_str s);
> > -struct mg_str mg_strdup(const struct mg_str s);
> > -const char *mg_strstr(const struct mg_str haystack, const struct mg_str 
> needle);
> > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2);
> > bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str 
> *caps);
> > -bool mg_globmatch(const char *pattern, size_t plen, const char *s, 
> size_t n);
> > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
> > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, 
> char delim);
> > -char *mg_hex(const void *buf, size_t len, char *dst);
> > -void mg_unhex(const char *buf, size_t len, unsigned char *to);
> > -unsigned long mg_unhexn(const char *s, size_t len);
> > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
> > -int64_t mg_to64(struct mg_str str);
> > -uint64_t mg_tou64(struct mg_str str);
> > -char *mg_remove_double_dots(char *s);
> > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char 
> delim);
> > +
> > +bool mg_str_to_num(struct mg_str, int base, void *val, size_t val_len);
> > +
> >
> >
> >
> > +// Single producer, single consumer non-blocking queue
> > +
> > +struct mg_queue {
> > + char *buf;
> > + size_t size;
> > + volatile size_t tail;
> > + volatile size_t head;
> > +};
> > +
> > +void mg_queue_init(struct mg_queue *, char *, size_t); // Init queue
> > +size_t mg_queue_book(struct mg_queue *, char **buf, size_t); // Reserve 
> space
> > +void mg_queue_add(struct mg_queue *, size_t); // Add new message
> > +size_t mg_queue_next(struct mg_queue *, char **); // Get oldest message
> > +void mg_queue_del(struct mg_queue *, size_t); // Delete oldest message
> >
> >
> > -typedef void (*mg_pfn_t)(char, void *); // Custom putchar
> > +
> > +
> > +typedef void (*mg_pfn_t)(char, void *); // Output function
> > typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M printer
> > -void mg_pfn_iobuf(char ch, void *param); // iobuf printer
> >
> > size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, 
> va_list *);
> > size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, 
> ...);
> > +
> > +
> > +
> > +
> > +
> > +
> > +// Convenience wrappers around mg_xprintf
> > size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
> > size_t mg_snprintf(char *, size_t, const char *fmt, ...);
> > char *mg_vmprintf(const char *fmt, va_list *ap);
> > char *mg_mprintf(const char *fmt, ...);
> > +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *);
> > +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
> > +
> > +// %M print helper functions
> > +size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list 
> *ap);
> > +size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap);
> > +size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap);
> > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap);
> > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list 
> *ap);
> > +size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap);
> > +size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap);
> > +size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap);
> > +
> > +// Various output functions
> > +void mg_pfn_iobuf(char ch, void *param); // param: struct mg_iobuf *
> > +void mg_pfn_stdout(char c, void *param); // param: ignored
> > +
> > +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", 
> MG_ESC("hi"))
> > +#define MG_ESC(str) mg_print_esc, 0, (str)
> >
> >
> >
> > @@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
> >
> >
> > enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
> > +extern int mg_log_level; // Current log level, one of MG_LL_*
> > +
> > void mg_log(const char *fmt, ...);
> > -bool mg_log_prefix(int ll, const char *file, int line, const char 
> *fname);
> > -void mg_log_set(int log_level);
> > +void mg_log_prefix(int ll, const char *file, int line, const char 
> *fname);
> > +// bool mg_log2(int ll, const char *file, int line, const char *fmt, 
> ...);
> > void mg_hexdump(const void *buf, size_t len);
> > void mg_log_set_fn(mg_pfn_t fn, void *param);
> >
> > +#define mg_log_set(level_) mg_log_level = (level_)
> > +
> > #if MG_ENABLE_LOG
> > -#define MG_LOG(level, args) \
> > - do { \
> > - if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; 
> \
> > +#define MG_LOG(level, args) \
> > + do { \
> > + if ((level) <= mg_log_level) { \
> > + mg_log_prefix((level), __FILE__, __LINE__, __func__); \
> > + mg_log args; \
> > + } \
> > } while (0)
> > #else
> > #define MG_LOG(level, args) \
> > @@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 
> 4 };
> > // stat(), write(), read() calls.
> > struct mg_fs {
> > int (*st)(const char *path, size_t *size, time_t *mtime); // stat file
> > - void (*ls)(const char *path, void (*fn)(const char *, void *), void *);
> > + void (*ls)(const char *path, void (*fn)(const char *, void *),
> > + void *); // List directory entries: call fn(file_name, fn_data)
> > + // for each directory entry
> > void *(*op)(const char *path, int flags); // Open file
> > void (*cl)(void *fd); // Close file
> > size_t (*rd)(void *fd, void *buf, size_t len); // Read file
> > @@ -877,28 +1022,84 @@ struct mg_fd {
> >
> > struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags);
> > void mg_fs_close(struct mg_fd *fd);
> > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size);
> > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t 
> len);
> > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
> > bool mg_file_write(struct mg_fs *fs, const char *path, const void *, 
> size_t);
> > bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, 
> ...);
> >
> > +// Packed API
> > +const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
> > +const char *mg_unlist(size_t no); // Get no'th packed filename
> > +struct mg_str mg_unpacked(const char *path); // Packed file as mg_str
> > +
> >
> >
> >
> >
> >
> > +
> > +#if MG_ENABLE_ASSERT
> > +#include <assert.h>
> > +#elif !defined(assert)
> > +#define assert(x)
> > +#endif
> > +
> > +void mg_bzero(volatile unsigned char *buf, size_t len);
> > void mg_random(void *buf, size_t len);
> > char *mg_random_str(char *buf, size_t len);
> > uint16_t mg_ntohs(uint16_t net);
> > uint32_t mg_ntohl(uint32_t net);
> > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
> > -uint64_t mg_millis(void);
> > +uint64_t mg_millis(void); // Return milliseconds since boot
> > +uint64_t mg_now(void); // Return milliseconds since Epoch
> > +bool mg_path_is_sane(const struct mg_str path);
> >
> > #define mg_htons(x) mg_ntohs(x)
> > #define mg_htonl(x) mg_ntohl(x)
> >
> > -#define MG_U32(a, b, c, d) \
> > - (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
> > - ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
> > +#define MG_U32(a, b, c, d) \
> > + (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \
> > + ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
> > +
> > +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
> > +
> > +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", 
> MG_IPADDR_PARTS(&ip))
> > +#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
> > +#define MG_IPADDR_PARTS(ADDR) \
> > + MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
> > +
> > +#define MG_REG(x) ((volatile uint32_t *) (x))[0]
> > +#define MG_BIT(x) (((uint32_t) 1U) << (x))
> > +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | 
> (SETMASK)
> > +
> > +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * 
> (a)))
> > +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
> > +
> > +#if defined(__GNUC__)
> > +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
> > +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
> > +#elif defined(__CCRH__)
> > +#define MG_RH850_DISABLE_IRQ() __DI()
> > +#define MG_RH850_ENABLE_IRQ() __EI()
> > +#else
> > +#define MG_ARM_DISABLE_IRQ()
> > +#define MG_ARM_ENABLE_IRQ()
> > +#endif
> > +
> > +#if defined(__CC_ARM)
> > +#define MG_DSB() __dsb(0xf)
> > +#elif defined(__ARMCC_VERSION)
> > +#define MG_DSB() __builtin_arm_dsb(0xf)
> > +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
> > +#define MG_DSB() asm("DSB 0xf")
> > +#elif defined(__ICCARM__)
> > +#define MG_DSB() __iar_builtin_DSB()
> > +#else
> > +#define MG_DSB()
> > +#endif
> > +
> > +struct mg_addr;
> > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
> >
> > // Linked list management macros
> > #define LIST_ADD_HEAD(type_, head_, elem_) \
> > @@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
> > size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t);
> > size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
> >
> > -int mg_base64_update(unsigned char p, char *to, int len);
> > -int mg_base64_final(char *to, int len);
> > -int mg_base64_encode(const unsigned char *p, int n, char *to);
> > -int mg_base64_decode(const char *src, int n, char *dst);
> > +
> > +size_t mg_base64_update(unsigned char input_byte, char *buf, size_t 
> len);
> > +size_t mg_base64_final(char *buf, size_t len);
> > +size_t mg_base64_encode(const unsigned char *p, size_t n, char *buf, 
> size_t);
> > +size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t);
> >
> >
> >
> > @@ -976,35 +1178,793 @@ typedef struct {
> > void mg_sha1_init(mg_sha1_ctx *);
> > void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t 
> len);
> > void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
> > +// https://github.com/B-Con/crypto-algorithms
> > +// Author: Brad Conte (brad AT bradconte.com)
> > +// Disclaimer: This code is presented "as is" without any guarantees.
> > +// Details: Defines the API for the corresponding SHA1 implementation.
> > +// Copyright: public domain
> > +
> > +
> > +
> > +
> > +
> > +typedef struct {
> > + uint32_t state[8];
> > + uint64_t bits;
> > + uint32_t len;
> > + unsigned char buffer[64];
> > +} mg_sha256_ctx;
> > +
> > +void mg_sha256_init(mg_sha256_ctx *);
> > +void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, 
> size_t len);
> > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
> > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, 
> uint8_t *data,
> > + size_t datasz);
> > +#ifndef TLS_X15519_H
> > +#define TLS_X15519_H
> > +
> > +
> > +
> > +#define X25519_BYTES 32
> > +extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
> > +
> > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t 
> scalar[X25519_BYTES],
> > + const uint8_t x1[X25519_BYTES], int clamp);
> > +
> > +
> > +#endif /* TLS_X15519_H */
> > 
> +/******************************************************************************
> > + *
> > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE 
> GOOD OF ALL
> > + *
> > + * This is a simple and straightforward implementation of AES-GCM 
> authenticated
> > + * encryption. The focus of this work was correctness & accuracy. It is 
> written
> > + * in straight 'C' without any particular focus upon optimization or 
> speed. It
> > + * should be endian (memory byte order) neutral since the few places 
> that care
> > + * are handled explicitly.
> > + *
> > + * This implementation of AES-GCM was created by Steven M. Gibson of 
> GRC.com.
> > + *
> > + * It is intended for general purpose use, but was written in support 
> of GRC's
> > + * reference implementation of the SQRL (Secure Quick Reliable Login) 
> client.
> > + *
> > + * See: 
> http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> > + * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ 
> \
> > + * gcm/gcm-revised-spec.pdf
> > + *
> > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY 
> WARRANTY MADE
> > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN 
> RISK.
> > + *
> > + 
> *******************************************************************************/
> > +#ifndef TLS_AES128_H
> > +#define TLS_AES128_H
> > +
> > +typedef unsigned char uchar; // add some convienent shorter types
> > +typedef unsigned int uint;
> > +
> > 
> +/******************************************************************************
> > + * AES_CONTEXT : cipher context / holds inter-call data
> > + 
> ******************************************************************************/
> > +typedef struct {
> > + int mode; // 1 for Encryption, 0 for Decryption
> > + int rounds; // keysize-based rounds count
> > + uint32_t *rk; // pointer to current round key
> > + uint32_t buf[68]; // key expansion buffer
> > +} aes_context;
> > +
> > +
> > +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure
> > +
> > 
> +/******************************************************************************
> > + * GCM_CONTEXT : MUST be called once before ANY use of this library
> > + 
> ******************************************************************************/
> > +int mg_gcm_initialize(void);
> > +
> > +//
> > +// aes-gcm.h
> > +// MKo
> > +//
> > +// Created by Markus Kosmal on 20/11/14.
> > +//
> > +//
> > +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned char 
> *input,
> > + size_t input_length, const unsigned char *key,
> > + const size_t key_len, const unsigned char *iv,
> > + const size_t iv_len, unsigned char *aead,
> > + size_t aead_len, unsigned char *tag,
> > + const size_t tag_len);
> > +
> > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char 
> *input,
> > + size_t input_length, const unsigned char *key,
> > + const size_t key_len, const unsigned char *iv,
> > + const size_t iv_len);
> > +
> > +#endif /* TLS_AES128_H */
> > +
> > +// End of aes128 PD
> > +
> > +
> > +
> > +#define MG_UECC_SUPPORTS_secp256r1 1
> > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause 
> license. */
> > +
> > +#ifndef _UECC_H_
> > +#define _UECC_H_
> > +
> > +/* Platform selection options.
> > +If MG_UECC_PLATFORM is not defined, the code will try to guess it based 
> on
> > +compiler macros. Possible values for MG_UECC_PLATFORM are defined 
> below: */
> > +#define mg_uecc_arch_other 0
> > +#define mg_uecc_x86 1
> > +#define mg_uecc_x86_64 2
> > +#define mg_uecc_arm 3
> > +#define mg_uecc_arm_thumb 4
> > +#define mg_uecc_arm_thumb2 5
> > +#define mg_uecc_arm64 6
> > +#define mg_uecc_avr 7
> > +
> > +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate for your 
> platform
> > +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly defined then 
> it will
> > +be automatically set based on your platform. */
> > +
> > +/* Optimization level; trade speed for code size.
> > + Larger values produce code that is faster but larger.
> > + Currently supported values are 0 - 4; 0 is unusably slow for most
> > + applications. Optimization level 4 currently only has an effect ARM 
> platforms
> > + where more than one curve is enabled. */
> > +#ifndef MG_UECC_OPTIMIZATION_LEVEL
> > +#define MG_UECC_OPTIMIZATION_LEVEL 2
> > +#endif
> > +
> > +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this will 
> cause a
> > +specific function to be used for (scalar) squaring instead of the 
> generic
> > +multiplication function. This can make things faster somewhat faster, 
> but
> > +increases the code size. */
> > +#ifndef MG_UECC_SQUARE_FUNC
> > +#define MG_UECC_SQUARE_FUNC 0
> > +#endif
> > +
> > +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), 
> this will
> > +switch to native little-endian format for *all* arrays passed in and 
> out of the
> > +public API. This includes public and private keys, shared secrets, 
> signatures
> > +and message hashes. Using this switch reduces the amount of call stack 
> memory
> > +used by uECC, since less intermediate translations are required. Note 
> that this
> > +will *only* work on native little-endian processors and it will treat 
> the
> > +uint8_t arrays passed into the public API as word arrays, therefore 
> requiring
> > +the provided byte arrays to be word aligned on architectures that do 
> not support
> > +unaligned accesses. IMPORTANT: Keys and signatures generated with
> > +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and 
> signatures
> > +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use 
> the same
> > +endianness. */
> > +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> > +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
> > +#endif
> > +
> > +/* Curve support selection. Set to 0 to remove that curve. */
> > +#ifndef MG_UECC_SUPPORTS_secp160r1
> > +#define MG_UECC_SUPPORTS_secp160r1 0
> > +#endif
> > +#ifndef MG_UECC_SUPPORTS_secp192r1
> > +#define MG_UECC_SUPPORTS_secp192r1 0
> > +#endif
> > +#ifndef MG_UECC_SUPPORTS_secp224r1
> > +#define MG_UECC_SUPPORTS_secp224r1 0
> > +#endif
> > +#ifndef MG_UECC_SUPPORTS_secp256r1
> > +#define MG_UECC_SUPPORTS_secp256r1 1
> > +#endif
> > +#ifndef MG_UECC_SUPPORTS_secp256k1
> > +#define MG_UECC_SUPPORTS_secp256k1 0
> > +#endif
> > +
> > +/* Specifies whether compressed point format is supported.
> > + Set to 0 to disable point compression/decompression functions. */
> > +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
> > +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
> > +#endif
> > +
> > +struct MG_UECC_Curve_t;
> > +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
> > +
> > +#ifdef __cplusplus
> > +extern "C" {
> > +#endif
> > +
> > +#if MG_UECC_SUPPORTS_secp160r1
> > +MG_UECC_Curve mg_uecc_secp160r1(void);
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp192r1
> > +MG_UECC_Curve mg_uecc_secp192r1(void);
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp224r1
> > +MG_UECC_Curve mg_uecc_secp224r1(void);
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp256r1
> > +MG_UECC_Curve mg_uecc_secp256r1(void);
> > +#endif
> > +#if MG_UECC_SUPPORTS_secp256k1
> > +MG_UECC_Curve mg_uecc_secp256k1(void);
> > +#endif
> > +
> > +/* MG_UECC_RNG_Function type
> > +The RNG function should fill 'size' random bytes into 'dest'. It should 
> return 1
> > +if 'dest' was filled with random data, or 0 if the random data could 
> not be
> > +generated. The filled-in values should be either truly random, or from a
> > +cryptographically-secure PRNG.
> > +
> > +A correctly functioning RNG function must be set (using 
> mg_uecc_set_rng())
> > +before calling mg_uecc_make_key() or mg_uecc_sign().
> > +
> > +Setting a correctly functioning RNG function improves the resistance to
> > +side-channel attacks for mg_uecc_shared_secret() and
> > +mg_uecc_sign_deterministic().
> > +
> > +A correct RNG function is set by default when building for Windows, 
> Linux, or OS
> > +X. If you are building on another POSIX-compliant system that supports
> > +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the 
> predefined
> > +RNG. For embedded platforms there is no predefined RNG function; you 
> must
> > +provide your own.
> > +*/
> > +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
> > +
> > +/* mg_uecc_set_rng() function.
> > +Set the function that will be used to generate random bytes. The RNG 
> function
> > +should return 1 if the random data was generated, or 0 if the random 
> data could
> > +not be generated.
> > +
> > +On platforms where there is no predefined RNG function (eg embedded 
> platforms),
> > +this must be called before mg_uecc_make_key() or mg_uecc_sign() are 
> used.
> > +
> > +Inputs:
> > + rng_function - The function that will be used to generate random bytes.
> > +*/
> > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
> > +
> > +/* mg_uecc_get_rng() function.
> > +
> > +Returns the function that will be used to generate random bytes.
> > +*/
> > +MG_UECC_RNG_Function mg_uecc_get_rng(void);
> > +
> > +/* mg_uecc_curve_private_key_size() function.
> > +
> > +Returns the size of a private key for the curve in bytes.
> > +*/
> > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_curve_public_key_size() function.
> > +
> > +Returns the size of a public key for the curve in bytes.
> > +*/
> > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_make_key() function.
> > +Create a public/private key pair.
> > +
> > +Outputs:
> > + public_key - Will be filled in with the public key. Must be at least 2 
> *
> > +the curve size (in bytes) long. For example, if the curve is secp256r1,
> > +public_key must be 64 bytes long. private_key - Will be filled in with 
> the
> > +private key. Must be as long as the curve order; this is typically the 
> same as
> > +the curve size, except for secp160r1. For example, if the curve is 
> secp256r1,
> > +private_key must be 32 bytes long.
> > +
> > + For secp160r1, private_key must be 21 bytes long! Note that
> > +the first byte will almost always be 0 (there is about a 1 in 2^80 
> chance of it
> > +being non-zero).
> > +
> > +Returns 1 if the key pair was generated successfully, 0 if an error 
> occurred.
> > +*/
> > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> > + MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_shared_secret() function.
> > +Compute a shared secret given your secret key and someone else's public 
> key. If
> > +the public key is not from a trusted source and has not been previously
> > +verified, you should verify it first using mg_uecc_valid_public_key(). 
> Note: It
> > +is recommended that you hash the result of mg_uecc_shared_secret() 
> before using
> > +it for symmetric encryption or HMAC.
> > +
> > +Inputs:
> > + public_key - The public key of the remote party.
> > + private_key - Your private key.
> > +
> > +Outputs:
> > + secret - Will be filled in with the shared secret value. Must be the 
> same
> > +size as the curve size; for example, if the curve is secp256r1, secret 
> must be
> > +32 bytes long.
> > +
> > +Returns 1 if the shared secret was generated successfully, 0 if an error
> > +occurred.
> > +*/
> > +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t 
> *private_key,
> > + uint8_t *secret, MG_UECC_Curve curve);
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +/* mg_uecc_compress() function.
> > +Compress a public key.
> > +
> > +Inputs:
> > + public_key - The public key to compress.
> > +
> > +Outputs:
> > + compressed - Will be filled in with the compressed public key. Must be 
> at
> > +least (curve size + 1) bytes long; for example, if the curve is 
> secp256r1,
> > + compressed must be 33 bytes long.
> > +*/
> > +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
> > + MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_decompress() function.
> > +Decompress a compressed public key.
> > +
> > +Inputs:
> > + compressed - The compressed public key.
> > +
> > +Outputs:
> > + public_key - Will be filled in with the decompressed public key.
> > +*/
> > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
> > + MG_UECC_Curve curve);
> > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> > +
> > +/* mg_uecc_valid_public_key() function.
> > +Check to see if a public key is valid.
> > +
> > +Note that you are not required to check for a valid public key before 
> using any
> > +other uECC functions. However, you may wish to avoid spending CPU time 
> computing
> > +a shared secret or verifying a signature using an invalid public key.
> > +
> > +Inputs:
> > + public_key - The public key to check.
> > +
> > +Returns 1 if the public key is valid, 0 if it is invalid.
> > +*/
> > +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve 
> curve);
> > +
> > +/* mg_uecc_compute_public_key() function.
> > +Compute the corresponding public key for a private key.
> > +
> > +Inputs:
> > + private_key - The private key to compute the public key for
> > +
> > +Outputs:
> > + public_key - Will be filled in with the corresponding public key
> > +
> > +Returns 1 if the key was computed successfully, 0 if an error occurred.
> > +*/
> > +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t 
> *public_key,
> > + MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_sign() function.
> > +Generate an ECDSA signature for a given hash value.
> > +
> > +Usage: Compute a hash of the data you wish to sign (SHA-2 is 
> recommended) and
> > +pass it in to this function along with your private key.
> > +
> > +Inputs:
> > + private_key - Your private key.
> > + message_hash - The hash of the message to sign.
> > + hash_size - The size of message_hash in bytes.
> > +
> > +Outputs:
> > + signature - Will be filled in with the signature value. Must be at 
> least 2 *
> > +curve size long. For example, if the curve is secp256r1, signature must 
> be 64
> > +bytes long.
> > +
> > +Returns 1 if the signature generated successfully, 0 if an error 
> occurred.
> > +*/
> > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t 
> *message_hash,
> > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
> > +
> > +/* MG_UECC_HashContext structure.
> > +This is used to pass in an arbitrary hash function to
> > +mg_uecc_sign_deterministic(). The structure will be used for multiple 
> hash
> > +computations; each time a new hash is computed, init_hash() will be 
> called,
> > +followed by one or more calls to update_hash(), and finally a call to
> > +finish_hash() to produce the resulting hash.
> > +
> > +The intention is that you will create a structure that includes
> > +MG_UECC_HashContext followed by any hash-specific data. For example:
> > +
> > +typedef struct SHA256_HashContext {
> > + MG_UECC_HashContext uECC;
> > + SHA256_CTX ctx;
> > +} SHA256_HashContext;
> > +
> > +void init_SHA256(MG_UECC_HashContext *base) {
> > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> > + SHA256_Init(&context->ctx);
> > +}
> > +
> > +void update_SHA256(MG_UECC_HashContext *base,
> > + const uint8_t *message,
> > + unsigned message_size) {
> > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> > + SHA256_Update(&context->ctx, message, message_size);
> > +}
> > +
> > +void finish_SHA256(MG_UECC_HashContext *base, uint8_t *hash_result) {
> > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> > + SHA256_Final(hash_result, &context->ctx);
> > +}
> > +
> > +... when signing ...
> > +{
> > + uint8_t tmp[32 + 32 + 64];
> > + SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, 
> &finish_SHA256, 64,
> > +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash, &ctx.uECC, 
> signature);
> > +}
> > +*/
> > +typedef struct MG_UECC_HashContext {
> > + void (*init_hash)(const struct MG_UECC_HashContext *context);
> > + void (*update_hash)(const struct MG_UECC_HashContext *context,
> > + const uint8_t *message, unsigned message_size);
> > + void (*finish_hash)(const struct MG_UECC_HashContext *context,
> > + uint8_t *hash_result);
> > + unsigned
> > + block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */
> > + unsigned
> > + result_size; /* Hash function result size in bytes, eg 32 for SHA-256. 
> */
> > + uint8_t *tmp; /* Must point to a buffer of at least (2 * result_size +
> > + block_size) bytes. */
> > +} MG_UECC_HashContext;
> > +
> > +/* mg_uecc_sign_deterministic() function.
> > +Generate an ECDSA signature for a given hash value, using a 
> deterministic
> > +algorithm (see RFC 6979). You do not need to set the RNG using 
> mg_uecc_set_rng()
> > +before calling this function; however, if the RNG is defined it will 
> improve
> > +resistance to side-channel attacks.
> > +
> > +Usage: Compute a hash of the data you wish to sign (SHA-2 is 
> recommended) and
> > +pass it to this function along with your private key and a hash 
> context. Note
> > +that the message_hash does not need to be computed with the same hash 
> function
> > +used by hash_context.
> > +
> > +Inputs:
> > + private_key - Your private key.
> > + message_hash - The hash of the message to sign.
> > + hash_size - The size of message_hash in bytes.
> > + hash_context - A hash context to use.
> > +
> > +Outputs:
> > + signature - Will be filled in with the signature value.
> > +
> > +Returns 1 if the signature generated successfully, 0 if an error 
> occurred.
> > +*/
> > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> > + const uint8_t *message_hash, unsigned hash_size,
> > + const MG_UECC_HashContext *hash_context,
> > + uint8_t *signature, MG_UECC_Curve curve);
> > +
> > +/* mg_uecc_verify() function.
> > +Verify an ECDSA signature.
> > +
> > +Usage: Compute the hash of the signed data using the same hash as the 
> signer and
> > +pass it to this function along with the signer's public key and the 
> signature
> > +values (r and s).
> > +
> > +Inputs:
> > + public_key - The signer's public key.
> > + message_hash - The hash of the signed data.
> > + hash_size - The size of message_hash in bytes.
> > + signature - The signature value.
> > +
> > +Returns 1 if the signature is valid, 0 if it is invalid.
> > +*/
> > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t 
> *message_hash,
> > + unsigned hash_size, const uint8_t *signature,
> > + MG_UECC_Curve curve);
> > +
> > +#ifdef __cplusplus
> > +} /* end of extern "C" */
> > +#endif
> > +
> > +#endif /* _UECC_H_ */
> > +
> > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause 
> license. */
> > +
> > +#ifndef _UECC_VLI_H_
> > +#define _UECC_VLI_H_
> > +
> > +//
> > +//
> > +
> > +/* Functions for raw large-integer manipulation. These are only 
> available
> > + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
> > +#ifndef MG_UECC_ENABLE_VLI_API
> > +#define MG_UECC_ENABLE_VLI_API 0
> > +#endif
> > +
> > +#ifdef __cplusplus
> > +extern "C" {
> > +#endif
> > +
> > +#if MG_UECC_ENABLE_VLI_API
> > +
> > +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
> > +
> > +/* Constant-time comparison to zero - secure way to compare long 
> integers */
> > +/* Returns 1 if vli == 0, 0 otherwise. */
> > +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
> > + wordcount_t num_words);
> > +
> > +/* Returns nonzero if bit 'bit' of vli is set. */
> > +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, 
> bitcount_t bit);
> > +
> > +/* Counts the number of bits required to represent vli. */
> > +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
> > + const wordcount_t max_words);
> > +
> > +/* Sets dest = src. */
> > +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t *src,
> > + wordcount_t num_words);
> > +
> > +/* Constant-time comparison function - secure way to compare long 
> integers */
> > +/* Returns one if left == right, zero otherwise */
> > +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words);
> > +
> > +/* Constant-time comparison function - secure way to compare long 
> integers */
> > +/* Returns sign of left - right, in constant time. */
> > +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right, wordcount_t num_words);
> > +
> > +/* Computes vli = vli >> 1. */
> > +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t num_words);
> > +
> > +/* Computes result = left + right, returning carry. Can modify in 
> place. */
> > +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = left - right, returning borrow. Can modify in 
> place. */
> > +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = left * right. Result must be 2 * num_words long. */
> > +void mg_uecc_vli_mult(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *right, wordcount_t num_words);
> > +
> > +/* Computes result = left^2. Result must be 2 * num_words long. */
> > +void mg_uecc_vli_square(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = (left + right) % mod.
> > + Assumes that left < mod and right < mod, and that result does not 
> overlap
> > + mod. */
> > +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = (left - right) % mod.
> > + Assumes that left < mod and right < mod, and that result does not 
> overlap
> > + mod. */
> > +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = product % mod, where product is 2N words long.
> > + Currently only designed to work for mod == curve->p or curve_n. */
> > +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t *product,
> > + const mg_uecc_word_t *mod, wordcount_t num_words);
> > +
> > +/* Calculates result = product (mod curve->p), where product is up to
> > + 2 * curve->num_words long. */
> > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t 
> *product,
> > + MG_UECC_Curve curve);
> > +
> > +/* Computes result = (left * right) % mod.
> > + Currently only designed to work for mod == curve->p or curve_n. */
> > +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> > + wordcount_t num_words);
> > +
> > +/* Computes result = (left * right) % curve->p. */
> > +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + const mg_uecc_word_t *right, MG_UECC_Curve curve);
> > +
> > +/* Computes result = left^2 % mod.
> > + Currently only designed to work for mod == curve->p or curve_n. */
> > +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const mg_uecc_word_t 
> *left,
> > + const mg_uecc_word_t *mod, wordcount_t num_words);
> > +
> > +/* Computes result = left^2 % curve->p. */
> > +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> > + const mg_uecc_word_t *left,
> > + MG_UECC_Curve curve);
> > +
> > +/* Computes result = (1 / input) % mod.*/
> > +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const mg_uecc_word_t 
> *input,
> > + const mg_uecc_word_t *mod, wordcount_t num_words);
> > +
> > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> > +/* Calculates a = sqrt(a) (mod curve->p) */
> > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
> > +#endif
> > +
> > +/* Converts an integer in uECC native format to big-endian bytes. */
> > +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
> > + const mg_uecc_word_t *native);
> > +/* Converts big-endian bytes to an integer in uECC native format. */
> > +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const uint8_t 
> *bytes,
> > + int num_bytes);
> > +
> > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
> > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
> > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
> > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
> > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
> > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
> > +
> > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
> > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
> > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
> > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
> > +
> > +int mg_uecc_valid_point(const mg_uecc_word_t *point, MG_UECC_Curve 
> curve);
> > +
> > +/* Multiplies a point by a scalar. Points are represented by the X 
> coordinate
> > + followed by the Y coordinate in the same array, both coordinates are
> > + curve->num_words long. Note that scalar must be curve->num_n_words 
> long (NOT
> > + curve->num_words). */
> > +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t 
> *point,
> > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
> > +
> > +/* Generates a random integer in the range 0 < random < top.
> > + Both random and top have num_words words. */
> > +int mg_uecc_generate_random_int(mg_uecc_word_t *random,
> > + const mg_uecc_word_t *top,
> > + wordcount_t num_words);
> > +
> > +#endif /* MG_UECC_ENABLE_VLI_API */
> > +
> > +#ifdef __cplusplus
> > +} /* end of extern "C" */
> > +#endif
> > +
> > +#endif /* _UECC_VLI_H_ */
> > +
> > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause 
> license. */
> > +
> > +#ifndef _UECC_TYPES_H_
> > +#define _UECC_TYPES_H_
> > +
> > +#ifndef MG_UECC_PLATFORM
> > +#if defined(__AVR__) && __AVR__
> > +#define MG_UECC_PLATFORM mg_uecc_avr
> > +#elif defined(__thumb2__) || \
> > + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
> > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
> > +#elif defined(__thumb__)
> > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb
> > +#elif defined(__arm__) || defined(_M_ARM)
> > +#define MG_UECC_PLATFORM mg_uecc_arm
> > +#elif defined(__aarch64__)
> > +#define MG_UECC_PLATFORM mg_uecc_arm64
> > +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
> > + defined(__I86__)
> > +#define MG_UECC_PLATFORM mg_uecc_x86
> > +#elif defined(__amd64__) || defined(_M_X64)
> > +#define MG_UECC_PLATFORM mg_uecc_x86_64
> > +#else
> > +#define MG_UECC_PLATFORM mg_uecc_arch_other
> > +#endif
> > +#endif
> > +
> > +#ifndef MG_UECC_ARM_USE_UMAAL
> > +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
> > +#define MG_UECC_ARM_USE_UMAAL 1
> > +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \
> > + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
> > +#define MG_UECC_ARM_USE_UMAAL 1
> > +#else
> > +#define MG_UECC_ARM_USE_UMAAL 0
> > +#endif
> > +#endif
> > +
> > +#ifndef MG_UECC_WORD_SIZE
> > +#if MG_UECC_PLATFORM == mg_uecc_avr
> > +#define MG_UECC_WORD_SIZE 1
> > +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == 
> mg_uecc_arm64)
> > +#define MG_UECC_WORD_SIZE 8
> > +#else
> > +#define MG_UECC_WORD_SIZE 4
> > +#endif
> > +#endif
> > +
> > +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
> > + (MG_UECC_WORD_SIZE != 8)
> > +#error "Unsupported value for MG_UECC_WORD_SIZE"
> > +#endif
> > +
> > +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
> > +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
> > +#undef MG_UECC_WORD_SIZE
> > +#define MG_UECC_WORD_SIZE 1
> > +#endif
> > +
> > +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \
> > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
> > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
> > + (MG_UECC_WORD_SIZE != 4))
> > +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
> > +#undef MG_UECC_WORD_SIZE
> > +#define MG_UECC_WORD_SIZE 4
> > +#endif
> > +
> > +typedef int8_t wordcount_t;
> > +typedef int16_t bitcount_t;
> > +typedef int8_t cmpresult_t;
> > +
> > +#if (MG_UECC_WORD_SIZE == 1)
> > +
> > +typedef uint8_t mg_uecc_word_t;
> > +typedef uint16_t mg_uecc_dword_t;
> > +
> > +#define HIGH_BIT_SET 0x80
> > +#define MG_UECC_WORD_BITS 8
> > +#define MG_UECC_WORD_BITS_SHIFT 3
> > +#define MG_UECC_WORD_BITS_MASK 0x07
> > +
> > +#elif (MG_UECC_WORD_SIZE == 4)
> > +
> > +typedef uint32_t mg_uecc_word_t;
> > +typedef uint64_t mg_uecc_dword_t;
> > +
> > +#define HIGH_BIT_SET 0x80000000
> > +#define MG_UECC_WORD_BITS 32
> > +#define MG_UECC_WORD_BITS_SHIFT 5
> > +#define MG_UECC_WORD_BITS_MASK 0x01F
> > +
> > +#elif (MG_UECC_WORD_SIZE == 8)
> > +
> > +typedef uint64_t mg_uecc_word_t;
> > +
> > +#define HIGH_BIT_SET 0x8000000000000000U
> > +#define MG_UECC_WORD_BITS 64
> > +#define MG_UECC_WORD_BITS_SHIFT 6
> > +#define MG_UECC_WORD_BITS_MASK 0x03F
> > +
> > +#endif /* MG_UECC_WORD_SIZE */
> > +
> > +#endif /* _UECC_TYPES_H_ */
> > +// End of uecc BSD-2
> >
> >
> > struct mg_connection;
> > typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
> > - void *ev_data, void *fn_data);
> > + void *ev_data);
> > void mg_call(struct mg_connection *c, int ev, void *ev_data);
> > void mg_error(struct mg_connection *c, const char *fmt, ...);
> >
> > enum {
> > - MG_EV_ERROR, // Error char *error_message
> > - MG_EV_OPEN, // Connection created NULL
> > - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
> > - MG_EV_RESOLVE, // Host name is resolved NULL
> > - MG_EV_CONNECT, // Connection established NULL
> > - MG_EV_ACCEPT, // Connection accepted NULL
> > - MG_EV_TLS_HS, // TLS handshake succeeded NULL
> > - MG_EV_READ, // Data received from socket long *bytes_read
> > - MG_EV_WRITE, // Data written to socket long *bytes_written
> > - MG_EV_CLOSE, // Connection closed NULL
> > - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message *
> > - MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct mg_http_message *
> > - MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message *
> > - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
> > - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
> > - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
> > - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
> > - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
> > - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
> > - MG_EV_USER // Starting ID for user events
> > + MG_EV_ERROR, // Error char *error_message
> > + MG_EV_OPEN, // Connection created NULL
> > + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
> > + MG_EV_RESOLVE, // Host name is resolved NULL
> > + MG_EV_CONNECT, // Connection established NULL
> > + MG_EV_ACCEPT, // Connection accepted NULL
> > + MG_EV_TLS_HS, // TLS handshake succeeded NULL
> > + MG_EV_READ, // Data received from socket long *bytes_read
> > + MG_EV_WRITE, // Data written to socket long *bytes_written
> > + MG_EV_CLOSE, // Connection closed NULL
> > + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message *
> > + MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message *
> > + MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message *
> > + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
> > + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
> > + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
> > + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
> > + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
> > + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
> > + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
> > + MG_EV_USER // Starting ID for user events
> > };
> >
> >
> > @@ -1021,10 +1981,10 @@ struct mg_dns {
> > };
> >
> > struct mg_addr {
> > - uint16_t port; // TCP or UDP port in network byte order
> > - uint32_t ip; // IP address in network byte order
> > - uint8_t ip6[16]; // IPv6 address
> > - bool is_ip6; // True when address is IPv6 address
> > + uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte order
> > + uint16_t port; // TCP or UDP port in network byte order
> > + uint8_t scope_id; // IPv6 scope ID
> > + bool is_ip6; // True when address is IPv6 address
> > };
> >
> > struct mg_mgr {
> > @@ -1036,12 +1996,14 @@ struct mg_mgr {
> > unsigned long nextid; // Next connection ID
> > unsigned long timerid; // Next timer ID
> > void *userdata; // Arbitrary user data pointer
> > + void *tls_ctx; // TLS context shared by all TLS sessions
> > uint16_t mqtt_id; // MQTT IDs for pub/sub
> > void *active_dns_requests; // DNS requests in progress
> > struct mg_timer *timers; // Active timers
> > int epoll_fd; // Used when MG_EPOLL_ENABLE=1
> > void *priv; // Used by the MIP stack
> > size_t extraconnsize; // Used by the MIP stack
> > + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup()
> > #if MG_ENABLE_FREERTOS_TCP
> > SocketSet_t ss; // NOTE(lsm): referenced from socket struct
> > #endif
> > @@ -1056,6 +2018,8 @@ struct mg_connection {
> > unsigned long id; // Auto-incrementing unique connection ID
> > struct mg_iobuf recv; // Incoming data
> > struct mg_iobuf send; // Outgoing data
> > + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE
> > + struct mg_iobuf rtls; // TLS only. Incoming encrypted data
> > mg_event_handler_t fn; // User-specified event handler function
> > void *fn_data; // User-specified function parameter
> > mg_event_handler_t pfn; // Protocol-specific handler function
> > @@ -1066,6 +2030,7 @@ struct mg_connection {
> > unsigned is_client : 1; // Outbound (client) connection
> > unsigned is_accepted : 1; // Accepted (server) connection
> > unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress
> > + unsigned is_arplooking : 1; // Non-blocking ARP resolution is in 
> progress
> > unsigned is_connecting : 1; // Non-blocking connect is in progress
> > unsigned is_tls : 1; // TLS-enabled connection
> > unsigned is_tls_hs : 1; // TLS handshake is in progress
> > @@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *, const void 
> *, size_t);
> > size_t mg_printf(struct mg_connection *, const char *fmt, ...);
> > size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap);
> > bool mg_aton(struct mg_str str, struct mg_addr *addr);
> > -int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp);
> >
> > // These functions are used to integrate with custom network stacks
> > struct mg_connection *mg_alloc_conn(struct mg_mgr *);
> > void mg_close_conn(struct mg_connection *c);
> > bool mg_open_listener(struct mg_connection *c, const char *url);
> > +
> > +// Utility functions
> > +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, 
> size_t len);
> > +bool mg_wakeup_init(struct mg_mgr *);
> > struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
> > unsigned flags, void (*fn)(void *), void *arg);
> >
> > -// Low-level IO primives used by TLS layer
> > -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> > -long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
> > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
> > -
> >
> >
> >
> > @@ -1127,7 +2090,6 @@ struct mg_http_message {
> > struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers
> > struct mg_str body; // Body
> > struct mg_str head; // Request + headers
> > - struct mg_str chunk; // Chunk for chunked encoding, or partial body
> > struct mg_str message; // Request + headers + body
> > };
> >
> > @@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *, const 
> char *name, char *, size_t);
> > int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int 
> form);
> > size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len);
> > void mg_http_creds(struct mg_http_message *, char *, size_t, char *, 
> size_t);
> > -bool mg_http_match_uri(const struct mg_http_message *, const char 
> *glob);
> > long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
> > - struct mg_fs *fs, const char *path, size_t max_size);
> > + struct mg_fs *fs, const char *dir, size_t max_size);
> > void mg_http_bauth(struct mg_connection *, const char *user, const char 
> *pass);
> > struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
> > size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part 
> *);
> > @@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct mg_connection *c, 
> const char *root,
> > const char *fullpath);
> >
> >
> > +#define MG_TLS_NONE 0 // No TLS support
> > +#define MG_TLS_MBED 1 // mbedTLS
> > +#define MG_TLS_OPENSSL 2 // OpenSSL
> > +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL)
> > +#define MG_TLS_BUILTIN 3 // Built-in
> > +#define MG_TLS_CUSTOM 4 // Custom implementation
> > +
> > +#ifndef MG_TLS
> > +#define MG_TLS MG_TLS_NONE
> > +#endif
> > +
> >
> >
> >
> >
> > struct mg_tls_opts {
> > - const char *ca; // CA certificate file. For both listeners and clients
> > - const char *crl; // Certificate Revocation List. For clients
> > - const char *cert; // Certificate
> > - const char *certkey; // Certificate key
> > - const char *ciphers; // Cipher list
> > - struct mg_str srvname; // If not empty, enables server name 
> verification
> > - struct mg_fs *fs; // FS API for reading certificate files
> > + struct mg_str ca; // PEM or DER
> > + struct mg_str cert; // PEM or DER
> > + struct mg_str key; // PEM or DER
> > + struct mg_str name; // If not empty, enable host name verification
> > + int skip_verification; // Skip certificate and host name verification
> > };
> >
> > -void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *);
> > +void mg_tls_init(struct mg_connection *, const struct mg_tls_opts 
> *opts);
> > void mg_tls_free(struct mg_connection *);
> > long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
> > long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
> > size_t mg_tls_pending(struct mg_connection *);
> > void mg_tls_handshake(struct mg_connection *);
> >
> > +// Private
> > +void mg_tls_ctx_init(struct mg_mgr *);
> > +void mg_tls_ctx_free(struct mg_mgr *);
> > +
> > +// Low-level IO primives used by TLS layer
> > +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> > +long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
> > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
> >
> >
> >
> >
> >
> >
> > -#if MG_ENABLE_MBEDTLS
> > +
> > +#if MG_TLS == MG_TLS_MBED
> > #include <mbedtls/debug.h>
> > #include <mbedtls/net_sockets.h>
> > #include <mbedtls/ssl.h>
> > +#include <mbedtls/ssl_ticket.h>
> > +
> > +struct mg_tls_ctx {
> > + int dummy;
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + mbedtls_ssl_ticket_context tickets;
> > +#endif
> > +};
> >
> > struct mg_tls {
> > - char *cafile; // CA certificate path
> > mbedtls_x509_crt ca; // Parsed CA certificate
> > mbedtls_x509_crt cert; // Parsed certificate
> > + mbedtls_pk_context pk; // Private key context
> > mbedtls_ssl_context ssl; // SSL/TLS context
> > mbedtls_ssl_config conf; // SSL-TLS config
> > - mbedtls_pk_context pk; // Private key context
> > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> > + mbedtls_ssl_ticket_context ticket; // Session tickets context
> > +#endif
> > };
> > #endif
> >
> >
> > -#if MG_ENABLE_OPENSSL
> > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
> >
> > -#ifdef CONFIG_SSL_IMPL_WOLFSSL
> > -#include <wolfssl/openssl/err.h>
> > -#include <wolfssl/openssl/ssl.h>
> > -#else
> > #include <openssl/err.h>
> > #include <openssl/ssl.h>
> > -#endif
> >
> > struct mg_tls {
> > + BIO_METHOD *bm;
> > SSL_CTX *ctx;
> > SSL *ssl;
> > };
> > @@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char *buf, 
> size_t len);
> > #define MQTT_CMD_DISCONNECT 14
> > #define MQTT_CMD_AUTH 15
> >
> > +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
> > +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
> > +#define MQTT_PROP_CONTENT_TYPE 0x03
> > +#define MQTT_PROP_RESPONSE_TOPIC 0x08
> > +#define MQTT_PROP_CORRELATION_DATA 0x09
> > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
> > +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
> > +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
> > +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
> > +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
> > +#define MQTT_PROP_AUTHENTICATION_DATA 0x16
> > +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
> > +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
> > +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
> > +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
> > +#define MQTT_PROP_SERVER_REFERENCE 0x1C
> > +#define MQTT_PROP_REASON_STRING 0x1F
> > +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
> > +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
> > +#define MQTT_PROP_TOPIC_ALIAS 0x23
> > +#define MQTT_PROP_MAXIMUM_QOS 0x24
> > +#define MQTT_PROP_RETAIN_AVAILABLE 0x25
> > +#define MQTT_PROP_USER_PROPERTY 0x26
> > +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
> > +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
> > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
> > +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
> > +
> > +enum {
> > + MQTT_PROP_TYPE_BYTE,
> > + MQTT_PROP_TYPE_STRING,
> > + MQTT_PROP_TYPE_STRING_PAIR,
> > + MQTT_PROP_TYPE_BINARY_DATA,
> > + MQTT_PROP_TYPE_VARIABLE_INT,
> > + MQTT_PROP_TYPE_INT,
> > + MQTT_PROP_TYPE_SHORT
> > +};
> > +
> > enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
> >
> > +struct mg_mqtt_prop {
> > + uint8_t id; // Enumerated at MQTT5 Reference
> > + uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types
> > + struct mg_str key; // Non-NULL only for user property type
> > + struct mg_str val; // Non-NULL only for UTF-8 types and user properties
> > +};
> > +
> > struct mg_mqtt_opts {
> > - struct mg_str user; // Username, can be empty
> > - struct mg_str pass; // Password, can be empty
> > - struct mg_str client_id; // Client ID
> > - struct mg_str will_topic; // Will topic
> > - struct mg_str will_message; // Will message
> > - uint8_t will_qos; // Will message quality of service
> > - uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4.
> > - uint16_t keepalive; // Keep-alive timer in seconds
> > - bool will_retain; // Retain last will
> > - bool clean; // Use clean session, 0 or 1
> > + struct mg_str user; // Username, can be empty
> > + struct mg_str pass; // Password, can be empty
> > + struct mg_str client_id; // Client ID
> > + struct mg_str topic; // message/subscription topic
> > + struct mg_str message; // message content
> > + uint8_t qos; // message quality of service
> > + uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4
> > + uint16_t keepalive; // Keep-alive timer in seconds
> > + uint16_t retransmit_id; // For PUBLISH, init to 0
> > + bool retain; // Retain flag
> > + bool clean; // Clean session flag
> > + struct mg_mqtt_prop *props; // MQTT5 props array
> > + size_t num_props; // number of props
> > + struct mg_mqtt_prop *will_props; // Valid only for CONNECT packet 
> (MQTT5)
> > + size_t num_will_props; // Number of will props
> > };
> >
> > struct mg_mqtt_message {
> > - struct mg_str topic; // Parsed topic
> > - struct mg_str data; // Parsed message
> > - struct mg_str dgram; // Whole MQTT datagram, including headers
> > - uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, 
> PUBLISH
> > - uint8_t cmd; // MQTT command, one of MQTT_CMD_*
> > - uint8_t qos; // Quality of service
> > - uint8_t ack; // Connack return code. 0 - success
> > + struct mg_str topic; // Parsed topic for PUBLISH
> > + struct mg_str data; // Parsed message for PUBLISH
> > + struct mg_str dgram; // Whole MQTT packet, including headers
> > + uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
> > + uint8_t cmd; // MQTT command, one of MQTT_CMD_*
> > + uint8_t qos; // Quality of service
> > + uint8_t ack; // CONNACK return code, 0 = success
> > + size_t props_start; // Offset to the start of the properties (MQTT5)
> > + size_t props_size; // Length of the properties
> > };
> >
> > struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
> > @@ -1327,15 +2364,16 @@ struct mg_connection *mg_mqtt_connect(struct 
> mg_mgr *, const char *url,
> > struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
> > mg_event_handler_t fn, void *fn_data);
> > void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts);
> > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> > - struct mg_str data, int qos, bool retain);
> > -void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos);
> > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts 
> *opts);
> > +void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts 
> *opts);
> > int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct 
> mg_mqtt_message *);
> > void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t 
> flags,
> > uint32_t len);
> > void mg_mqtt_ping(struct mg_connection *);
> > void mg_mqtt_pong(struct mg_connection *);
> > -void mg_mqtt_disconnect(struct mg_connection *);
> > +void mg_mqtt_disconnect(struct mg_connection *, const struct 
> mg_mqtt_opts *);
> > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop 
> *,
> > + size_t ofs);
> >
> >
> >
> > @@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf, size_t 
> len, size_t ofs,
> > enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = 
> -3 };
> > int mg_json_get(struct mg_str json, const char *path, int *toklen);
> >
> > +struct mg_str mg_json_get_tok(struct mg_str json, const char *path);
> > bool mg_json_get_num(struct mg_str json, const char *path, double *v);
> > bool mg_json_get_bool(struct mg_str json, const char *path, bool *v);
> > long mg_json_get_long(struct mg_str json, const char *path, long dflt);
> > @@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json, const 
> char *path);
> > char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
> > char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
> >
> > +bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
> > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
> > + struct mg_str *val);
> > +
> >
> >
> >
> > @@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *, const char 
> *fmt, va_list *ap);
> > void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
> > void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list 
> *);
> > void mg_rpc_list(struct mg_rpc_req *r);
> > +// Copyright (c) 2023 Cesanta Software Limited
> > +// All rights reserved
> >
> >
> >
> >
> >
> > -struct mip_if; // MIP network interface
> > +#define MG_OTA_NONE 0 // No OTA support
> > +#define MG_OTA_FLASH 1 // OTA via an internal flash
> > +#define MG_OTA_ESP32 2 // ESP32 OTA implementation
> > +#define MG_OTA_CUSTOM 100 // Custom implementation
> >
> > -struct mip_driver {
> > - bool (*init)(struct mip_if *); // Initialise driver
> > - size_t (*tx)(const void *, size_t, struct mip_if *); // Transmit frame
> > - size_t (*rx)(void *buf, size_t len, struct mip_if *); // Receive frame 
> (poll)
> > - bool (*up)(struct mip_if *); // Up/down status
> > -};
> > +#ifndef MG_OTA
> > +#define MG_OTA MG_OTA_NONE
> > +#endif
> > +
> > +#if defined(__GNUC__) && !defined(__APPLE__)
> > +#define MG_IRAM __attribute__((section(".iram")))
> > +#else
> > +#define MG_IRAM
> > +#endif
> >
> > -// Receive queue - single producer, single consumer queue. 
> Interrupt-based
> > -// drivers copy received frames to the queue in interrupt context. 
> mip_poll()
> > -// function runs in event loop context, reads from the queue
> > -struct queue {
> > - uint8_t *buf;
> > - size_t len;
> > - volatile size_t tail, head;
> > +// Firmware update API
> > +bool mg_ota_begin(size_t new_firmware_size); // Start writing
> > +bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned 
> to 1k
> > +bool mg_ota_end(void); // Stop writing
> > +
> > +enum {
> > + MG_OTA_UNAVAILABLE = 0, // No OTA information is present
> > + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA
> > + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback
> > + MG_OTA_COMMITTED = 3 // The firmware is good
> > };
> > +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
> > +
> > +int mg_ota_status(int firmware); // Return firmware status MG_OTA_*
> > +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
> > +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX 
> UTC epoch
> > +size_t mg_ota_size(int firmware); // Firmware size
> > +
> > +bool mg_ota_commit(void); // Commit current firmware
> > +bool mg_ota_rollback(void); // Rollback to the previous firmware
> > +MG_IRAM void mg_ota_boot(void); // Bootloader function
> > +// Copyright (c) 2023 Cesanta Software Limited
> > +// All rights reserved
> >
> > -#define MIP_ARP_ENTRIES 5 // Number of ARP cache entries. Maximum 21
> > -#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES) // ARP cache size
> > +
> > +
> > +
> > +
> > +#define MG_DEVICE_NONE 0 // Dummy system
> > +
> > +#define MG_DEVICE_STM32H5 1 // STM32 H5
> > +#define MG_DEVICE_STM32H7 2 // STM32 H7
> > +#define MG_DEVICE_CH32V307 100 // WCH CH32V307
> > +#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
> > +#define MG_DEVICE_RT1020 300 // IMXRT1020
> > +#define MG_DEVICE_RT1060 301 // IMXRT1060
> > +#define MG_DEVICE_CUSTOM 1000 // Custom implementation
> > +
> > +#ifndef MG_DEVICE
> > +#define MG_DEVICE MG_DEVICE_NONE
> > +#endif
> > +
> > +// Flash information
> > +void *mg_flash_start(void); // Return flash start address
> > +size_t mg_flash_size(void); // Return flash size
> > +size_t mg_flash_sector_size(void); // Return flash sector size
> > +size_t mg_flash_write_align(void); // Return flash write align, minimum 
> 4
> > +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
> > +
> > +// Write, erase, swap bank
> > +bool mg_flash_write(void *addr, const void *buf, size_t len);
> > +bool mg_flash_erase(void *sector);
> > +bool mg_flash_swap_bank(void);
> > +
> > +// Convenience functions to store data on a flash sector with wear 
> levelling
> > +// If `sector` is NULL, then the last sector of flash is used
> > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len);
> > +bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t 
> len);
> > +
> > +void mg_device_reset(void); // Reboot device immediately
> > +
> > +
> > +
> > +
> > +
> > +
> > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> > +struct mg_tcpip_if; // Mongoose TCP/IP network interface
> > +
> > +struct mg_tcpip_driver {
> > + bool (*init)(struct mg_tcpip_if *); // Init driver
> > + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit 
> frame
> > + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Receive 
> frame
> > + bool (*up)(struct mg_tcpip_if *); // Up/down status
> > +};
> >
> > // Network interface
> > -struct mip_if {
> > - uint8_t mac[6]; // MAC address. Must be set to a valid MAC
> > - uint32_t ip, mask, gw; // IP address, mask, default gateway
> > - struct mg_str rx; // Output (TX) buffer
> > - struct mg_str tx; // Input (RX) buffer
> > - bool enable_dhcp_client; // Enable DCHP client
> > - bool enable_dhcp_server; // Enable DCHP server
> > - struct mip_driver *driver; // Low level driver
> > - void *driver_data; // Driver-specific data
> > - struct mg_mgr *mgr; // Mongoose event manager
> > - struct queue queue; // Set queue.len for interrupt based drivers
> > +struct mg_tcpip_if {
> > + uint8_t mac[6]; // MAC address. Must be set to a valid MAC
> > + uint32_t ip, mask, gw; // IP address, mask, default gateway
> > + struct mg_str tx; // Output (TX) buffer
> > + bool enable_dhcp_client; // Enable DCHP client
> > + bool enable_dhcp_server; // Enable DCHP server
> > + bool enable_get_gateway; // DCHP server sets client as gateway
> > + bool enable_crc32_check; // Do a CRC check on RX frames and strip it
> > + bool enable_mac_check; // Do a MAC check on RX frames
> > + struct mg_tcpip_driver *driver; // Low level driver
> > + void *driver_data; // Driver-specific data
> > + struct mg_mgr *mgr; // Mongoose event manager
> > + struct mg_queue recv_queue; // Receive queue
> > + uint16_t mtu; // Interface MTU
> > +#define MG_TCPIP_MTU_DEFAULT 1500
> >
> > // Internal state, user can use it but should not change it
> > - uint64_t now; // Current time
> > - uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
> > - uint64_t lease_expire; // Lease expiration time
> > - uint8_t arp_cache[MIP_ARP_CS]; // Each entry is 12 bytes
> > - uint16_t eport; // Next ephemeral port
> > - uint16_t dropped; // Number of dropped frames
> > - uint8_t state; // Current state
> > -#define MIP_STATE_DOWN 0 // Interface is down
> > -#define MIP_STATE_UP 1 // Interface is up
> > -#define MIP_STATE_READY 2 // Interface is up and has IP
> > + uint8_t gwmac[6]; // Router's MAC
> > + uint64_t now; // Current time
> > + uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
> > + uint64_t lease_expire; // Lease expiration time, in ms
> > + uint16_t eport; // Next ephemeral port
> > + volatile uint32_t ndrop; // Number of received, but dropped frames
> > + volatile uint32_t nrecv; // Number of received frames
> > + volatile uint32_t nsent; // Number of transmitted frames
> > + volatile uint32_t nerr; // Number of driver errors
> > + uint8_t state; // Current state
> > +#define MG_TCPIP_STATE_DOWN 0 // Interface is down
> > +#define MG_TCPIP_STATE_UP 1 // Interface is up
> > +#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP
> > +#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned
> > };
> >
> > -void mip_init(struct mg_mgr *, struct mip_if *);
> > -void mip_free(struct mip_if *);
> > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
> > -size_t mip_qread(void *buf, struct mip_if *ifp);
> > -// conveniency rx function for IRQ-driven drivers
> > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
> > -
> > -extern struct mip_driver mip_driver_stm32;
> > -extern struct mip_driver mip_driver_w5500;
> > -extern struct mip_driver mip_driver_tm4c;
> > +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
> > +void mg_tcpip_free(struct mg_tcpip_if *);
> > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
> > +
> > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_same54;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_ra;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
> > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
> >
> > // Drivers that require SPI, can use this SPI abstraction
> > -struct mip_spi {
> > +struct mg_tcpip_spi {
> > void *spi; // Opaque SPI bus descriptor
> > void (*begin)(void *); // SPI begin: slave select low
> > void (*end)(void *); // SPI end: slave select high
> > uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read 
> reply
> > };
> > +#endif
> >
> > -#ifdef MIP_QPROFILE
> > -enum {
> > - QP_IRQTRIGGERED = 0, // payload is number of interrupts so far
> > - QP_FRAMEPUSHED, // available space in the frame queue
> > - QP_FRAMEPOPPED, // available space in the frame queue
> > - QP_FRAMEDONE, // available space in the frame queue
> > - QP_FRAMEDROPPED, // number of dropped frames
> > - QP_QUEUEOVF // profiling queue is full, payload is number of frame 
> drops
> > +
> > +
> > +// Macros to record timestamped events that happens with a connection.
> > +// They are saved into a c->prof IO buffer, each event is a name and a 
> 32-bit
> > +// timestamp in milliseconds since connection init time.
> > +//
> > +// Test (run in two separate terminals):
> > +// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
> > +// curl localhost:8000
> > +// Output:
> > +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile:
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE
> > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE
> > +//
> > +// Usage:
> > +// Enable profiling by setting MG_ENABLE_PROFILE=1
> > +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to 
> measure
> > +
> > +#if MG_ENABLE_PROFILE
> > +struct mg_profitem {
> > + const char *name; // Event name
> > + uint32_t timestamp; // Milliseconds since connection creation 
> (MG_EV_OPEN)
> > };
> >
> > -void qp_mark(unsigned int type, int len);
> > -void qp_log(void); // timestamp, type, payload
> > -void qp_init(void);
> > +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items wih to 
> realloc
> > +
> > +// Adding a profile item to the c->prof. Must be as fast as possible.
> > +// Reallocation of the c->prof iobuf is not desirable here, that's why 
> we
> > +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
> > +// This macro just inits and copies 8 bytes, and calls mg_millis(),
> > +// which should be fast enough.
> > +#define MG_PROF_ADD(c, name_) \
> > + do { \
> > + struct mg_iobuf *io = &c->prof; \
> > + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \
> > + struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \
> > + mg_iobuf_add(io, io->len, &item, sizeof(item)); \
> > + } while (0)
> > +
> > +// Initialising profile for a new connection. Not time sensitive
> > +#define MG_PROF_INIT(c) \
> > + do { \
> > + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
> > + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \
> > + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \
> > + } while (0)
> > +
> > +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
> > +
> > +// Dumping the profile. Not time sensitive
> > +#define MG_PROF_DUMP(c) \
> > + do { \
> > + struct mg_iobuf *io = &c->prof; \
> > + struct mg_profitem *p = (struct mg_profitem *) io->buf; \
> > + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \
> > + MG_INFO(("%lu profile:", c->id)); \
> > + while (p < e) { \
> > + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
> > + p++; \
> > + } \
> > + } while (0)
> > +
> > #else
> > -#define qp_mark(a, b)
> > +#define MG_PROF_INIT(c)
> > +#define MG_PROF_FREE(c)
> > +#define MG_PROF_ADD(c, name)
> > +#define MG_PROF_DUMP(c)
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && 
> MG_ENABLE_DRIVER_CMSIS
> > +
> > +#include "Driver_ETH_MAC.h" // keep this include
> > +#include "Driver_ETH_PHY.h" // keep this include
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && 
> MG_ENABLE_DRIVER_IMXRT
> > +
> > +struct mg_tcpip_driver_imxrt_data {
> > + // MDC clock divider. MDC clock is derived from IPS Bus clock 
> (ipg_clk),
> > + // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz
> > + // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
> > + // ipg_clk mdc_cr VALUE
> > + // --------------------------
> > + // -1 <-- TODO() tell driver to guess the value
> > + // 25 MHz 4
> > + // 33 MHz 6
> > + // 40 MHz 7
> > + // 50 MHz 9
> > + // 66 MHz 13
> > + int mdc_cr; // Valid values: -1 to 63
> > +
> > + uint8_t phy_addr; // PHY address
> > +};
> > +
> > +#ifndef MG_TCPIP_PHY_ADDR
> > +#define MG_TCPIP_PHY_ADDR 2
> > +#endif
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 24
> > +#endif
> > +
> > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> > + do { \
> > + static struct mg_tcpip_driver_imxrt_data driver_data_; \
> > + static struct mg_tcpip_if mif_; \
> > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> > + mif_.ip = MG_TCPIP_IP; \
> > + mif_.mask = MG_TCPIP_MASK; \
> > + mif_.gw = MG_TCPIP_GW; \
> > + mif_.driver = &mg_tcpip_driver_imxrt; \
> > + mif_.driver_data = &driver_data_; \
> > + MG_SET_MAC_ADDRESS(mif_.mac); \
> > + mg_tcpip_init(mgr, &mif_); \
> > + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
> > + } while (0)
> > +
> > #endif
> >
> >
> > -struct mip_driver_stm32_data {
> > +
> > +
> > +struct mg_phy {
> > + uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
> > + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
> > +};
> > +
> > +// PHY configuration settings, bitmask
> > +enum {
> > + MG_PHY_LEDS_ACTIVE_HIGH =
> > + (1 << 0), // Set if PHY LEDs are connected to ground
> > + MG_PHY_CLOCKS_MAC =
> > + (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
> > +};
> > +
> > +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
> > +
> > +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
> > +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
> > + uint8_t *speed);
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && 
> MG_ENABLE_DRIVER_RA
> > +
> > +struct mg_tcpip_driver_ra_data {
> > + // MDC clock "divider". MDC clock is software generated,
> > + uint32_t clock; // core clock frequency in Hz
> > + uint16_t irqno; // IRQn, R_ICU->IELSR[irqno]
> > + uint8_t phy_addr; // PHY address
> > +};
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && 
> MG_ENABLE_DRIVER_SAME54
> > +
> > +struct mg_tcpip_driver_same54_data {
> > + int mdc_cr;
> > +};
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 5
> > +#endif
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> > + MG_ENABLE_DRIVER_STM32F
> > +
> > +struct mg_tcpip_driver_stm32f_data {
> > // MDC clock divider. MDC clock is derived from HCLK, must not exceed 
> 2.5MHz
> > // HCLK range DIVIDER mdc_cr VALUE
> > // -------------------------------------
> > @@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
> > // 216-310 MHz HCLK/124 5
> > // 110, 111 Reserved
> > int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> > +
> > + uint8_t phy_addr; // PHY address
> > +};
> > +
> > +#ifndef MG_TCPIP_PHY_ADDR
> > +#define MG_TCPIP_PHY_ADDR 0
> > +#endif
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 4
> > +#endif
> > +
> > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> > + do { \
> > + static struct mg_tcpip_driver_stm32f_data driver_data_; \
> > + static struct mg_tcpip_if mif_; \
> > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> > + mif_.ip = MG_TCPIP_IP; \
> > + mif_.mask = MG_TCPIP_MASK; \
> > + mif_.gw = MG_TCPIP_GW; \
> > + mif_.driver = &mg_tcpip_driver_stm32f; \
> > + mif_.driver_data = &driver_data_; \
> > + MG_SET_MAC_ADDRESS(mif_.mac); \
> > + mg_tcpip_init(mgr, &mif_); \
> > + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
> > + } while (0)
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> > + MG_ENABLE_DRIVER_STM32H
> > +
> > +struct mg_tcpip_driver_stm32h_data {
> > + // MDC clock divider. MDC clock is derived from HCLK, must not exceed 
> 2.5MHz
> > + // HCLK range DIVIDER mdc_cr VALUE
> > + // -------------------------------------
> > + // -1 <-- tell driver to guess the value
> > + // 60-100 MHz HCLK/42 0
> > + // 100-150 MHz HCLK/62 1
> > + // 20-35 MHz HCLK/16 2
> > + // 35-60 MHz HCLK/26 3
> > + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI
> > + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI
> > + // 110, 111 Reserved
> > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> > +
> > + uint8_t phy_addr; // PHY address
> > + uint8_t phy_conf; // PHY config
> > };
> >
> > +#ifndef MG_TCPIP_PHY_ADDR
> > +#define MG_TCPIP_PHY_ADDR 0
> > +#endif
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 4
> > +#endif
> > +
> > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> > + do { \
> > + static struct mg_tcpip_driver_stm32h_data driver_data_; \
> > + static struct mg_tcpip_if mif_; \
> > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> > + mif_.ip = MG_TCPIP_IP; \
> > + mif_.mask = MG_TCPIP_MASK; \
> > + mif_.gw = MG_TCPIP_GW; \
> > + mif_.driver = &mg_tcpip_driver_stm32h; \
> > + mif_.driver_data = &driver_data_; \
> > + MG_SET_MAC_ADDRESS(mif_.mac); \
> > + mg_tcpip_init(mgr, &mif_); \
> > + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
> > + } while (0)
> > +
> > +#endif
> > +
> >
> > -struct mip_driver_tm4c_data {
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && 
> MG_ENABLE_DRIVER_TM4C
> > +
> > +struct mg_tcpip_driver_tm4c_data {
> > // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 
> 2.5MHz
> > // SYSCLK range DIVIDER mdc_cr VALUE
> > // -------------------------------------
> > @@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
> > int mdc_cr; // Valid values: -1, 0, 1, 2, 3
> > };
> >
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 1
> > +#endif
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && 
> MG_ENABLE_DRIVER_W5500
> > +
> > +#undef MG_ENABLE_TCPIP_DRIVER_INIT
> > +#define MG_ENABLE_TCPIP_DRIVER_INIT 0
> > +
> > +#endif
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && 
> MG_ENABLE_DRIVER_XMC7
> > +
> > +struct mg_tcpip_driver_xmc7_data {
> > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> > + uint8_t phy_addr;
> > +};
> > +
> > +#ifndef MG_TCPIP_PHY_ADDR
> > +#define MG_TCPIP_PHY_ADDR 0
> > +#endif
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 3
> > +#endif
> > +
> > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> > + do { \
> > + static struct mg_tcpip_driver_xmc7_data driver_data_; \
> > + static struct mg_tcpip_if mif_; \
> > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> > + mif_.ip = MG_TCPIP_IP; \
> > + mif_.mask = MG_TCPIP_MASK; \
> > + mif_.gw = MG_TCPIP_GW; \
> > + mif_.driver = &mg_tcpip_driver_xmc7; \
> > + mif_.driver_data = &driver_data_; \
> > + MG_SET_MAC_ADDRESS(mif_.mac); \
> > + mg_tcpip_init(mgr, &mif_); \
> > + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
> > + } while (0)
> > +
> > +#endif
> > +
> > +
> > +
> > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && 
> MG_ENABLE_DRIVER_XMC
> > +
> > +struct mg_tcpip_driver_xmc_data {
> > + // 13.2.8.1 Station Management Functions
> > + // MDC clock divider (). MDC clock is derived from ETH MAC clock
> > + // It must not exceed 2.5MHz
> > + // ETH Clock range DIVIDER mdc_cr VALUE
> > + // --------------------------------------------
> > + // -1 <-- tell driver to guess the value
> > + // 60-100 MHz ETH Clock/42 0
> > + // 100-150 MHz ETH Clock/62 1
> > + // 20-35 MHz ETH Clock/16 2
> > + // 35-60 MHz ETH Clock/26 3
> > + // 150-250 MHz ETH Clock/102 4
> > + // 250-300 MHz ETH Clock/124 5
> > + // 110, 111 Reserved
> > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> > + uint8_t phy_addr;
> > +};
> > +
> > +#ifndef MG_TCPIP_PHY_ADDR
> > +#define MG_TCPIP_PHY_ADDR 0
> > +#endif
> > +
> > +#ifndef MG_DRIVER_MDC_CR
> > +#define MG_DRIVER_MDC_CR 4
> > +#endif
> > +
> > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> > + do { \
> > + static struct mg_tcpip_driver_xmc_data driver_data_; \
> > + static struct mg_tcpip_if mif_; \
> > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> > + mif_.ip = MG_TCPIP_IP; \
> > + mif_.mask = MG_TCPIP_MASK; \
> > + mif_.gw = MG_TCPIP_GW; \
> > + mif_.driver = &mg_tcpip_driver_xmc; \
> > + mif_.driver_data = &driver_data_; \
> > + MG_SET_MAC_ADDRESS(mif_.mac); \
> > + mg_tcpip_init(mgr, &mif_); \
> > + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
> > + } while (0)
> > +
> > +#endif
> > +
> > #ifdef __cplusplus
> > }
> > #endif
>
Stefano Babic July 9, 2024, 12:21 p.m. UTC | #3
Hi Michael,

On 09.07.24 13:31, Michael Glembotzki wrote:
> Problem still exists with V3. The patch files I created with format
> patch are OK, but when sending it to Patchwork something seems to break
> with the header.

It depends how you send the patch. You should still use git, "git
send-email". This guarantees thatmails are not damaged by your mailer.

Best regards,
Stefano

> I could post a github link, or do you have any idea
> what the problem could be?
>
> Best regards
> Michael
> Stefano Babic schrieb am Sonntag, 7. Juli 2024 um 11:41:01 UTC+2:
>
>     Hi Michael,
>
>     I wanted to work on this, but I cannot apply clean, git am reports a
>     malformed patch. Can you check and repost ? Thanks.
>
>     Best regards,
>     Stefano
>
>     On 15.06.24 21:11, Michael Glembotzki wrote:
>      > mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05
>      >
>      > Signed-off-by: Michael Glembotzki <Michael.G...@iris-sensing.com>
>      > ---
>      > mongoose/mongoose.c | 21614
>     +++++++++++++++++++++++++++++++-----------
>      > mongoose/mongoose.h | 2003 +++-
>      > 2 files changed, 17594 insertions(+), 6023 deletions(-)
>      >
>      > diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
>      > index 83d8fe18..3fc0e14a 100644
>      > --- a/mongoose/mongoose.c
>      > +++ b/mongoose/mongoose.c
>      > @@ -1,5 +1,5 @@
>      > // Copyright (c) 2004-2013 Sergey Lyubka
>      > -// Copyright (c) 2013-2022 Cesanta Software Limited
>      > +// Copyright (c) 2013-2024 Cesanta Software Limited
>      > // All rights reserved
>      > //
>      > // This software is dual-licensed: you can redistribute it and/or
>     modify
>      > @@ -15,7 +15,7 @@
>      > // Alternatively, you can license this software under a commercial
>      > // license, as set out in https://www.mongoose.ws/licensing/
>     <https://www.mongoose.ws/licensing/>
>      > //
>      > -// SPDX-License-Identifier: GPL-2.0-only
>      > +// SPDX-License-Identifier: GPL-2.0-only or commercial
>      >
>      > #include "mongoose.h"
>      >
>      > @@ -24,8 +24,7 @@
>      > #endif
>      >
>      >
>      > -
>      > -static int mg_b64idx(int c) {
>      > +static int mg_base64_encode_single(int c) {
>      > if (c < 26) {
>      > return c + 'A';
>      > } else if (c < 52) {
>      > @@ -37,7 +36,7 @@ static int mg_b64idx(int c) {
>      > }
>      > }
>      >
>      > -static int mg_b64rev(int c) {
>      > +static int mg_base64_decode_single(int c) {
>      > if (c >= 'A' && c <= 'Z') {
>      > return c - 'A';
>      > } else if (c >= 'a' && c <= 'z') {
>      > @@ -55,24 +54,24 @@ static int mg_b64rev(int c) {
>      > }
>      > }
>      >
>      > -int mg_base64_update(unsigned char ch, char *to, int n) {
>      > - int rem = (n & 3) % 3;
>      > +size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
>      > + unsigned long rem = (n & 3) % 3;
>      > if (rem == 0) {
>      > - to[n] = (char) mg_b64idx(ch >> 2);
>      > + to[n] = (char) mg_base64_encode_single(ch >> 2);
>      > to[++n] = (char) ((ch & 3) << 4);
>      > } else if (rem == 1) {
>      > - to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
>      > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
>      > to[++n] = (char) ((ch & 15) << 2);
>      > } else {
>      > - to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
>      > - to[++n] = (char) mg_b64idx(ch & 63);
>      > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
>      > + to[++n] = (char) mg_base64_encode_single(ch & 63);
>      > n++;
>      > }
>      > return n;
>      > }
>      >
>      > -int mg_base64_final(char *to, int n) {
>      > - int saved = n;
>      > +size_t mg_base64_final(char *to, size_t n) {
>      > + size_t saved = n;
>      > // printf("---[%.*s]\n", n, to);
>      > if (n & 3) n = mg_base64_update(0, to, n);
>      > if ((saved & 3) == 2) n--;
>      > @@ -82,20 +81,27 @@ int mg_base64_final(char *to, int n) {
>      > return n;
>      > }
>      >
>      > -int mg_base64_encode(const unsigned char *p, int n, char *to) {
>      > - int i, len = 0;
>      > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
>     *to, size_t dl) {
>      > + size_t i, len = 0;
>      > + if (dl > 0) to[0] = '\0';
>      > + if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
>      > for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
>      > len = mg_base64_final(to, len);
>      > return len;
>      > }
>      >
>      > -int mg_base64_decode(const char *src, int n, char *dst) {
>      > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
>     size_t dl) {
>      > const char *end = src == NULL ? NULL : src + n; // Cannot add to
>     NULL
>      > - int len = 0;
>      > + size_t len = 0;
>      > + if (dl < n / 4 * 3 + 1) goto fail;
>      > while (src != NULL && src + 3 < end) {
>      > - int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c =
>     mg_b64rev(src[2]),
>      > - d = mg_b64rev(src[3]);
>      > - if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0)
>     return 0;
>      > + int a = mg_base64_decode_single(src[0]),
>      > + b = mg_base64_decode_single(src[1]),
>      > + c = mg_base64_decode_single(src[2]),
>      > + d = mg_base64_decode_single(src[3]);
>      > + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
>      > + goto fail;
>      > + }
>      > dst[len++] = (char) ((a << 2) | (b >> 4));
>      > if (src[2] != '=') {
>      > dst[len++] = (char) ((b << 4) | (c >> 2));
>      > @@ -105,1942 +111,2915 @@ int mg_base64_decode(const char *src,
>     int n, char *dst) {
>      > }
>      > dst[len] = '\0';
>      > return len;
>      > +fail:
>      > + if (dl > 0) dst[0] = '\0';
>      > + return 0;
>      > }
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/dns.c"
>      > +#line 1 "src/device_ch32v307.c"
>      > #endif
>      >
>      >
>      >
>      > +#if MG_DEVICE == MG_DEVICE_CH32V307
>      > +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
>     <https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html>
>      >
>      > +#define FLASH_BASE 0x40022000
>      > +#define FLASH_ACTLR (FLASH_BASE + 0)
>      > +#define FLASH_KEYR (FLASH_BASE + 4)
>      > +#define FLASH_OBKEYR (FLASH_BASE + 8)
>      > +#define FLASH_STATR (FLASH_BASE + 12)
>      > +#define FLASH_CTLR (FLASH_BASE + 16)
>      > +#define FLASH_ADDR (FLASH_BASE + 20)
>      > +#define FLASH_OBR (FLASH_BASE + 28)
>      > +#define FLASH_WPR (FLASH_BASE + 32)
>      >
>      > -
>      > -
>      > -struct dns_data {
>      > - struct dns_data *next;
>      > - struct mg_connection *c;
>      > - uint64_t expire;
>      > - uint16_t txnid;
>      > -};
>      > -
>      > -static void mg_sendnsreq(struct mg_connection *, struct mg_str
>     *, int,
>      > - struct mg_dns *, bool);
>      > -
>      > -static void mg_dns_free(struct mg_connection *c, struct dns_data
>     *d) {
>      > - LIST_DELETE(struct dns_data,
>      > - (struct dns_data **) &c->mgr->active_dns_requests, d);
>      > - free(d);
>      > +void *mg_flash_start(void) {
>      > + return (void *) 0x08000000;
>      > }
>      > -
>      > -void mg_resolve_cancel(struct mg_connection *c) {
>      > - struct dns_data *tmp, *d = (struct dns_data *)
>     c->mgr->active_dns_requests;
>      > - for (; d != NULL; d = tmp) {
>      > - tmp = d->next;
>      > - if (d->c == c) mg_dns_free(c, d);
>      > +size_t mg_flash_size(void) {
>      > + return 480 * 1024; // First 320k is 0-wait
>      > +}
>      > +size_t mg_flash_sector_size(void) {
>      > + return 4096;
>      > +}
>      > +size_t mg_flash_write_align(void) {
>      > + return 4;
>      > +}
>      > +int mg_flash_bank(void) {
>      > + return 0;
>      > +}
>      > +void mg_device_reset(void) {
>      > + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; //
>     NVIC_SystemReset()
>      > +}
>      > +static void flash_unlock(void) {
>      > + static bool unlocked;
>      > + if (unlocked == false) {
>      > + MG_REG(FLASH_KEYR) = 0x45670123;
>      > + MG_REG(FLASH_KEYR) = 0xcdef89ab;
>      > + unlocked = true;
>      > }
>      > }
>      > +static void flash_wait(void) {
>      > + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
>      > +}
>      >
>      > -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
>     len, size_t ofs,
>      > - char *to, size_t tolen, size_t j,
>      > - int depth) {
>      > - size_t i = 0;
>      > - if (tolen > 0 && depth == 0) to[0] = '\0';
>      > - if (depth > 5) return 0;
>      > - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
>     + 1]));
>      > - while (ofs + i + 1 < len) {
>      > - size_t n = s[ofs + i];
>      > - if (n == 0) {
>      > - i++;
>      > - break;
>      > - }
>      > - if (n & 0xc0) {
>      > - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
>     len
>      > - // MG_INFO(("PTR %lx", (unsigned long) ptr));
>      > - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
>      > - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
>     == 0)
>      > - return 0;
>      > - i += 2;
>      > - break;
>      > - }
>      > - if (ofs + i + n + 1 >= len) return 0;
>      > - if (j > 0) {
>      > - if (j < tolen) to[j] = '.';
>      > - j++;
>      > - }
>      > - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
>      > - j += n;
>      > - i += n + 1;
>      > - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
>      > - // MG_INFO(("--> [%s]", to));
>      > - }
>      > - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
>     is nul-term
>      > - return i;
>      > +bool mg_flash_erase(void *addr) {
>      > + //MG_INFO(("%p", addr));
>      > + flash_unlock();
>      > + flash_wait();
>      > + MG_REG(FLASH_ADDR) = (uint32_t) addr;
>      > + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT;
>      > + flash_wait();
>      > + return true;
>      > }
>      >
>      > -static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
>     size_t ofs,
>      > - char *dst, size_t dstlen) {
>      > - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
>      > +static bool is_page_boundary(const void *addr) {
>      > + uint32_t val = (uint32_t) addr;
>      > + return (val & (mg_flash_sector_size() - 1)) == 0;
>      > }
>      >
>      > -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
>      > - bool is_question, struct mg_dns_rr *rr) {
>      > - const uint8_t *s = buf + ofs, *e = &buf[len];
>      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>      > + //MG_INFO(("%p %p %lu", addr, buf, len));
>      > + //mg_hexdump(buf, len);
>      > + flash_unlock();
>      > + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
>      > + uint16_t *dst = (uint16_t *) addr;
>      > + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG
>      > + //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
>      > + while (src < end) {
>      > + if (is_page_boundary(dst)) mg_flash_erase(dst);
>      > + *dst++ = *src++;
>      > + flash_wait();
>      > + }
>      > + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG
>      > + return true;
>      > +}
>      > +#endif
>      >
>      > - memset(rr, 0, sizeof(*rr));
>      > - if (len < sizeof(struct mg_dns_header)) return 0; // Too small
>      > - if (len > 512) return 0; // Too large, we don't expect that
>      > - if (s >= e) return 0; // Overflow
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/device_dummy.c"
>      > +#endif
>      >
>      > - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
>     NULL, 0)) == 0)
>      > - return 0;
>      > - s += rr->nlen + 4;
>      > - if (s > e) return 0;
>      > - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
>      > - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>      > - if (is_question) return (size_t) (rr->nlen + 4);
>      >
>      > - s += 6;
>      > - if (s > e) return 0;
>      > - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>      > - if (s + rr->alen > e) return 0;
>      > - return (size_t) (rr->nlen + rr->alen + 10);
>      > +#if MG_DEVICE == MG_DEVICE_NONE
>      > +void *mg_flash_start(void) {
>      > + return NULL;
>      > +}
>      > +size_t mg_flash_size(void) {
>      > + return 0;
>      > +}
>      > +size_t mg_flash_sector_size(void) {
>      > + return 0;
>      > +}
>      > +size_t mg_flash_write_align(void) {
>      > + return 0;
>      > +}
>      > +int mg_flash_bank(void) {
>      > + return 0;
>      > +}
>      > +bool mg_flash_erase(void *location) {
>      > + (void) location;
>      > + return false;
>      > +}
>      > +bool mg_flash_swap_bank(void) {
>      > + return true;
>      > +}
>      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>      > + (void) addr, (void) buf, (void) len;
>      > + return false;
>      > +}
>      > +void mg_device_reset(void) {
>      > }
>      > +#endif
>      >
>      > -bool mg_dns_parse(const uint8_t *buf, size_t len, struct
>     mg_dns_message *dm) {
>      > - const struct mg_dns_header *h = (struct mg_dns_header *) buf;
>      > - struct mg_dns_rr rr;
>      > - size_t i, n, ofs = sizeof(*h);
>      > - memset(dm, 0, sizeof(*dm));
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/device_flash.c"
>      > +#endif
>      >
>      > - if (len < sizeof(*h)) return 0; // Too small, headers dont fit
>      > - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
>      > - if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity
>      > - dm->txnid = mg_ntohs(h->txnid);
>      >
>      > - for (i = 0; i < mg_ntohs(h->num_questions); i++) {
>      > - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
>     return false;
>      > - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
>      > - ofs += n;
>      > +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE ==
>     MG_DEVICE_STM32H5 || \
>      > + MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
>      > +// Flash can be written only if it is erased. Erased flash is
>     0xff (all bits 1)
>      > +// Writes must be mg_flash_write_align() - aligned. Thus if we
>     want to save an
>      > +// object, we pad it at the end for alignment.
>      > +//
>      > +// Objects in the flash sector are stored sequentially:
>      > +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size
>     | ......
>      > +//
>      > +// In order to get to the next object, read its size, then align
>     up.
>      > +
>      > +// Traverse the list of saved objects
>      > +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t
>     *size) {
>      > + size_t aligned_size = 0, align = mg_flash_write_align(), left =
>     end - p;
>      > + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
>      > + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
>      > + if (size) *size = (size_t) p32[0];
>      > + if (key) *key = p32[1];
>      > + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
>      > + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail
>      > }
>      > - for (i = 0; i < mg_ntohs(h->num_answers); i++) {
>      > - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
>     return false;
>      > - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
>     rr.aclass,
>      > - // dm->name));
>      > - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
>      > - ofs += n;
>      > -
>      > - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
>      > - dm->addr.is_ip6 = false;
>      > - memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
>      > - dm->resolved = true;
>      > - break; // Return success
>      > - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
>      > - dm->addr.is_ip6 = true;
>      > - memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
>      > - dm->resolved = true;
>      > - break; // Return success
>      > + return aligned_size;
>      > +}
>      > +
>      > +// Return the last sector of Bank 2
>      > +static char *flash_last_sector(void) {
>      > + size_t ss = mg_flash_sector_size(), size = mg_flash_size();
>      > + char *base = (char *) mg_flash_start(), *last = base + size - ss;
>      > + if (mg_flash_bank() == 2) last -= size / 2;
>      > + return last;
>      > +}
>      > +
>      > +// Find a saved object with a given key
>      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>     len) {
>      > + char *base = (char *) mg_flash_start(), *s = (char *) sector,
>     *res = NULL;
>      > + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
>      > + bool ok = false;
>      > + if (s == NULL) s = flash_last_sector();
>      > + if (s < base || s >= base + mg_flash_size()) {
>      > + MG_ERROR(("%p is outsize of flash", sector));
>      > + } else if (((s - base) % ss) != 0) {
>      > + MG_ERROR(("%p is not a sector boundary", sector));
>      > + } else {
>      > + uint32_t k, scanned = 0;
>      > + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
>      > + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k,
>     key));
>      > + // mg_hexdump(s + ofs, n);
>      > + if (k == key && sz == len) {
>      > + res = s + ofs + sizeof(uint32_t) * 2;
>      > + memcpy(buf, res, len); // Copy object
>      > + ok = true; // Keep scanning for the newer versions of it
>      > + }
>      > + ofs += n, scanned++;
>      > }
>      > + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key,
>     res));
>      > }
>      > - return true;
>      > + return ok;
>      > }
>      >
>      > -static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
>      > - void *fn_data) {
>      > - struct dns_data *d, *tmp;
>      > - if (ev == MG_EV_POLL) {
>      > - uint64_t now = *(uint64_t *) ev_data;
>      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>     NULL;
>      > - d = tmp) {
>      > - tmp = d->next;
>      > - // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
>      > - if (now > d->expire) mg_error(d->c, "DNS timeout");
>      > +// For all saved objects in the sector, delete old versions of
>     objects
>      > +static void mg_flash_sector_cleanup(char *sector) {
>      > + // Buffer all saved objects into an IO buffer (backed by RAM)
>      > + // erase sector, and re-save them.
>      > + struct mg_iobuf io = {0, 0, 0, 2048};
>      > + size_t ss = mg_flash_sector_size();
>      > + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
>      > + uint32_t key;
>      > + // Traverse all objects
>      > + MG_DEBUG(("Cleaning up sector %p", sector));
>      > + while ((n = mg_flash_next(sector + ofs, sector + ss, &key,
>     &size)) > 0) {
>      > + // Delete an old copy of this object in the cache
>      > + for (size_t o = 0; o < io.len; o += size2 + hs) {
>      > + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
>      > + size2 = *(uint32_t *) (io.buf + o);
>      > + if (k == key) {
>      > + mg_iobuf_del(&io, o, size2 + hs);
>      > + break;
>      > + }
>      > }
>      > - } else if (ev == MG_EV_READ) {
>      > - struct mg_dns_message dm;
>      > - int resolved = 0;
>      > - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
>      > - MG_ERROR(("Unexpected DNS response:"));
>      > - mg_hexdump(c->recv.buf, c->recv.len);
>      > - } else {
>      > - // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
>      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>     NULL;
>      > - d = tmp) {
>      > - tmp = d->next;
>      > - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
>      > - if (dm.txnid != d->txnid) continue;
>      > - if (d->c->is_resolving) {
>      > - if (dm.resolved) {
>      > - dm.addr.port = d->c->rem.port; // Save port
>      > - d->c->rem = dm.addr; // Copy resolved address
>      > - MG_DEBUG(
>      > - ("%lu %s is %I", d->c->id, dm.name <http://dm.name>,
>     d->c->rem.is_ip6 ? 16 : 4,
>      > - d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
>      > - mg_connect_resolved(d->c);
>      > -#if MG_ENABLE_IPV6
>      > - } else if (dm.addr.is_ip6 == false && dm.name
>     <http://dm.name>[0] != '\0' &&
>      > - c->mgr->use_dns6 == false) {
>      > - struct mg_str x = mg_str(dm.name <http://dm.name>);
>      > - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
>      > -#endif
>      > - } else {
>      > - mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
>      > + // And add the new copy
>      > + mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
>      > + ofs += n;
>      > + }
>      > + // All objects are cached in RAM now
>      > + if (mg_flash_erase(sector)) { // Erase sector. If successful,
>      > + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse
>     cached objects
>      > + size = *(uint32_t *) (io.buf + ofs);
>      > + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
>      > + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to
>     flash
>      > + }
>      > + }
>      > + mg_iobuf_free(&io);
>      > +}
>      > +
>      > +// Save an object with a given key - append to the end of an
>     object list
>      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>     size_t len) {
>      > + char *base = (char *) mg_flash_start(), *s = (char *) sector;
>      > + size_t ss = mg_flash_sector_size(), ofs = 0, n;
>      > + bool ok = false;
>      > + if (s == NULL) s = flash_last_sector();
>      > + if (s < base || s >= base + mg_flash_size()) {
>      > + MG_ERROR(("%p is outsize of flash", sector));
>      > + } else if (((s - base) % ss) != 0) {
>      > + MG_ERROR(("%p is not a sector boundary", sector));
>      > + } else {
>      > + char ab[mg_flash_write_align()]; // Aligned write block
>      > + uint32_t hdr[2] = {(uint32_t) len, key};
>      > + size_t needed = sizeof(hdr) + len;
>      > + size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
>      > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
>     ofs += n;
>      > +
>      > + // If there is not enough space left, cleanup sector and
>     re-eval ofs
>      > + if (ofs + needed_aligned >= ss) {
>      > + mg_flash_sector_cleanup(s);
>      > + ofs = 0;
>      > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
>     ofs += n;
>      > + }
>      > +
>      > + if (ofs + needed_aligned <= ss) {
>      > + // Enough space to save this object
>      > + if (sizeof(ab) < sizeof(hdr)) {
>      > + // Flash write granularity is 32 bit or less, write with no
>     buffering
>      > + ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
>      > + if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
>      > + } else {
>      > + // Flash granularity is sizeof(hdr) or more. We need to save in
>      > + // 3 chunks: initial block, bulk, rest. This is because we have
>      > + // two memory chunks to write: hdr and buf, on aligned boundaries.
>      > + n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
>      > + if (n > len) n = len; // is
>      > + memset(ab, 0xff, sizeof(ab)); // initialized to all-one
>      > + memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
>      > + memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
>      > + MG_INFO(("saving initial block of %lu", sizeof(ab)));
>      > + ok = mg_flash_write(s + ofs, ab, sizeof(ab));
>      > + if (ok && len > n) {
>      > + size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
>      > + if (n2 > 0) {
>      > + MG_INFO(("saving bulk, %lu", n2));
>      > + ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
>      > + }
>      > + if (ok && len > n) {
>      > + size_t n3 = len - n - n2;
>      > + if (n3 > sizeof(ab)) n3 = sizeof(ab);
>      > + memset(ab, 0xff, sizeof(ab));
>      > + memcpy(ab, (char *) buf + n + n2, n3);
>      > + MG_INFO(("saving rest, %lu", n3));
>      > + ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
>      > }
>      > - } else {
>      > - MG_ERROR(("%lu already resolved", d->c->id));
>      > }
>      > - mg_dns_free(c, d);
>      > - resolved = 1;
>      > }
>      > - }
>      > - if (!resolved) MG_ERROR(("stray DNS reply"));
>      > - c->recv.len = 0;
>      > - } else if (ev == MG_EV_CLOSE) {
>      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>     NULL;
>      > - d = tmp) {
>      > - tmp = d->next;
>      > - mg_error(d->c, "DNS error");
>      > - mg_dns_free(c, d);
>      > + MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len,
>     needed_aligned,
>      > + s + ofs, key, ok));
>      > + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs -
>     needed_aligned));
>      > + } else {
>      > + MG_ERROR(("Sector is full"));
>      > }
>      > }
>      > - (void) fn_data;
>      > + return ok;
>      > +}
>      > +#else
>      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>     size_t len) {
>      > + (void) sector, (void) key, (void) buf, (void) len;
>      > + return false;
>      > +}
>      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>     len) {
>      > + (void) sector, (void) key, (void) buf, (void) len;
>      > + return false;
>      > }
>      > +#endif
>      >
>      > -static bool mg_dns_send(struct mg_connection *c, const struct
>     mg_str *name,
>      > - uint16_t txnid, bool ipv6) {
>      > - struct {
>      > - struct mg_dns_header header;
>      > - uint8_t data[256];
>      > - } pkt;
>      > - size_t i, n;
>      > - memset(&pkt, 0, sizeof(pkt));
>      > - pkt.header.txnid = mg_htons(txnid);
>      > - pkt.header.flags = mg_htons(0x100);
>      > - pkt.header.num_questions = mg_htons(1);
>      > - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
>      > - if (name->ptr[i] == '.' || i >= name->len) {
>      > - pkt.data[n] = (uint8_t) (i - n);
>      > - memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
>      > - n = i + 1;
>      > - }
>      > - if (i >= name->len) break;
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/device_imxrt.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
>      > +
>      > +struct mg_flexspi_lut_seq {
>      > + uint8_t seqNum;
>      > + uint8_t seqId;
>      > + uint16_t reserved;
>      > +};
>      > +
>      > +struct mg_flexspi_mem_config {
>      > + uint32_t tag;
>      > + uint32_t version;
>      > + uint32_t reserved0;
>      > + uint8_t readSampleClkSrc;
>      > + uint8_t csHoldTime;
>      > + uint8_t csSetupTime;
>      > + uint8_t columnAddressWidth;
>      > + uint8_t deviceModeCfgEnable;
>      > + uint8_t deviceModeType;
>      > + uint16_t waitTimeCfgCommands;
>      > + struct mg_flexspi_lut_seq deviceModeSeq;
>      > + uint32_t deviceModeArg;
>      > + uint8_t configCmdEnable;
>      > + uint8_t configModeType[3];
>      > + struct mg_flexspi_lut_seq configCmdSeqs[3];
>      > + uint32_t reserved1;
>      > + uint32_t configCmdArgs[3];
>      > + uint32_t reserved2;
>      > + uint32_t controllerMiscOption;
>      > + uint8_t deviceType;
>      > + uint8_t sflashPadType;
>      > + uint8_t serialClkFreq;
>      > + uint8_t lutCustomSeqEnable;
>      > + uint32_t reserved3[2];
>      > + uint32_t sflashA1Size;
>      > + uint32_t sflashA2Size;
>      > + uint32_t sflashB1Size;
>      > + uint32_t sflashB2Size;
>      > + uint32_t csPadSettingOverride;
>      > + uint32_t sclkPadSettingOverride;
>      > + uint32_t dataPadSettingOverride;
>      > + uint32_t dqsPadSettingOverride;
>      > + uint32_t timeoutInMs;
>      > + uint32_t commandInterval;
>      > + uint16_t dataValidTime[2];
>      > + uint16_t busyOffset;
>      > + uint16_t busyBitPolarity;
>      > + uint32_t lookupTable[64];
>      > + struct mg_flexspi_lut_seq lutCustomSeq[12];
>      > + uint32_t reserved4[4];
>      > +};
>      > +
>      > +struct mg_flexspi_nor_config {
>      > + struct mg_flexspi_mem_config memConfig;
>      > + uint32_t pageSize;
>      > + uint32_t sectorSize;
>      > + uint8_t ipcmdSerialClkFreq;
>      > + uint8_t isUniformBlockSize;
>      > + uint8_t reserved0[2];
>      > + uint8_t serialNorType;
>      > + uint8_t needExitNoCmdMode;
>      > + uint8_t halfClkForNonReadCmd;
>      > + uint8_t needRestoreNoCmdMode;
>      > + uint32_t blockSize;
>      > + uint32_t reserve2[11];
>      > +};
>      > +
>      > +/* FLEXSPI memory config block related defintions */
>      > +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB"
>     Big Endian
>      > +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0
>      > +
>      > +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \
>      > + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0)
>     | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
>      > + MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) |
>     MG_FLEXSPI_LUT_OPCODE1(cmd1))
>      > +
>      > +#define MG_CMD_SDR 0x01
>      > +#define MG_CMD_DDR 0x21
>      > +#define MG_DUMMY_SDR 0x0C
>      > +#define MG_DUMMY_DDR 0x2C
>      > +#define MG_RADDR_SDR 0x02
>      > +#define MG_RADDR_DDR 0x22
>      > +#define MG_READ_SDR 0x09
>      > +#define MG_READ_DDR 0x29
>      > +#define MG_WRITE_SDR 0x08
>      > +#define MG_WRITE_DDR 0x28
>      > +#define MG_STOP 0
>      > +
>      > +#define MG_FLEXSPI_1PAD 0
>      > +#define MG_FLEXSPI_2PAD 1
>      > +#define MG_FLEXSPI_4PAD 2
>      > +#define MG_FLEXSPI_8PAD 3
>      > +
>      > +#define MG_FLEXSPI_QSPI_LUT \
>      > + { \
>      > + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB,
>     MG_RADDR_SDR, MG_FLEXSPI_4PAD, \
>      > + 0x18), \
>      > + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06,
>     MG_READ_SDR, MG_FLEXSPI_4PAD, \
>      > + 0x04), \
>      > + [4 * 1 + 0] = \
>      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05,
>     MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \
>      > + [4 * 3 + 0] = \
>      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP,
>     MG_FLEXSPI_1PAD, 0x0), \
>      > + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>     0x20, MG_RADDR_SDR, \
>      > + MG_FLEXSPI_1PAD, 0x18), \
>      > + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>     0xD8, MG_RADDR_SDR, \
>      > + MG_FLEXSPI_1PAD, 0x18), \
>      > + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>     0x02, MG_RADDR_SDR, \
>      > + MG_FLEXSPI_1PAD, 0x18), \
>      > + [4 * 9 + 1] = \
>      > + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04,
>     MG_STOP, MG_FLEXSPI_1PAD, 0x0), \
>      > + [4 * 11 + 0] = \
>      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP,
>     MG_FLEXSPI_1PAD, 0x0), \
>      > }
>      > - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
>      > - n += 5;
>      > - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
>      > - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
>     query
>      > - // n += 6;
>      > - return mg_send(c, &pkt, sizeof(pkt.header) + n);
>      > +
>      > +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t)
>     (x)))) & 0xFFU)
>      > +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t)
>     (x)) << 8U)) & 0x300U)
>      > +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x))
>     << 10U)) & 0xFC00U)
>      > +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t)
>     (x)) << 16U)) & 0xFF0000U)
>      > +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t)
>     (x)) << 24U)) & 0x3000000U)
>      > +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x))
>     << 26U)) & 0xFC000000U)
>      > +
>      > +#define FLEXSPI_NOR_INSTANCE 0
>      > +
>      > +#if MG_DEVICE == MG_DEVICE_RT1020
>      > +struct mg_flexspi_nor_driver_interface {
>      > + uint32_t version;
>      > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
>     *config);
>      > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
>     *config, uint32_t dst_addr,
>      > + const uint32_t *src);
>      > + uint32_t reserved;
>      > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
>     *config, uint32_t start,
>      > + uint32_t lengthInBytes);
>      > + uint32_t reserved2;
>      > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
>     uint32_t *lutBase,
>      > + uint32_t seqNumber);
>      > + int (*xfer)(uint32_t instance, char *xfer);
>      > + void (*clear_cache)(uint32_t instance);
>      > +};
>      > +#elif MG_DEVICE == MG_DEVICE_RT1060
>      > +struct mg_flexspi_nor_driver_interface {
>      > + uint32_t version;
>      > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
>     *config);
>      > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
>     *config, uint32_t dst_addr,
>      > + const uint32_t *src);
>      > + int (*erase_all)(uint32_t instance, struct
>     mg_flexspi_nor_config *config);
>      > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
>     *config, uint32_t start,
>      > + uint32_t lengthInBytes);
>      > + int (*read)(uint32_t instance, struct mg_flexspi_nor_config
>     *config, uint32_t *dst, uint32_t addr,
>      > + uint32_t lengthInBytes);
>      > + void (*clear_cache)(uint32_t instance);
>      > + int (*xfer)(uint32_t instance, char *xfer);
>      > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
>     uint32_t *lutBase,
>      > + uint32_t seqNumber);
>      > + int (*get_config)(uint32_t instance, struct
>     mg_flexspi_nor_config *config, uint32_t *option);
>      > +};
>      > +#endif
>      > +
>      > +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
>      > + (*(uint32_t*)0x0020001c + 16)))
>      > +
>      > +static bool s_flash_irq_disabled;
>      > +
>      > +MG_IRAM void *mg_flash_start(void) {
>      > + return (void *) 0x60000000;
>      > +}
>      > +MG_IRAM size_t mg_flash_size(void) {
>      > + return 8 * 1024 * 1024;
>      > +}
>      > +MG_IRAM size_t mg_flash_sector_size(void) {
>      > + return 4 * 1024; // 4k
>      > +}
>      > +MG_IRAM size_t mg_flash_write_align(void) {
>      > + return 256;
>      > +}
>      > +MG_IRAM int mg_flash_bank(void) {
>      > + return 0;
>      > }
>      >
>      > -static void mg_sendnsreq(struct mg_connection *c, struct mg_str
>     *name, int ms,
>      > - struct mg_dns *dnsc, bool ipv6) {
>      > - struct dns_data *d = NULL;
>      > - if (dnsc->url == NULL) {
>      > - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
>      > - } else if (dnsc->c == NULL) {
>      > - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
>      > - if (dnsc->c != NULL) {
>      > - dnsc->c->pfn = dns_cb;
>      > - // dnsc->c->is_hexdumping = 1;
>      > - }
>      > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
>      > + char *base = (char *) mg_flash_start(), *end = base +
>     mg_flash_size();
>      > + volatile char *p = (char *) dst;
>      > + return p >= base && p < end && ((p - base) %
>     mg_flash_sector_size()) == 0;
>      > +}
>      > +
>      > +// Note: the get_config function below works both for RT1020 and
>     1060
>      > +#if MG_DEVICE == MG_DEVICE_RT1020
>      > +MG_IRAM static int flexspi_nor_get_config(struct
>     mg_flexspi_nor_config *config) {
>      > + struct mg_flexspi_nor_config default_config = {
>      > + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
>      > + .version = MG_FLEXSPI_CFG_BLK_VERSION,
>      > + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad
>      > + .csHoldTime = 3,
>      > + .csSetupTime = 3,
>      > + .controllerMiscOption = MG_BIT(4),
>      > + .deviceType = 1, // serial NOR
>      > + .sflashPadType = 4,
>      > + .serialClkFreq = 7, // 133MHz
>      > + .sflashA1Size = 8 * 1024 * 1024,
>      > + .lookupTable = MG_FLEXSPI_QSPI_LUT},
>      > + .pageSize = 256,
>      > + .sectorSize = 4 * 1024,
>      > + .ipcmdSerialClkFreq = 1,
>      > + .blockSize = 64 * 1024,
>      > + .isUniformBlockSize = false};
>      > +
>      > + *config = default_config;
>      > + return 0;
>      > +}
>      > +#else
>      > +MG_IRAM static int flexspi_nor_get_config(struct
>     mg_flexspi_nor_config *config) {
>      > + uint32_t options[] = {0xc0000000, 0x00};
>      > +
>      > + MG_ARM_DISABLE_IRQ();
>      > + uint32_t status =
>      > + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
>      > + if (!s_flash_irq_disabled) {
>      > + MG_ARM_ENABLE_IRQ();
>      > }
>      > - if (dnsc->c == NULL) {
>      > - mg_error(c, "resolver");
>      > - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
>     NULL) {
>      > - mg_error(c, "resolve OOM");
>      > - } else {
>      > - struct dns_data *reqs = (struct dns_data *)
>     c->mgr->active_dns_requests;
>      > - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
>      > - d->next = (struct dns_data *) c->mgr->active_dns_requests;
>      > - c->mgr->active_dns_requests = d;
>      > - d->expire = mg_millis() + (uint64_t) ms;
>      > - d->c = c;
>      > - c->is_resolving = 1;
>      > - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
>     name->len,
>      > - name->ptr, &dnsc->url, d->txnid));
>      > - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
>      > - mg_error(dnsc->c, "DNS send");
>      > - }
>      > + if (status) {
>      > + MG_ERROR(("Failed to extract flash configuration: status %u",
>     status));
>      > }
>      > + return status;
>      > }
>      > +#endif
>      >
>      > -void mg_resolve(struct mg_connection *c, const char *url) {
>      > - struct mg_str host = mg_url_host(url);
>      > - c->rem.port = mg_htons(mg_url_port(url));
>      > - if (mg_aton(host, &c->rem)) {
>      > - // host is an IP address, do not fire name resolution
>      > - mg_connect_resolved(c);
>      > - } else {
>      > - // host is not an IP, send DNS resolution request
>      > - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
>     &c->mgr->dns4;
>      > - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
>      > +MG_IRAM bool mg_flash_erase(void *addr) {
>      > + struct mg_flexspi_nor_config config;
>      > + if (flexspi_nor_get_config(&config) != 0) {
>      > + return false;
>      > + }
>      > + if (flash_page_start(addr) == false) {
>      > + MG_ERROR(("%p is not on a sector boundary", addr));
>      > + return false;
>      > + }
>      > +
>      > + void *dst = (void *)((char *) addr - (char *) mg_flash_start());
>      > +
>      > + // Note: Interrupts must be disabled before any call to the ROM
>     API on RT1020
>      > + // and 1060
>      > + MG_ARM_DISABLE_IRQ();
>      > + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config,
>     (uint32_t) dst,
>      > + mg_flash_sector_size()) == 0);
>      > + if (!s_flash_irq_disabled) {
>      > + MG_ARM_ENABLE_IRQ(); // Reenable them after the call
>      > }
>      > + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok"
>     : "fail"));
>      > + return ok;
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/event.c"
>      > -#endif
>      > +MG_IRAM bool mg_flash_swap_bank(void) {
>      > + return true;
>      > +}
>      > +
>      > +static inline void spin(volatile uint32_t count) {
>      > + while (count--) (void) 0;
>      > +}
>      > +
>      > +static inline void flash_wait(void) {
>      > + while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) &
>     MG_BIT(1)) == 0)
>      > + spin(1);
>      > +}
>      >
>      > +MG_IRAM static void *flash_code_location(void) {
>      > + return (void *) ((char *) mg_flash_start() + 0x2000);
>      > +}
>      >
>      > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
>     len) {
>      > + struct mg_flexspi_nor_config config;
>      > + if (flexspi_nor_get_config(&config) != 0) {
>      > + return false;
>      > + }
>      > + if ((len % mg_flash_write_align()) != 0) {
>      > + MG_ERROR(("%lu is not aligned to %lu", len,
>     mg_flash_write_align()));
>      > + return false;
>      > + }
>      >
>      > + if ((char *) addr < (char *) mg_flash_start()) {
>      > + MG_ERROR(("Invalid flash write address: %p", addr));
>      > + return false;
>      > + }
>      >
>      > + uint32_t *dst = (uint32_t *) addr;
>      > + uint32_t *src = (uint32_t *) buf;
>      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>      > + bool ok = true;
>      > +
>      > + // Note: If we overwrite the flash irq section of the image, we
>     must also
>      > + // make sure interrupts are disabled and are not reenabled
>     until we write
>      > + // this sector with another irq table.
>      > + if ((char *) addr == (char *) flash_code_location()) {
>      > + s_flash_irq_disabled = true;
>      > + MG_ARM_DISABLE_IRQ();
>      > + }
>      >
>      > -void mg_call(struct mg_connection *c, int ev, void *ev_data) {
>      > - // Run user-defined handler first, in order to give it an ability
>      > - // to intercept processing (e.g. clean input buffer) before the
>      > - // protocol handler kicks in
>      > - if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
>      > - if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
>      > + while (ok && src < end) {
>      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
>      > + break;
>      > + }
>      > + uint32_t status;
>      > + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
>      > + if ((char *) buf >= (char *) mg_flash_start()) {
>      > + // If we copy from FLASH to FLASH, then we first need to copy
>     the source
>      > + // to RAM
>      > + size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
>      > + uint32_t tmp[tmp_buf_size];
>      > +
>      > + for (size_t i = 0; i < tmp_buf_size; i++) {
>      > + flash_wait();
>      > + tmp[i] = src[i];
>      > + }
>      > + MG_ARM_DISABLE_IRQ();
>      > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
>      > + (uint32_t) dst_ofs, tmp);
>      > + } else {
>      > + MG_ARM_DISABLE_IRQ();
>      > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
>      > + (uint32_t) dst_ofs, src);
>      > + }
>      > + if (!s_flash_irq_disabled) {
>      > + MG_ARM_ENABLE_IRQ();
>      > + }
>      > + src = (uint32_t *) ((char *) src + mg_flash_write_align());
>      > + dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
>      > + if (status != 0) {
>      > + ok = false;
>      > + }
>      > + }
>      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ?
>     "ok" : "fail"));
>      > + return ok;
>      > }
>      >
>      > -void mg_error(struct mg_connection *c, const char *fmt, ...) {
>      > - char buf[64];
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
>      > - va_end(ap);
>      > - MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
>      > - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
>      > - mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it
>      > +MG_IRAM void mg_device_reset(void) {
>      > + MG_DEBUG(("Resetting device..."));
>      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>      > }
>      >
>      > +#endif
>      > +
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/fmt.c"
>      > +#line 1 "src/device_stm32h5.c"
>      > #endif
>      >
>      >
>      >
>      > +#if MG_DEVICE == MG_DEVICE_STM32H5
>      >
>      > -static void mg_pfn_iobuf_private(char ch, void *param, bool
>     expand) {
>      > - struct mg_iobuf *io = (struct mg_iobuf *) param;
>      > - if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
>     io->len + 2);
>      > - if (io->len + 2 <= io->size) {
>      > - io->buf[io->len++] = (uint8_t) ch;
>      > - io->buf[io->len] = 0;
>      > - } else if (io->len < io->size) {
>      > - io->buf[io->len++] = 0; // Guarantee to 0-terminate
>      > - }
>      > -}
>      > +#define FLASH_BASE 0x40022000 // Base address of the flash
>     controller
>      > +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
>      > +#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
>      > +#define FLASH_OPTCR (FLASH_BASE + 0x1c)
>      > +#define FLASH_NSSR (FLASH_BASE + 0x20)
>      > +#define FLASH_NSCR (FLASH_BASE + 0x28)
>      > +#define FLASH_NSCCR (FLASH_BASE + 0x30)
>      > +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
>      > +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
>      >
>      > -static void mg_putchar_iobuf_static(char ch, void *param) {
>      > - mg_pfn_iobuf_private(ch, param, false);
>      > +void *mg_flash_start(void) {
>      > + return (void *) 0x08000000;
>      > }
>      > -
>      > -void mg_pfn_iobuf(char ch, void *param) {
>      > - mg_pfn_iobuf_private(ch, param, true);
>      > +size_t mg_flash_size(void) {
>      > + return 2 * 1024 * 1024; // 2Mb
>      > }
>      > -
>      > -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>     va_list *ap) {
>      > - struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
>      > - size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
>      > - if (n < len) buf[n] = '\0';
>      > - return n;
>      > +size_t mg_flash_sector_size(void) {
>      > + return 8 * 1024; // 8k
>      > }
>      > -
>      > -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
>      > - va_list ap;
>      > - size_t n;
>      > - va_start(ap, fmt);
>      > - n = mg_vsnprintf(buf, len, fmt, &ap);
>      > - va_end(ap);
>      > - return n;
>      > +size_t mg_flash_write_align(void) {
>      > + return 16; // 128 bit
>      > }
>      > -
>      > -char *mg_vmprintf(const char *fmt, va_list *ap) {
>      > - struct mg_iobuf io = {0, 0, 0, 256};
>      > - mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
>      > - return (char *) io.buf;
>      > +int mg_flash_bank(void) {
>      > + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
>      > }
>      >
>      > -char *mg_mprintf(const char *fmt, ...) {
>      > - char *s;
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - s = mg_vmprintf(fmt, &ap);
>      > - va_end(ap);
>      > - return s;
>      > +static void flash_unlock(void) {
>      > + static bool unlocked = false;
>      > + if (unlocked == false) {
>      > + MG_REG(FLASH_KEYR) = 0x45670123;
>      > + MG_REG(FLASH_KEYR) = 0Xcdef89ab;
>      > + MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
>      > + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
>      > + unlocked = true;
>      > + }
>      > }
>      >
>      > -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
>     char *fmt, ...) {
>      > - size_t len = 0;
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - len = mg_vxprintf(out, ptr, fmt, &ap);
>      > - va_end(ap);
>      > - return len;
>      > +static int flash_page_start(volatile uint32_t *dst) {
>      > + char *base = (char *) mg_flash_start(), *end = base +
>     mg_flash_size();
>      > + volatile char *p = (char *) dst;
>      > + return p >= base && p < end && ((p - base) %
>     mg_flash_sector_size()) == 0;
>      > }
>      >
>      > -static bool is_digit(int c) {
>      > - return c >= '0' && c <= '9';
>      > +static bool flash_is_err(void) {
>      > + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481
>     7.11.9
>      > }
>      >
>      > -static int addexp(char *buf, int e, int sign) {
>      > - int n = 0;
>      > - buf[n++] = 'e';
>      > - buf[n++] = (char) sign;
>      > - if (e > 400) return 0;
>      > - if (e < 10) buf[n++] = '0';
>      > - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
>     / 100);
>      > - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
>      > - buf[n++] = (char) (e + '0');
>      > - return n;
>      > +static void flash_wait(void) {
>      > + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
>      > + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
>      > + (void) 0;
>      > + }
>      > }
>      >
>      > -static int xisinf(double x) {
>      > - union {
>      > - double f;
>      > - uint64_t u;
>      > - } ieee754 = {x};
>      > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
>     0x7ff00000 &&
>      > - ((unsigned) ieee754.u == 0);
>      > +static void flash_clear_err(void) {
>      > + flash_wait(); // Wait until ready
>      > + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all
>     errors
>      > }
>      >
>      > -static int xisnan(double x) {
>      > - union {
>      > - double f;
>      > - uint64_t u;
>      > - } ieee754 = {x};
>      > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
>      > - ((unsigned) ieee754.u != 0) >
>      > - 0x7ff00000;
>      > +static bool flash_bank_is_swapped(void) {
>      > + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8
>      > }
>      >
>      > -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
>     width) {
>      > - char buf[40];
>      > - int i, s = 0, n = 0, e = 0;
>      > - double t, mul, saved;
>      > - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
>      > - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
>     "inf" : "-inf");
>      > - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
>      > - if (d < 0.0) d = -d, buf[s++] = '-';
>      > -
>      > - // Round
>      > - saved = d;
>      > - mul = 1.0;
>      > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
>      > - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
>      > - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
>      > - d += t;
>      > - // Calculate exponent, and 'mul' for scientific representation
>      > - mul = 1.0;
>      > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
>      > - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
>      > - // printf(" --> %g %d %g %g\n", saved, e, t, mul);
>      > -
>      > - if (e >= width) {
>      > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
>      > - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
>      > - n += addexp(buf + s + n, e, '+');
>      > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>      > - } else if (e <= -width) {
>      > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
>      > - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
>      > - n += addexp(buf + s + n, -e, '-');
>      > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>      > +bool mg_flash_erase(void *location) {
>      > + bool ok = false;
>      > + if (flash_page_start(location) == false) {
>      > + MG_ERROR(("%p is not on a sector boundary"));
>      > } else {
>      > - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
>      > - int ch = (int) (d / t);
>      > - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
>      > - d -= ch * t;
>      > - t /= 10.0;
>      > - }
>      > - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
>     + n, buf);
>      > - if (n == 0) buf[s++] = '0';
>      > - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
>     /= 10.0;
>      > - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
>      > - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
>      > - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
>     i++) {
>      > - int ch = (int) (d / t);
>      > - buf[s + n++] = (char) (ch + '0');
>      > - d -= ch * t;
>      > - t /= 10.0;
>      > + uintptr_t diff = (char *) location - (char *) mg_flash_start();
>      > + uint32_t sector = diff / mg_flash_sector_size();
>      > + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
>      > + flash_unlock();
>      > + flash_clear_err();
>      > + MG_REG(FLASH_NSCR) = 0;
>      > + if ((sector < 128 && flash_bank_is_swapped()) ||
>      > + (sector > 127 && !flash_bank_is_swapped())) {
>      > + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL
>      > }
>      > + if (sector > 127) sector -= 128;
>      > + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase |
>     sector_num
>      > + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing
>      > + flash_wait();
>      > + ok = !flash_is_err();
>      > + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector,
>     location,
>      > + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
>      > + // mg_hexdump(location, 32);
>      > + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
>      > }
>      > - while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
>      > - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
>      > - n += s;
>      > - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
>      > - buf[n] = '\0';
>      > - return mg_snprintf(dst, dstlen, "%s", buf);
>      > + return ok;
>      > }
>      >
>      > -static size_t mg_lld(char *buf, int64_t val, bool is_signed,
>     bool is_hex) {
>      > - const char *letters = "0123456789abcdef";
>      > - uint64_t v = (uint64_t) val;
>      > - size_t s = 0, n, i;
>      > - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
>      > - // This loop prints a number in reverse order. I guess this is
>     because we
>      > - // write numbers from right to left: least significant digit
>     comes last.
>      > - // Maybe because we use Arabic numbers, and Arabs write RTL?
>      > - if (is_hex) {
>      > - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
>      > - } else {
>      > - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
>      > +bool mg_flash_swap_bank(void) {
>      > + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
>      > + flash_unlock();
>      > + flash_clear_err();
>      > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
>      > + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
>      > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
>      > + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
>      > + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired)
>     (void) 0;
>      > + return true;
>      > +}
>      > +
>      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>      > + if ((len % mg_flash_write_align()) != 0) {
>      > + MG_ERROR(("%lu is not aligned to %lu", len,
>     mg_flash_write_align()));
>      > + return false;
>      > }
>      > - // Reverse a string
>      > - for (i = 0; i < n / 2; i++) {
>      > - char t = buf[s + i];
>      > - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
>      > + uint32_t *dst = (uint32_t *) addr;
>      > + uint32_t *src = (uint32_t *) buf;
>      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>      > + bool ok = true;
>      > + flash_unlock();
>      > + flash_clear_err();
>      > + MG_ARM_DISABLE_IRQ();
>      > + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
>      > + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
>      > + while (ok && src < end) {
>      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
>      > + *(volatile uint32_t *) dst++ = *src++;
>      > + flash_wait();
>      > + if (flash_is_err()) ok = false;
>      > }
>      > - if (val == 0) buf[n++] = '0'; // Handle special case
>      > - return n + s;
>      > + MG_ARM_ENABLE_IRQ();
>      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
>     len, dst,
>      > + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
>      > + MG_REG(FLASH_NSSR)));
>      > + MG_REG(FLASH_NSCR) = 0; // Clear flags
>      > + return ok;
>      > }
>      >
>      > -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
>      > - size_t len) {
>      > - size_t i = 0;
>      > - while (i < len && buf[i] != '\0') out(buf[i++], ptr);
>      > - return i;
>      > +void mg_device_reset(void) {
>      > + // SCB->AIRCR = ((0x5fa <<
>     SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
>      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>      > }
>      > +#endif
>      >
>      > -static char mg_esc(int c, bool esc) {
>      > - const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
>      > - for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
>      > - if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/device_stm32h7.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +#if MG_DEVICE == MG_DEVICE_STM32H7
>      > +
>      > +#define FLASH_BASE1 0x52002000 // Base address for bank1
>      > +#define FLASH_BASE2 0x52002100 // Base address for bank2
>      > +#define FLASH_KEYR 0x04 // See RM0433 4.9.2
>      > +#define FLASH_OPTKEYR 0x08
>      > +#define FLASH_OPTCR 0x18
>      > +#define FLASH_SR 0x10
>      > +#define FLASH_CR 0x0c
>      > +#define FLASH_CCR 0x14
>      > +#define FLASH_OPTSR_CUR 0x1c
>      > +#define FLASH_OPTSR_PRG 0x20
>      > +#define FLASH_SIZE_REG 0x1ff1e880
>      > +
>      > +MG_IRAM void *mg_flash_start(void) {
>      > + return (void *) 0x08000000;
>      > +}
>      > +MG_IRAM size_t mg_flash_size(void) {
>      > + return MG_REG(FLASH_SIZE_REG) * 1024;
>      > +}
>      > +MG_IRAM size_t mg_flash_sector_size(void) {
>      > + return 128 * 1024; // 128k
>      > +}
>      > +MG_IRAM size_t mg_flash_write_align(void) {
>      > + return 32; // 256 bit
>      > +}
>      > +MG_IRAM int mg_flash_bank(void) {
>      > + if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual
>     bank support
>      > + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
>      > +}
>      > +
>      > +MG_IRAM static void flash_unlock(void) {
>      > + static bool unlocked = false;
>      > + if (unlocked == false) {
>      > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
>      > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
>      > + if (mg_flash_bank() > 0) {
>      > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
>      > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
>      > + }
>      > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is
>     "shared"
>      > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus
>     unlock once
>      > + unlocked = true;
>      > }
>      > - return 0;
>      > }
>      >
>      > -static char mg_escape(int c) {
>      > - return mg_esc(c, true);
>      > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
>      > + char *base = (char *) mg_flash_start(), *end = base +
>     mg_flash_size();
>      > + volatile char *p = (char *) dst;
>      > + return p >= base && p < end && ((p - base) %
>     mg_flash_sector_size()) == 0;
>      > }
>      >
>      > -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
>      > - size_t len) {
>      > - size_t i = 0, extra = 0;
>      > - for (i = 0; i < len && buf[i] != '\0'; i++) {
>      > - char c = mg_escape(buf[i]);
>      > - if (c) {
>      > - out('\\', ptr), out(c, ptr), extra++;
>      > - } else {
>      > - out(buf[i], ptr);
>      > - }
>      > - }
>      > - return i + extra;
>      > +MG_IRAM static bool flash_is_err(uint32_t bank) {
>      > + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); //
>     RM0433 4.9.5
>      > }
>      >
>      > -static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
>      > - size_t len) {
>      > - size_t n = 2;
>      > - out('"', ptr);
>      > - n += qcpy(out, ptr, buf, len);
>      > - out('"', ptr);
>      > - return n;
>      > +MG_IRAM static void flash_wait(uint32_t bank) {
>      > + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2)))
>     (void) 0;
>      > }
>      >
>      > -static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t
>     *buf,
>      > - size_t len) {
>      > - size_t i, n = 0;
>      > - const char *t =
>      > -
>     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
>      > - out('"', ptr), n++;
>      > - for (i = 0; i < len; i += 3) {
>      > - uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
>      > - c3 = i + 2 < len ? buf[i + 2] : 0;
>      > - char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
>     '='};
>      > - if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
>      > - if (i + 2 < len) tmp[3] = t[c3 & 63];
>      > - n += scpy(out, ptr, tmp, sizeof(tmp));
>      > - }
>      > - out('"', ptr), n++;
>      > - return n;
>      > +MG_IRAM static void flash_clear_err(uint32_t bank) {
>      > + flash_wait(bank); // Wait until ready
>      > + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear
>     all errors
>      > }
>      >
>      > -size_t mg_vxprintf(void (*out)(char, void *), void *param, const
>     char *fmt,
>      > - va_list *ap) {
>      > - size_t i = 0, n = 0;
>      > - while (fmt[i] != '\0') {
>      > - if (fmt[i] == '%') {
>      > - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
>     prec */;
>      > - char pad = ' ', minus = 0, c = fmt[++i];
>      > - if (c == '#') x++, c = fmt[++i];
>      > - if (c == '-') minus++, c = fmt[++i];
>      > - if (c == '0') pad = '0', c = fmt[++i];
>      > - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
>     fmt[++i];
>      > - if (c == '.') {
>      > - c = fmt[++i];
>      > - if (c == '*') {
>      > - pr = (size_t) va_arg(*ap, int);
>      > - c = fmt[++i];
>      > - } else {
>      > - pr = 0;
>      > - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
>     fmt[++i];
>      > - }
>      > - }
>      > - while (c == 'h') c = fmt[++i]; // Treat h and hh as int
>      > - if (c == 'l') {
>      > - is_long++, c = fmt[++i];
>      > - if (c == 'l') is_long++, c = fmt[++i];
>      > - }
>      > - if (c == 'p') x = 1, is_long = 1;
>      > - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
>      > - c == 'g' || c == 'f') {
>      > - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
>      > - char tmp[40];
>      > - size_t xl = x ? 2 : 0;
>      > - if (c == 'g' || c == 'f') {
>      > - double v = va_arg(*ap, double);
>      > - if (pr == ~0U) pr = 6;
>      > - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
>      > - } else if (is_long == 2) {
>      > - int64_t v = va_arg(*ap, int64_t);
>      > - k = mg_lld(tmp, v, s, h);
>      > - } else if (is_long == 1) {
>      > - long v = va_arg(*ap, long);
>      > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
>     s, h);
>      > - } else {
>      > - int v = va_arg(*ap, int);
>      > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
>      > - }
>      > - for (j = 0; j < xl && w > 0; j++) w--;
>      > - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
>      > - n += scpy(out, param, &pad, 1);
>      > - n += scpy(out, param, (char *) "0x", xl);
>      > - for (j = 0; pad == '0' && k < w && j + k < w; j++)
>      > - n += scpy(out, param, &pad, 1);
>      > - n += scpy(out, param, tmp, k);
>      > - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
>      > - n += scpy(out, param, &pad, 1);
>      > - } else if (c == 'M') {
>      > - mg_pm_t f = va_arg(*ap, mg_pm_t);
>      > - n += f(out, param, ap);
>      > - } else if (c == 'c') {
>      > - int ch = va_arg(*ap, int);
>      > - out((char) ch, param);
>      > - n++;
>      > - } else if (c == 'H') {
>      > - // Print hex-encoded double-quoted string
>      > - size_t bl = (size_t) va_arg(*ap, int);
>      > - uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
>      > - const char *hex = "0123456789abcdef";
>      > - n += scpy(out, param, (char *) &dquote, 1);
>      > - for (j = 0; j < bl; j++) {
>      > - n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
>      > - n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
>      > - }
>      > - n += scpy(out, param, (char *) &dquote, 1);
>      > - } else if (c == 'I') {
>      > - // Print IPv4 or IPv6 address
>      > - size_t len = (size_t) va_arg(*ap, int); // Length 16 means IPv6
>     address
>      > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the IP
>     address
>      > - if (len == 6) {
>      > - uint16_t *p = (uint16_t *) buf;
>      > - n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x",
>     mg_htons(p[0]),
>      > - mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
>      > - mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
>      > - mg_htons(p[7]));
>      > - } else {
>      > - n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int)
>     buf[1],
>      > - (int) buf[2], (int) buf[3]);
>      > - }
>      > - } else if (c == 'A') {
>      > - // Print hardware addresses (currently Ethernet MAC)
>      > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the hw
>     address
>      > - n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
>      > - (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
>      > - (int) buf[4], (int) buf[5]);
>      > - } else if (c == 'V') {
>      > - // Print base64-encoded double-quoted string
>      > - size_t len = (size_t) va_arg(*ap, int);
>      > - uint8_t *buf = va_arg(*ap, uint8_t *);
>      > - n += bcpy(out, param, buf, len);
>      > - } else if (c == 's' || c == 'Q' || c == 'q') {
>      > - char *p = va_arg(*ap, char *);
>      > - size_t (*f)(void (*)(char, void *), void *, char *, size_t) =
>     scpy;
>      > - if (c == 'Q') f = Qcpy;
>      > - if (c == 'q') f = qcpy;
>      > - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
>      > - for (j = 0; !minus && pr < w && j + pr < w; j++)
>      > - n += f(out, param, &pad, 1);
>      > - n += f(out, param, p, pr);
>      > - for (j = 0; minus && pr < w && j + pr < w; j++)
>      > - n += f(out, param, &pad, 1);
>      > - } else if (c == '%') {
>      > - out('%', param);
>      > - n++;
>      > - } else {
>      > - out('%', param);
>      > - out(c, param);
>      > - n += 2;
>      > - }
>      > - i++;
>      > - } else {
>      > - out(fmt[i], param), n++, i++;
>      > - }
>      > - }
>      > - return n;
>      > +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
>      > + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/fs.c"
>      > -#endif
>      > -
>      > -
>      > -
>      > -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>     flags) {
>      > - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
>      > - if (fd != NULL) {
>      > - fd->fd = fs->op(path, flags);
>      > - fd->fs = fs;
>      > - if (fd->fd == NULL) {
>      > - free(fd);
>      > - fd = NULL;
>      > - }
>      > - }
>      > - return fd;
>      > +// Figure out flash bank based on the address
>      > +MG_IRAM static uint32_t flash_bank(void *addr) {
>      > + size_t ofs = (char *) addr - (char *) mg_flash_start();
>      > + if (mg_flash_bank() == 0) return FLASH_BASE1;
>      > + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
>      > }
>      >
>      > -void mg_fs_close(struct mg_fd *fd) {
>      > - if (fd != NULL) {
>      > - fd->fs->cl(fd->fd);
>      > - free(fd);
>      > +MG_IRAM bool mg_flash_erase(void *addr) {
>      > + bool ok = false;
>      > + if (flash_page_start(addr) == false) {
>      > + MG_ERROR(("%p is not on a sector boundary", addr));
>      > + } else {
>      > + uintptr_t diff = (char *) addr - (char *) mg_flash_start();
>      > + uint32_t sector = diff / mg_flash_sector_size();
>      > + uint32_t bank = flash_bank(addr);
>      > + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
>      > +
>      > + flash_unlock();
>      > + if (sector > 7) sector -= 8;
>      > +
>      > + flash_clear_err(bank);
>      > + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
>      > + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
>      > + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
>      > + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
>      > + ok = !flash_is_err(bank);
>      > + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector,
>     addr,
>      > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
>      > + MG_REG(bank + FLASH_SR)));
>      > + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
>      > }
>      > + return ok;
>      > }
>      >
>      > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
>     *sizep) {
>      > - struct mg_fd *fd;
>      > - char *data = NULL;
>      > - size_t size = 0;
>      > - fs->st(path, &size, NULL);
>      > - if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
>      > - data = (char *) calloc(1, size + 1);
>      > - if (data != NULL) {
>      > - if (fs->rd(fd->fd, data, size) != size) {
>      > - free(data);
>      > - data = NULL;
>      > - } else {
>      > - data[size] = '\0';
>      > - if (sizep != NULL) *sizep = size;
>      > - }
>      > - }
>      > - mg_fs_close(fd);
>      > - }
>      > - return data;
>      > +MG_IRAM bool mg_flash_swap_bank(void) {
>      > + if (mg_flash_bank() == 0) return true;
>      > + uint32_t bank = FLASH_BASE1;
>      > + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
>      > + flash_unlock();
>      > + flash_clear_err(bank);
>      > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
>      > + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
>      > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
>      > + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
>      > + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) !=
>     desired) (void) 0;
>      > + return true;
>      > }
>      >
>      > -bool mg_file_write(struct mg_fs *fs, const char *path, const
>     void *buf,
>      > - size_t len) {
>      > - bool result = false;
>      > - struct mg_fd *fd;
>      > - char tmp[MG_PATH_MAX];
>      > - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
>      > - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
>      > - result = fs->wr(fd->fd, buf, len) == len;
>      > - mg_fs_close(fd);
>      > - if (result) {
>      > - fs->rm(path);
>      > - fs->mv(tmp, path);
>      > - } else {
>      > - fs->rm(tmp);
>      > - }
>      > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
>     len) {
>      > + if ((len % mg_flash_write_align()) != 0) {
>      > + MG_ERROR(("%lu is not aligned to %lu", len,
>     mg_flash_write_align()));
>      > + return false;
>      > }
>      > - return result;
>      > + uint32_t bank = flash_bank(addr);
>      > + uint32_t *dst = (uint32_t *) addr;
>      > + uint32_t *src = (uint32_t *) buf;
>      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>      > + bool ok = true;
>      > + flash_unlock();
>      > + flash_clear_err(bank);
>      > + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
>      > + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
>      > + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
>      > + MG_ARM_DISABLE_IRQ();
>      > + while (ok && src < end) {
>      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
>      > + *(volatile uint32_t *) dst++ = *src++;
>      > + flash_wait(bank);
>      > + if (flash_is_err(bank)) ok = false;
>      > + }
>      > + MG_ARM_ENABLE_IRQ();
>      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
>     len, dst,
>      > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
>      > + MG_REG(bank + FLASH_SR)));
>      > + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
>      > + return ok;
>      > }
>      >
>      > -bool mg_file_printf(struct mg_fs *fs, const char *path, const
>     char *fmt, ...) {
>      > - va_list ap;
>      > - char *data;
>      > - bool result = false;
>      > - va_start(ap, fmt);
>      > - data = mg_vmprintf(fmt, &ap);
>      > - va_end(ap);
>      > - result = mg_file_write(fs, path, data, strlen(data));
>      > - free(data);
>      > - return result;
>      > +MG_IRAM void mg_device_reset(void) {
>      > + // SCB->AIRCR = ((0x5fa <<
>     SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
>      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>      > }
>      > +#endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/fs_fat.c"
>      > +#line 1 "src/dns.c"
>      > #endif
>      >
>      >
>      >
>      > -#if MG_ENABLE_FATFS
>      > -#include <ff.h>
>      >
>      > -static int mg_days_from_epoch(int y, int m, int d) {
>      > - y -= m <= 2;
>      > - int era = y / 400;
>      > - int yoe = y - era * 400;
>      > - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
>      > - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
>      > - return era * 146097 + doe - 719468;
>      > -}
>      >
>      > -static time_t mg_timegm(const struct tm *t) {
>      > - int year = t->tm_year + 1900;
>      > - int month = t->tm_mon; // 0-11
>      > - if (month > 11) {
>      > - year += month / 12;
>      > - month %= 12;
>      > - } else if (month < 0) {
>      > - int years_diff = (11 - month) / 12;
>      > - year -= years_diff;
>      > - month += 12 * years_diff;
>      > - }
>      > - int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
>      > - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
>      > -}
>      >
>      > -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
>      > - struct tm tm;
>      > - memset(&tm, 0, sizeof(struct tm));
>      > - tm.tm_sec = (ftime << 1) & 0x3e;
>      > - tm.tm_min = ((ftime >> 5) & 0x3f);
>      > - tm.tm_hour = ((ftime >> 11) & 0x1f);
>      > - tm.tm_mday = (fdate & 0x1f);
>      > - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
>      > - tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
>      > - return mg_timegm(&tm);
>      > -}
>      >
>      > -static int ff_stat(const char *path, size_t *size, time_t *mtime) {
>      > - FILINFO fi;
>      > - if (path[0] == '\0') {
>      > - if (size) *size = 0;
>      > - if (mtime) *mtime = 0;
>      > - return MG_FS_DIR;
>      > - } else if (f_stat(path, &fi) == 0) {
>      > - if (size) *size = (size_t) fi.fsize;
>      > - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
>      > - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
>     MG_FS_DIR : 0);
>      > - } else {
>      > - return 0;
>      > - }
>      > -}
>      >
>      > -static void ff_list(const char *dir, void (*fn)(const char *,
>     void *),
>      > - void *userdata) {
>      > - DIR d;
>      > - FILINFO fi;
>      > - if (f_opendir(&d, dir) == FR_OK) {
>      > - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
>      > - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
>      > - fn(fi.fname, userdata);
>      > - }
>      > - f_closedir(&d);
>      > - }
>      > -}
>      > +struct dns_data {
>      > + struct dns_data *next;
>      > + struct mg_connection *c;
>      > + uint64_t expire;
>      > + uint16_t txnid;
>      > +};
>      >
>      > -static void *ff_open(const char *path, int flags) {
>      > - FIL f;
>      > - unsigned char mode = FA_READ;
>      > - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
>     FA_OPEN_APPEND;
>      > - if (f_open(&f, path, mode) == 0) {
>      > - FIL *fp = calloc(1, sizeof(*fp));
>      > - memcpy(fp, &f, sizeof(*fp));
>      > - return fp;
>      > - } else {
>      > - return NULL;
>      > - }
>      > +static void mg_sendnsreq(struct mg_connection *, struct mg_str
>     *, int,
>      > + struct mg_dns *, bool);
>      > +
>      > +static void mg_dns_free(struct dns_data **head, struct dns_data
>     *d) {
>      > + LIST_DELETE(struct dns_data, head, d);
>      > + free(d);
>      > }
>      >
>      > -static void ff_close(void *fp) {
>      > - if (fp != NULL) {
>      > - f_close((FIL *) fp);
>      > - free(fp);
>      > +void mg_resolve_cancel(struct mg_connection *c) {
>      > + struct dns_data *tmp, *d;
>      > + struct dns_data **head = (struct dns_data **)
>     &c->mgr->active_dns_requests;
>      > + for (d = *head; d != NULL; d = tmp) {
>      > + tmp = d->next;
>      > + if (d->c == c) mg_dns_free(head, d);
>      > }
>      > }
>      >
>      > -static size_t ff_read(void *fp, void *buf, size_t len) {
>      > - UINT n = 0, misalign = ((size_t) buf) & 3;
>      > - if (misalign) {
>      > - char aligned[4];
>      > - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
>      > - memcpy(buf, aligned, n);
>      > - } else {
>      > - f_read((FIL *) fp, buf, len, &n);
>      > +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
>     len, size_t ofs,
>      > + char *to, size_t tolen, size_t j,
>      > + int depth) {
>      > + size_t i = 0;
>      > + if (tolen > 0 && depth == 0) to[0] = '\0';
>      > + if (depth > 5) return 0;
>      > + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
>     + 1]));
>      > + while (ofs + i + 1 < len) {
>      > + size_t n = s[ofs + i];
>      > + if (n == 0) {
>      > + i++;
>      > + break;
>      > + }
>      > + if (n & 0xc0) {
>      > + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
>     len
>      > + // MG_INFO(("PTR %lx", (unsigned long) ptr));
>      > + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
>      > + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
>     == 0)
>      > + return 0;
>      > + i += 2;
>      > + break;
>      > + }
>      > + if (ofs + i + n + 1 >= len) return 0;
>      > + if (j > 0) {
>      > + if (j < tolen) to[j] = '.';
>      > + j++;
>      > + }
>      > + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
>      > + j += n;
>      > + i += n + 1;
>      > + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
>      > + // MG_INFO(("--> [%s]", to));
>      > }
>      > - return n;
>      > + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
>     is nul-term
>      > + return i;
>      > }
>      >
>      > -static size_t ff_write(void *fp, const void *buf, size_t len) {
>      > - UINT n = 0;
>      > - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
>     : 0;
>      > +static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
>     size_t ofs,
>      > + char *dst, size_t dstlen) {
>      > + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
>      > }
>      >
>      > -static size_t ff_seek(void *fp, size_t offset) {
>      > - f_lseek((FIL *) fp, offset);
>      > - return offset;
>      > +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
>      > + bool is_question, struct mg_dns_rr *rr) {
>      > + const uint8_t *s = buf + ofs, *e = &buf[len];
>      > +
>      > + memset(rr, 0, sizeof(*rr));
>      > + if (len < sizeof(struct mg_dns_header)) return 0; // Too small
>      > + if (len > 512) return 0; // Too large, we don't expect that
>      > + if (s >= e) return 0; // Overflow
>      > +
>      > + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
>     NULL, 0)) == 0)
>      > + return 0;
>      > + s += rr->nlen + 4;
>      > + if (s > e) return 0;
>      > + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
>      > + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>      > + if (is_question) return (size_t) (rr->nlen + 4);
>      > +
>      > + s += 6;
>      > + if (s > e) return 0;
>      > + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>      > + if (s + rr->alen > e) return 0;
>      > + return (size_t) (rr->nlen + rr->alen + 10);
>      > }
>      >
>      > -static bool ff_rename(const char *from, const char *to) {
>      > - return f_rename(from, to) == FR_OK;
>      > +bool mg_dns_parse(const uint8_t *buf, size_t len, struct
>     mg_dns_message *dm) {
>      > + const struct mg_dns_header *h = (struct mg_dns_header *) buf;
>      > + struct mg_dns_rr rr;
>      > + size_t i, n, num_answers, ofs = sizeof(*h);
>      > + memset(dm, 0, sizeof(*dm));
>      > +
>      > + if (len < sizeof(*h)) return 0; // Too small, headers dont fit
>      > + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
>      > + num_answers = mg_ntohs(h->num_answers);
>      > + if (num_answers > 10) {
>      > + MG_DEBUG(("Got %u answers, ignoring beyond 10th one",
>     num_answers));
>      > + num_answers = 10; // Sanity cap
>      > + }
>      > + dm->txnid = mg_ntohs(h->txnid);
>      > +
>      > + for (i = 0; i < mg_ntohs(h->num_questions); i++) {
>      > + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
>     return false;
>      > + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
>      > + ofs += n;
>      > + }
>      > + for (i = 0; i < num_answers; i++) {
>      > + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
>     return false;
>      > + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
>     rr.aclass,
>      > + // dm->name));
>      > + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
>      > + ofs += n;
>      > +
>      > + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
>      > + dm->addr.is_ip6 = false;
>      > + memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
>      > + dm->resolved = true;
>      > + break; // Return success
>      > + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
>      > + dm->addr.is_ip6 = true;
>      > + memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
>      > + dm->resolved = true;
>      > + break; // Return success
>      > + }
>      > + }
>      > + return true;
>      > }
>      >
>      > -static bool ff_remove(const char *path) {
>      > - return f_unlink(path) == FR_OK;
>      > +static void dns_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + struct dns_data *d, *tmp;
>      > + struct dns_data **head = (struct dns_data **)
>     &c->mgr->active_dns_requests;
>      > + if (ev == MG_EV_POLL) {
>      > + uint64_t now = *(uint64_t *) ev_data;
>      > + for (d = *head; d != NULL; d = tmp) {
>      > + tmp = d->next;
>      > + // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
>      > + if (now > d->expire) mg_error(d->c, "DNS timeout");
>      > + }
>      > + } else if (ev == MG_EV_READ) {
>      > + struct mg_dns_message dm;
>      > + int resolved = 0;
>      > + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
>      > + MG_ERROR(("Unexpected DNS response:"));
>      > + mg_hexdump(c->recv.buf, c->recv.len);
>      > + } else {
>      > + // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
>      > + for (d = *head; d != NULL; d = tmp) {
>      > + tmp = d->next;
>      > + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
>      > + if (dm.txnid != d->txnid) continue;
>      > + if (d->c->is_resolving) {
>      > + if (dm.resolved) {
>      > + dm.addr.port = d->c->rem.port; // Save port
>      > + d->c->rem = dm.addr; // Copy resolved address
>      > + MG_DEBUG(
>      > + ("%lu %s is %M", d->c->id, dm.name <http://dm.name>,
>     mg_print_ip, &d->c->rem));
>      > + mg_connect_resolved(d->c);
>      > +#if MG_ENABLE_IPV6
>      > + } else if (dm.addr.is_ip6 == false && dm.name
>     <http://dm.name>[0] != '\0' &&
>      > + c->mgr->use_dns6 == false) {
>      > + struct mg_str x = mg_str(dm.name <http://dm.name>);
>      > + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
>      > +#endif
>      > + } else {
>      > + mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
>      > + }
>      > + } else {
>      > + MG_ERROR(("%lu already resolved", d->c->id));
>      > + }
>      > + mg_dns_free(head, d);
>      > + resolved = 1;
>      > + }
>      > + }
>      > + if (!resolved) MG_ERROR(("stray DNS reply"));
>      > + c->recv.len = 0;
>      > + } else if (ev == MG_EV_CLOSE) {
>      > + for (d = *head; d != NULL; d = tmp) {
>      > + tmp = d->next;
>      > + mg_error(d->c, "DNS error");
>      > + mg_dns_free(head, d);
>      > + }
>      > + }
>      > }
>      >
>      > -static bool ff_mkdir(const char *path) {
>      > - return f_mkdir(path) == FR_OK;
>      > +static bool mg_dns_send(struct mg_connection *c, const struct
>     mg_str *name,
>      > + uint16_t txnid, bool ipv6) {
>      > + struct {
>      > + struct mg_dns_header header;
>      > + uint8_t data[256];
>      > + } pkt;
>      > + size_t i, n;
>      > + memset(&pkt, 0, sizeof(pkt));
>      > + pkt.header.txnid = mg_htons(txnid);
>      > + pkt.header.flags = mg_htons(0x100);
>      > + pkt.header.num_questions = mg_htons(1);
>      > + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
>      > + if (name->buf[i] == '.' || i >= name->len) {
>      > + pkt.data[n] = (uint8_t) (i - n);
>      > + memcpy(&pkt.data[n + 1], name->buf + n, i - n);
>      > + n = i + 1;
>      > + }
>      > + if (i >= name->len) break;
>      > + }
>      > + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
>      > + n += 5;
>      > + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
>      > + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
>     query
>      > + // n += 6;
>      > + return mg_send(c, &pkt, sizeof(pkt.header) + n);
>      > }
>      >
>      > -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
>     ff_read,
>      > - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
>      > -#endif
>      > +static void mg_sendnsreq(struct mg_connection *c, struct mg_str
>     *name, int ms,
>      > + struct mg_dns *dnsc, bool ipv6) {
>      > + struct dns_data *d = NULL;
>      > + if (dnsc->url == NULL) {
>      > + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
>      > + } else if (dnsc->c == NULL) {
>      > + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
>      > + if (dnsc->c != NULL) {
>      > + dnsc->c->pfn = dns_cb;
>      > + // dnsc->c->is_hexdumping = 1;
>      > + }
>      > + }
>      > + if (dnsc->c == NULL) {
>      > + mg_error(c, "resolver");
>      > + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
>     NULL) {
>      > + mg_error(c, "resolve OOM");
>      > + } else {
>      > + struct dns_data *reqs = (struct dns_data *)
>     c->mgr->active_dns_requests;
>      > + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
>      > + d->next = (struct dns_data *) c->mgr->active_dns_requests;
>      > + c->mgr->active_dns_requests = d;
>      > + d->expire = mg_millis() + (uint64_t) ms;
>      > + d->c = c;
>      > + c->is_resolving = 1;
>      > + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
>     name->len,
>      > + name->buf, dnsc->url, d->txnid));
>      > + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
>      > + mg_error(dnsc->c, "DNS send");
>      > + }
>      > + }
>      > +}
>      > +
>      > +void mg_resolve(struct mg_connection *c, const char *url) {
>      > + struct mg_str host = mg_url_host(url);
>      > + c->rem.port = mg_htons(mg_url_port(url));
>      > + if (mg_aton(host, &c->rem)) {
>      > + // host is an IP address, do not fire name resolution
>      > + mg_connect_resolved(c);
>      > + } else {
>      > + // host is not an IP, send DNS resolution request
>      > + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
>     &c->mgr->dns4;
>      > + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
>      > + }
>      > +}
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/fs_packed.c"
>      > +#line 1 "src/event.c"
>      > #endif
>      >
>      >
>      >
>      >
>      > -struct packed_file {
>      > - const char *data;
>      > - size_t size;
>      > - size_t pos;
>      > -};
>      >
>      > -const char *mg_unpack(const char *path, size_t *size, time_t
>     *mtime);
>      > -const char *mg_unlist(size_t no);
>      >
>      > -#if MG_ENABLE_PACKED_FS
>      > -#else
>      > -const char *mg_unpack(const char *path, size_t *size, time_t
>     *mtime) {
>      > - (void) path, (void) size, (void) mtime;
>      > - return NULL;
>      > -}
>      > -const char *mg_unlist(size_t no) {
>      > - (void) no;
>      > - return NULL;
>      > -}
>      > +void mg_call(struct mg_connection *c, int ev, void *ev_data) {
>      > +#if MG_ENABLE_PROFILE
>      > + const char *names[] = {
>      > + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE",
>      > + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ",
>      > + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK",
>      > + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD",
>      > + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
>      > + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) /
>     sizeof(names[0]))) {
>      > + MG_PROF_ADD(c, names[ev]);
>      > + }
>      > #endif
>      > -
>      > -static int is_dir_prefix(const char *prefix, size_t n, const
>     char *path) {
>      > - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
>      > - return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
>      > - (n == 0 || path[n] == '/' || path[n - 1] == '/');
>      > + // Fire protocol handler first, user handler second. See #2559
>      > + if (c->pfn != NULL) c->pfn(c, ev, ev_data);
>      > + if (c->fn != NULL) c->fn(c, ev, ev_data);
>      > }
>      >
>      > -static int packed_stat(const char *path, size_t *size, time_t
>     *mtime) {
>      > - const char *p;
>      > - size_t i, n = strlen(path);
>      > - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
>     file
>      > - // Scan all files. If `path` is a dir prefix for any of them,
>     it's a dir
>      > - for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
>      > - if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
>      > - }
>      > - return 0;
>      > -}
>      > -
>      > -static void packed_list(const char *dir, void (*fn)(const char
>     *, void *),
>      > - void *userdata) {
>      > - char buf[MG_PATH_MAX], tmp[sizeof(buf)];
>      > - const char *path, *begin, *end;
>      > - size_t i, n = strlen(dir);
>      > - tmp[0] = '\0'; // Previously listed entry
>      > - for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
>      > - if (!is_dir_prefix(dir, n, path)) continue;
>      > - begin = &path[n + 1];
>      > - end = strchr(begin, '/');
>      > - if (end == NULL) end = begin + strlen(begin);
>      > - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
>      > - buf[sizeof(buf) - 1] = '\0';
>      > - // If this entry has been already listed, skip
>      > - // NOTE: we're assuming that file list is sorted alphabetically
>      > - if (strcmp(buf, tmp) == 0) continue;
>      > - fn(buf, userdata); // Not yet listed, call user function
>      > - strcpy(tmp, buf); // And save this entry as listed
>      > - }
>      > -}
>      > -
>      > -static void *packed_open(const char *path, int flags) {
>      > - size_t size = 0;
>      > - const char *data = mg_unpack(path, &size, NULL);
>      > - struct packed_file *fp = NULL;
>      > - if (data == NULL) return NULL;
>      > - if (flags & MG_FS_WRITE) return NULL;
>      > - fp = (struct packed_file *) calloc(1, sizeof(*fp));
>      > - fp->size = size;
>      > - fp->data = data;
>      > - return (void *) fp;
>      > +void mg_error(struct mg_connection *c, const char *fmt, ...) {
>      > + char buf[64];
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
>      > + va_end(ap);
>      > + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
>      > + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
>      > + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it
>      > }
>      >
>      > -static void packed_close(void *fp) {
>      > - if (fp != NULL) free(fp);
>      > -}
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/fmt.c"
>      > +#endif
>      >
>      > -static size_t packed_read(void *fd, void *buf, size_t len) {
>      > - struct packed_file *fp = (struct packed_file *) fd;
>      > - if (fp->pos + len > fp->size) len = fp->size - fp->pos;
>      > - memcpy(buf, &fp->data[fp->pos], len);
>      > - fp->pos += len;
>      > - return len;
>      > -}
>      >
>      > -static size_t packed_write(void *fd, const void *buf, size_t len) {
>      > - (void) fd, (void) buf, (void) len;
>      > - return 0;
>      > -}
>      >
>      > -static size_t packed_seek(void *fd, size_t offset) {
>      > - struct packed_file *fp = (struct packed_file *) fd;
>      > - fp->pos = offset;
>      > - if (fp->pos > fp->size) fp->pos = fp->size;
>      > - return fp->pos;
>      > -}
>      >
>      > -static bool packed_rename(const char *from, const char *to) {
>      > - (void) from, (void) to;
>      > - return false;
>      > +static bool is_digit(int c) {
>      > + return c >= '0' && c <= '9';
>      > }
>      >
>      > -static bool packed_remove(const char *path) {
>      > - (void) path;
>      > - return false;
>      > +static int addexp(char *buf, int e, int sign) {
>      > + int n = 0;
>      > + buf[n++] = 'e';
>      > + buf[n++] = (char) sign;
>      > + if (e > 400) return 0;
>      > + if (e < 10) buf[n++] = '0';
>      > + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
>     / 100);
>      > + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
>      > + buf[n++] = (char) (e + '0');
>      > + return n;
>      > }
>      >
>      > -static bool packed_mkdir(const char *path) {
>      > - (void) path;
>      > - return false;
>      > +static int xisinf(double x) {
>      > + union {
>      > + double f;
>      > + uint64_t u;
>      > + } ieee754 = {x};
>      > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
>     0x7ff00000 &&
>      > + ((unsigned) ieee754.u == 0);
>      > }
>      >
>      > -struct mg_fs mg_fs_packed = {
>      > - packed_stat, packed_list, packed_open, packed_close, packed_read,
>      > - packed_write, packed_seek, packed_rename, packed_remove,
>     packed_mkdir};
>      > -
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/fs_posix.c"
>      > -#endif
>      > -
>      > -
>      > -#if MG_ENABLE_FILE
>      > -
>      > -#ifndef MG_STAT_STRUCT
>      > -#define MG_STAT_STRUCT stat
>      > -#endif
>      > -
>      > -#ifndef MG_STAT_FUNC
>      > -#define MG_STAT_FUNC stat
>      > -#endif
>      > -
>      > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
>      > -#if !defined(S_ISDIR)
>      > - MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
>     mtime));
>      > - return 0;
>      > -#else
>      > -#if MG_ARCH == MG_ARCH_WIN32
>      > - struct _stati64 st;
>      > - wchar_t tmp[MG_PATH_MAX];
>      > - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
>     sizeof(tmp[0]));
>      > - if (_wstati64(tmp, &st) != 0) return 0;
>      > -#else
>      > - struct MG_STAT_STRUCT st;
>      > - if (MG_STAT_FUNC(path, &st) != 0) return 0;
>      > -#endif
>      > - if (size) *size = (size_t) st.st_size;
>      > - if (mtime) *mtime = st.st_mtime;
>      > - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
>     MG_FS_DIR : 0);
>      > -#endif
>      > +static int xisnan(double x) {
>      > + union {
>      > + double f;
>      > + uint64_t u;
>      > + } ieee754 = {x};
>      > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
>      > + ((unsigned) ieee754.u != 0) >
>      > + 0x7ff00000;
>      > }
>      >
>      > -#if MG_ARCH == MG_ARCH_WIN32
>      > -struct dirent {
>      > - char d_name[MAX_PATH];
>      > -};
>      > -
>      > -typedef struct win32_dir {
>      > - HANDLE handle;
>      > - WIN32_FIND_DATAW info;
>      > - struct dirent result;
>      > -} DIR;
>      > +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
>     width, bool tz) {
>      > + char buf[40];
>      > + int i, s = 0, n = 0, e = 0;
>      > + double t, mul, saved;
>      > + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
>      > + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
>     "inf" : "-inf");
>      > + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
>      > + if (d < 0.0) d = -d, buf[s++] = '-';
>      >
>      > -int gettimeofday(struct timeval *tv, void *tz) {
>      > - FILETIME ft;
>      > - unsigned __int64 tmpres = 0;
>      > + // Round
>      > + saved = d;
>      > + mul = 1.0;
>      > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
>      > + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
>      > + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
>      > + d += t;
>      > + // Calculate exponent, and 'mul' for scientific representation
>      > + mul = 1.0;
>      > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
>      > + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
>      > + // printf(" --> %g %d %g %g\n", saved, e, t, mul);
>      >
>      > - if (tv != NULL) {
>      > - GetSystemTimeAsFileTime(&ft);
>      > - tmpres |= ft.dwHighDateTime;
>      > - tmpres <<= 32;
>      > - tmpres |= ft.dwLowDateTime;
>      > - tmpres /= 10; // convert into microseconds
>      > - tmpres -= (int64_t) 11644473600000000;
>      > - tv->tv_sec = (long) (tmpres / 1000000UL);
>      > - tv->tv_usec = (long) (tmpres % 1000000UL);
>      > + if (e >= width && width > 1) {
>      > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
>      > + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
>      > + n += addexp(buf + s + n, e, '+');
>      > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>      > + } else if (e <= -width && width > 1) {
>      > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
>      > + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
>      > + n += addexp(buf + s + n, -e, '-');
>      > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>      > + } else {
>      > + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
>      > + int ch = (int) (d / t);
>      > + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
>      > + d -= ch * t;
>      > + t /= 10.0;
>      > + }
>      > + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
>     + n, buf);
>      > + if (n == 0) buf[s++] = '0';
>      > + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
>     /= 10.0;
>      > + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
>      > + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
>      > + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
>     i++) {
>      > + int ch = (int) (d / t);
>      > + buf[s + n++] = (char) (ch + '0');
>      > + d -= ch * t;
>      > + t /= 10.0;
>      > + }
>      > }
>      > - (void) tz;
>      > - return 0;
>      > + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim
>     trailing zeroes
>      > + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
>      > + n += s;
>      > + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
>      > + buf[n] = '\0';
>      > + return mg_snprintf(dst, dstlen, "%s", buf);
>      > }
>      >
>      > -static int to_wchar(const char *path, wchar_t *wbuf, size_t
>     wbuf_len) {
>      > - int ret;
>      > - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
>      > - strncpy(buf, path, sizeof(buf));
>      > - buf[sizeof(buf) - 1] = '\0';
>      > - // Trim trailing slashes. Leave backslash for paths like "X:\"
>      > - p = buf + strlen(buf) - 1;
>      > - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
>     '/')) *p-- = '\0';
>      > - memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
>      > - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
>     wbuf_len);
>      > - // Convert back to Unicode. If doubly-converted string does not
>     match the
>      > - // original, something is fishy, reject.
>      > - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
>     sizeof(buf2),
>      > - NULL, NULL);
>      > - if (strcmp(buf, buf2) != 0) {
>      > - wbuf[0] = L'\0';
>      > - ret = 0;
>      > +static size_t mg_lld(char *buf, int64_t val, bool is_signed,
>     bool is_hex) {
>      > + const char *letters = "0123456789abcdef";
>      > + uint64_t v = (uint64_t) val;
>      > + size_t s = 0, n, i;
>      > + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
>      > + // This loop prints a number in reverse order. I guess this is
>     because we
>      > + // write numbers from right to left: least significant digit
>     comes last.
>      > + // Maybe because we use Arabic numbers, and Arabs write RTL?
>      > + if (is_hex) {
>      > + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
>      > + } else {
>      > + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
>      > }
>      > - return ret;
>      > + // Reverse a string
>      > + for (i = 0; i < n / 2; i++) {
>      > + char t = buf[s + i];
>      > + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
>      > + }
>      > + if (val == 0) buf[n++] = '0'; // Handle special case
>      > + return n + s;
>      > }
>      >
>      > -DIR *opendir(const char *name) {
>      > - DIR *d = NULL;
>      > - wchar_t wpath[MAX_PATH];
>      > - DWORD attrs;
>      > +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
>      > + size_t len) {
>      > + size_t i = 0;
>      > + while (i < len && buf[i] != '\0') out(buf[i++], ptr);
>      > + return i;
>      > +}
>      >
>      > - if (name == NULL) {
>      > - SetLastError(ERROR_BAD_ARGUMENTS);
>      > - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
>      > - SetLastError(ERROR_NOT_ENOUGH_MEMORY);
>      > - } else {
>      > - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
>      > - attrs = GetFileAttributesW(wpath);
>      > - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
>      > - (void) wcscat(wpath, L"\\*");
>      > - d->handle = FindFirstFileW(wpath, &d->info);
>      > - d->result.d_name[0] = '\0';
>      > +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
>     char *fmt, ...) {
>      > + size_t len = 0;
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + len = mg_vxprintf(out, ptr, fmt, &ap);
>      > + va_end(ap);
>      > + return len;
>      > +}
>      > +
>      > +size_t mg_vxprintf(void (*out)(char, void *), void *param, const
>     char *fmt,
>      > + va_list *ap) {
>      > + size_t i = 0, n = 0;
>      > + while (fmt[i] != '\0') {
>      > + if (fmt[i] == '%') {
>      > + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
>     prec */;
>      > + char pad = ' ', minus = 0, c = fmt[++i];
>      > + if (c == '#') x++, c = fmt[++i];
>      > + if (c == '-') minus++, c = fmt[++i];
>      > + if (c == '0') pad = '0', c = fmt[++i];
>      > + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
>     fmt[++i];
>      > + if (c == '.') {
>      > + c = fmt[++i];
>      > + if (c == '*') {
>      > + pr = (size_t) va_arg(*ap, int);
>      > + c = fmt[++i];
>      > + } else {
>      > + pr = 0;
>      > + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
>     fmt[++i];
>      > + }
>      > + }
>      > + while (c == 'h') c = fmt[++i]; // Treat h and hh as int
>      > + if (c == 'l') {
>      > + is_long++, c = fmt[++i];
>      > + if (c == 'l') is_long++, c = fmt[++i];
>      > + }
>      > + if (c == 'p') x = 1, is_long = 1;
>      > + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
>      > + c == 'g' || c == 'f') {
>      > + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
>      > + char tmp[40];
>      > + size_t xl = x ? 2 : 0;
>      > + if (c == 'g' || c == 'f') {
>      > + double v = va_arg(*ap, double);
>      > + if (pr == ~0U) pr = 6;
>      > + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
>      > + } else if (is_long == 2) {
>      > + int64_t v = va_arg(*ap, int64_t);
>      > + k = mg_lld(tmp, v, s, h);
>      > + } else if (is_long == 1) {
>      > + long v = va_arg(*ap, long);
>      > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
>     s, h);
>      > + } else {
>      > + int v = va_arg(*ap, int);
>      > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
>      > + }
>      > + for (j = 0; j < xl && w > 0; j++) w--;
>      > + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
>      > + n += scpy(out, param, &pad, 1);
>      > + n += scpy(out, param, (char *) "0x", xl);
>      > + for (j = 0; pad == '0' && k < w && j + k < w; j++)
>      > + n += scpy(out, param, &pad, 1);
>      > + n += scpy(out, param, tmp, k);
>      > + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
>      > + n += scpy(out, param, &pad, 1);
>      > + } else if (c == 'm' || c == 'M') {
>      > + mg_pm_t f = va_arg(*ap, mg_pm_t);
>      > + if (c == 'm') out('"', param);
>      > + n += f(out, param, ap);
>      > + if (c == 'm') n += 2, out('"', param);
>      > + } else if (c == 'c') {
>      > + int ch = va_arg(*ap, int);
>      > + out((char) ch, param);
>      > + n++;
>      > + } else if (c == 's') {
>      > + char *p = va_arg(*ap, char *);
>      > + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
>      > + for (j = 0; !minus && pr < w && j + pr < w; j++)
>      > + n += scpy(out, param, &pad, 1);
>      > + n += scpy(out, param, p, pr);
>      > + for (j = 0; minus && pr < w && j + pr < w; j++)
>      > + n += scpy(out, param, &pad, 1);
>      > + } else if (c == '%') {
>      > + out('%', param);
>      > + n++;
>      > + } else {
>      > + out('%', param);
>      > + out(c, param);
>      > + n += 2;
>      > + }
>      > + i++;
>      > } else {
>      > - free(d);
>      > - d = NULL;
>      > + out(fmt[i], param), n++, i++;
>      > }
>      > }
>      > - return d;
>      > + return n;
>      > }
>      >
>      > -int closedir(DIR *d) {
>      > - int result = 0;
>      > - if (d != NULL) {
>      > - if (d->handle != INVALID_HANDLE_VALUE)
>      > - result = FindClose(d->handle) ? 0 : -1;
>      > - free(d);
>      > - } else {
>      > - result = -1;
>      > - SetLastError(ERROR_BAD_ARGUMENTS);
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/fs.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>     flags) {
>      > + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
>      > + if (fd != NULL) {
>      > + fd->fd = fs->op(path, flags);
>      > + fd->fs = fs;
>      > + if (fd->fd == NULL) {
>      > + free(fd);
>      > + fd = NULL;
>      > + }
>      > }
>      > - return result;
>      > + return fd;
>      > }
>      >
>      > -struct dirent *readdir(DIR *d) {
>      > - struct dirent *result = NULL;
>      > - if (d != NULL) {
>      > - memset(&d->result, 0, sizeof(d->result));
>      > - if (d->handle != INVALID_HANDLE_VALUE) {
>      > - result = &d->result;
>      > - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
>     result->d_name,
>      > - sizeof(result->d_name), NULL, NULL);
>      > - if (!FindNextFileW(d->handle, &d->info)) {
>      > - FindClose(d->handle);
>      > - d->handle = INVALID_HANDLE_VALUE;
>      > - }
>      > - } else {
>      > - SetLastError(ERROR_FILE_NOT_FOUND);
>      > - }
>      > - } else {
>      > - SetLastError(ERROR_BAD_ARGUMENTS);
>      > +void mg_fs_close(struct mg_fd *fd) {
>      > + if (fd != NULL) {
>      > + fd->fs->cl(fd->fd);
>      > + free(fd);
>      > }
>      > - return result;
>      > }
>      > -#endif
>      >
>      > -static void p_list(const char *dir, void (*fn)(const char *,
>     void *),
>      > - void *userdata) {
>      > -#if MG_ENABLE_DIRLIST
>      > - struct dirent *dp;
>      > - DIR *dirp;
>      > - if ((dirp = (opendir(dir))) == NULL) return;
>      > - while ((dp = readdir(dirp)) != NULL) {
>      > - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
>     continue;
>      > - fn(dp->d_name, userdata);
>      > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
>      > + struct mg_str result = {NULL, 0};
>      > + void *fp;
>      > + fs->st(path, &result.len, NULL);
>      > + if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
>      > + result.buf = (char *) calloc(1, result.len + 1);
>      > + if (result.buf != NULL &&
>      > + fs->rd(fp, (void *) result.buf, result.len) != result.len) {
>      > + free((void *) result.buf);
>      > + result.buf = NULL;
>      > + }
>      > + fs->cl(fp);
>      > }
>      > - closedir(dirp);
>      > -#else
>      > - (void) dir, (void) fn, (void) userdata;
>      > -#endif
>      > + if (result.buf == NULL) result.len = 0;
>      > + return result;
>      > }
>      >
>      > -static void *p_open(const char *path, int flags) {
>      > - const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
>      > -#if MG_ARCH == MG_ARCH_WIN32
>      > - wchar_t b1[MG_PATH_MAX], b2[10];
>      > - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
>     sizeof(b1[0]));
>      > - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
>     sizeof(b2[0]));
>      > - return (void *) _wfopen(b1, b2);
>      > -#else
>      > - return (void *) fopen(path, mode);
>      > -#endif
>      > +bool mg_file_write(struct mg_fs *fs, const char *path, const
>     void *buf,
>      > + size_t len) {
>      > + bool result = false;
>      > + struct mg_fd *fd;
>      > + char tmp[MG_PATH_MAX];
>      > + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
>      > + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
>      > + result = fs->wr(fd->fd, buf, len) == len;
>      > + mg_fs_close(fd);
>      > + if (result) {
>      > + fs->rm(path);
>      > + fs->mv(tmp, path);
>      > + } else {
>      > + fs->rm(tmp);
>      > + }
>      > + }
>      > + return result;
>      > }
>      >
>      > -static void p_close(void *fp) {
>      > - fclose((FILE *) fp);
>      > +bool mg_file_printf(struct mg_fs *fs, const char *path, const
>     char *fmt, ...) {
>      > + va_list ap;
>      > + char *data;
>      > + bool result = false;
>      > + va_start(ap, fmt);
>      > + data = mg_vmprintf(fmt, &ap);
>      > + va_end(ap);
>      > + result = mg_file_write(fs, path, data, strlen(data));
>      > + free(data);
>      > + return result;
>      > }
>      >
>      > -static size_t p_read(void *fp, void *buf, size_t len) {
>      > - return fread(buf, 1, len, (FILE *) fp);
>      > +// This helper function allows to scan a filesystem in a
>     sequential way,
>      > +// without using callback function:
>      > +// char buf[100] = "";
>      > +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
>      > +// ...
>      > +static void mg_fs_ls_fn(const char *filename, void *param) {
>      > + struct mg_str *s = (struct mg_str *) param;
>      > + if (s->buf[0] == '\0') {
>      > + mg_snprintf((char *) s->buf, s->len, "%s", filename);
>      > + } else if (strcmp(s->buf, filename) == 0) {
>      > + ((char *) s->buf)[0] = '\0'; // Fetch next file
>      > + }
>      > }
>      >
>      > -static size_t p_write(void *fp, const void *buf, size_t len) {
>      > - return fwrite(buf, 1, len, (FILE *) fp);
>      > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
>     size_t len) {
>      > + struct mg_str s = {buf, len};
>      > + fs->ls(path, mg_fs_ls_fn, &s);
>      > + return buf[0] != '\0';
>      > }
>      >
>      > -static size_t p_seek(void *fp, size_t offset) {
>      > -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
>      > - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
>      > - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
>      > - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
>      > -#else
>      > - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/fs_fat.c"
>      > #endif
>      > - return (size_t) ftell((FILE *) fp);
>      > -}
>      >
>      > -static bool p_rename(const char *from, const char *to) {
>      > - return rename(from, to) == 0;
>      > -}
>      >
>      > -static bool p_remove(const char *path) {
>      > - return remove(path) == 0;
>      > -}
>      >
>      > -static bool p_mkdir(const char *path) {
>      > - return mkdir(path, 0775) == 0;
>      > -}
>      > +#if MG_ENABLE_FATFS
>      > +#include <ff.h>
>      >
>      > -#else
>      > +static int mg_days_from_epoch(int y, int m, int d) {
>      > + y -= m <= 2;
>      > + int era = y / 400;
>      > + int yoe = y - era * 400;
>      > + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
>      > + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
>      > + return era * 146097 + doe - 719468;
>      > +}
>      >
>      > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
>      > - (void) path, (void) size, (void) mtime;
>      > - return 0;
>      > +static time_t mg_timegm(const struct tm *t) {
>      > + int year = t->tm_year + 1900;
>      > + int month = t->tm_mon; // 0-11
>      > + if (month > 11) {
>      > + year += month / 12;
>      > + month %= 12;
>      > + } else if (month < 0) {
>      > + int years_diff = (11 - month) / 12;
>      > + year -= years_diff;
>      > + month += 12 * years_diff;
>      > + }
>      > + int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
>      > + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
>      > }
>      > -static void p_list(const char *path, void (*fn)(const char *,
>     void *),
>      > - void *userdata) {
>      > - (void) path, (void) fn, (void) userdata;
>      > -}
>      > -static void *p_open(const char *path, int flags) {
>      > - (void) path, (void) flags;
>      > - return NULL;
>      > -}
>      > -static void p_close(void *fp) {
>      > - (void) fp;
>      > -}
>      > -static size_t p_read(void *fd, void *buf, size_t len) {
>      > - (void) fd, (void) buf, (void) len;
>      > - return 0;
>      > -}
>      > -static size_t p_write(void *fd, const void *buf, size_t len) {
>      > - (void) fd, (void) buf, (void) len;
>      > - return 0;
>      > -}
>      > -static size_t p_seek(void *fd, size_t offset) {
>      > - (void) fd, (void) offset;
>      > - return (size_t) ~0;
>      > -}
>      > -static bool p_rename(const char *from, const char *to) {
>      > - (void) from, (void) to;
>      > - return false;
>      > -}
>      > -static bool p_remove(const char *path) {
>      > - (void) path;
>      > - return false;
>      > -}
>      > -static bool p_mkdir(const char *path) {
>      > - (void) path;
>      > - return false;
>      > -}
>      > -#endif
>      > -
>      > -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
>     p_read,
>      > - p_write, p_seek, p_rename, p_remove, p_mkdir};
>      > -
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/http.c"
>      > -#endif
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -
>      > -// Chunk deletion marker is the MSB in the "processed" counter
>      > -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
>      > -
>      > -// Multipart POST example:
>      > -// --xyz
>      > -// Content-Disposition: form-data; name="val"
>      > -//
>      > -// abcdef
>      > -// --xyz
>      > -// Content-Disposition: form-data; name="foo"; filename="a.txt"
>      > -// Content-Type: text/plain
>      > -//
>      > -// hello world
>      > -//
>      > -// --xyz--
>      > -size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
>      > - struct mg_http_part *part) {
>      > - struct mg_str cd = mg_str_n("Content-Disposition", 19);
>      > - const char *s = body.ptr;
>      > - size_t b = ofs, h1, h2, b1, b2, max = body.len;
>      > -
>      > - // Init part params
>      > - if (part != NULL) part->name = part->filename = part->body =
>     mg_str_n(0, 0);
>      > -
>      > - // Skip boundary
>      > - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
>      > - if (b <= ofs || b + 2 >= max) return 0;
>      > - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
>     s));
>      > -
>      > - // Skip headers
>      > - h1 = h2 = b + 2;
>      > - for (;;) {
>      > - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
>      > - if (h2 == h1) break;
>      > - if (h2 + 2 >= max) return 0;
>      > - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
>      > - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
>     ':' &&
>      > - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
>      > - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
>     cd.len + 2));
>      > - part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
>      > - part->filename = mg_http_get_header_var(v, mg_str_n("filename",
>     8));
>      > - }
>      > - h1 = h2 = h2 + 2;
>      > - }
>      > - b1 = b2 = h2 + 2;
>      > - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
>     + 1] == '\n' &&
>      > - memcmp(&s[b2 + 2], s, b - ofs) == 0))
>      > - b2++;
>      >
>      > - if (b2 + 2 >= max) return 0;
>      > - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
>      > - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
>      > - return b2 + 2;
>      > +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
>      > + struct tm tm;
>      > + memset(&tm, 0, sizeof(struct tm));
>      > + tm.tm_sec = (ftime << 1) & 0x3e;
>      > + tm.tm_min = ((ftime >> 5) & 0x3f);
>      > + tm.tm_hour = ((ftime >> 11) & 0x1f);
>      > + tm.tm_mday = (fdate & 0x1f);
>      > + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
>      > + tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
>      > + return mg_timegm(&tm);
>      > }
>      >
>      > -void mg_http_bauth(struct mg_connection *c, const char *user,
>      > - const char *pass) {
>      > - struct mg_str u = mg_str(user), p = mg_str(pass);
>      > - size_t need = c->send.len + 36 + (u.len + p.len) * 2;
>      > - if (c->send.size < need) mg_iobuf_resize(&c->send, need);
>      > - if (c->send.size >= need) {
>      > - int i, n = 0;
>      > - char *buf = (char *) &c->send.buf[c->send.len];
>      > - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
>      > - for (i = 0; i < (int) u.len; i++) {
>      > - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
>      > - }
>      > - if (p.len > 0) {
>      > - n = mg_base64_update(':', buf + 21, n);
>      > - for (i = 0; i < (int) p.len; i++) {
>      > - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
>      > - }
>      > - }
>      > - n = mg_base64_final(buf + 21, n);
>      > - c->send.len += 21 + (size_t) n + 2;
>      > - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
>      > +static int ff_stat(const char *path, size_t *size, time_t *mtime) {
>      > + FILINFO fi;
>      > + if (path[0] == '\0') {
>      > + if (size) *size = 0;
>      > + if (mtime) *mtime = 0;
>      > + return MG_FS_DIR;
>      > + } else if (f_stat(path, &fi) == 0) {
>      > + if (size) *size = (size_t) fi.fsize;
>      > + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
>      > + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
>     MG_FS_DIR : 0);
>      > } else {
>      > - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
>     need));
>      > + return 0;
>      > }
>      > }
>      >
>      > -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
>      > - struct mg_str k, v, result = mg_str_n(NULL, 0);
>      > - while (mg_split(&buf, &k, &v, '&')) {
>      > - if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) ==
>     0) {
>      > - result = v;
>      > - break;
>      > +static void ff_list(const char *dir, void (*fn)(const char *,
>     void *),
>      > + void *userdata) {
>      > + DIR d;
>      > + FILINFO fi;
>      > + if (f_opendir(&d, dir) == FR_OK) {
>      > + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
>      > + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
>      > + fn(fi.fname, userdata);
>      > }
>      > + f_closedir(&d);
>      > }
>      > - return result;
>      > }
>      >
>      > -int mg_http_get_var(const struct mg_str *buf, const char *name,
>     char *dst,
>      > - size_t dst_len) {
>      > - int len;
>      > - if (dst == NULL || dst_len == 0) {
>      > - len = -2; // Bad destination
>      > - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
>      > - len = -1; // Bad source
>      > - dst[0] = '\0';
>      > - } else {
>      > - struct mg_str v = mg_http_var(*buf, mg_str(name));
>      > - if (v.ptr == NULL) {
>      > - len = -4; // Name does not exist
>      > - } else {
>      > - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
>      > - if (len < 0) len = -3; // Failed to decode
>      > +static void *ff_open(const char *path, int flags) {
>      > + FIL f;
>      > + unsigned char mode = FA_READ;
>      > + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
>     FA_OPEN_APPEND;
>      > + if (f_open(&f, path, mode) == 0) {
>      > + FIL *fp;
>      > + if ((fp = calloc(1, sizeof(*fp))) != NULL) {
>      > + memcpy(fp, &f, sizeof(*fp));
>      > + return fp;
>      > }
>      > }
>      > - return len;
>      > + return NULL;
>      > }
>      >
>      > -static bool isx(int c) {
>      > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>      > - (c >= 'A' && c <= 'F');
>      > +static void ff_close(void *fp) {
>      > + if (fp != NULL) {
>      > + f_close((FIL *) fp);
>      > + free(fp);
>      > + }
>      > }
>      >
>      > -int mg_url_decode(const char *src, size_t src_len, char *dst,
>     size_t dst_len,
>      > - int is_form_url_encoded) {
>      > - size_t i, j;
>      > - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
>      > - if (src[i] == '%') {
>      > - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
>     src_len
>      > - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
>      > - mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
>      > - i += 2;
>      > - } else {
>      > - return -1;
>      > - }
>      > - } else if (is_form_url_encoded && src[i] == '+') {
>      > - dst[j] = ' ';
>      > - } else {
>      > - dst[j] = src[i];
>      > - }
>      > +static size_t ff_read(void *fp, void *buf, size_t len) {
>      > + UINT n = 0, misalign = ((size_t) buf) & 3;
>      > + if (misalign) {
>      > + char aligned[4];
>      > + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
>      > + memcpy(buf, aligned, n);
>      > + } else {
>      > + f_read((FIL *) fp, buf, len, &n);
>      > }
>      > - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
>      > - return i >= src_len && j < dst_len ? (int) j : -1;
>      > + return n;
>      > }
>      >
>      > -static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c
>      >= ' '; }
>      > -
>      > -int mg_http_get_request_len(const unsigned char *buf, size_t
>     buf_len) {
>      > - size_t i;
>      > - for (i = 0; i < buf_len; i++) {
>      > - if (!isok(buf[i])) return -1;
>      > - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
>      > - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
>     '\n'))
>      > - return (int) i + 1;
>      > - }
>      > - return 0;
>      > +static size_t ff_write(void *fp, const void *buf, size_t len) {
>      > + UINT n = 0;
>      > + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
>     : 0;
>      > }
>      >
>      > -static const char *skip(const char *s, const char *e, const char
>     *d,
>      > - struct mg_str *v) {
>      > - v->ptr = s;
>      > - while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
>      > - v->len = (size_t) (s - v->ptr);
>      > - while (s < e && strchr(d, *s) != NULL) s++;
>      > - return s;
>      > +static size_t ff_seek(void *fp, size_t offset) {
>      > + f_lseek((FIL *) fp, offset);
>      > + return offset;
>      > }
>      >
>      > -struct mg_str *mg_http_get_header(struct mg_http_message *h,
>     const char *name) {
>      > - size_t i, n = strlen(name), max = sizeof(h->headers) /
>     sizeof(h->headers[0]);
>      > - for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
>      > - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
>      > - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
>      > - }
>      > - return NULL;
>      > +static bool ff_rename(const char *from, const char *to) {
>      > + return f_rename(from, to) == FR_OK;
>      > }
>      >
>      > -static void mg_http_parse_headers(const char *s, const char *end,
>      > - struct mg_http_header *h, int max_headers) {
>      > - int i;
>      > - for (i = 0; i < max_headers; i++) {
>      > - struct mg_str k, v, tmp;
>      > - const char *he = skip(s, end, "\n", &tmp);
>      > - s = skip(s, he, ": \r\n", &k);
>      > - s = skip(s, he, "\r\n", &v);
>      > - if (k.len == tmp.len) continue;
>      > - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim
>     spaces
>      > - if (k.len == 0) break;
>      > - // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1,
>     tmp.ptr,
>      > - //(int) k.len, k.ptr, (int) v.len, v.ptr));
>      > - h[i].name = k;
>      > - h[i].value = v;
>      > - }
>      > +static bool ff_remove(const char *path) {
>      > + return f_unlink(path) == FR_OK;
>      > }
>      >
>      > -int mg_http_parse(const char *s, size_t len, struct
>     mg_http_message *hm) {
>      > - int is_response, req_len = mg_http_get_request_len((unsigned
>     char *) s, len);
>      > - const char *end = s == NULL ? NULL : s + req_len, *qs; //
>     Cannot add to NULL
>      > - struct mg_str *cl;
>      > +static bool ff_mkdir(const char *path) {
>      > + return f_mkdir(path) == FR_OK;
>      > +}
>      >
>      > - memset(hm, 0, sizeof(*hm));
>      > - if (req_len <= 0) return req_len;
>      > +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
>     ff_read,
>      > + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
>      > +#endif
>      >
>      > - hm->message.ptr = hm->head.ptr = s;
>      > - hm->body.ptr = end;
>      > - hm->head.len = (size_t) req_len;
>      > - hm->chunk.ptr = end;
>      > - hm->message.len = hm->body.len = (size_t) ~0; // Set body
>     length to infinite
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/fs_packed.c"
>      > +#endif
>      >
>      > - // Parse request line
>      > - s = skip(s, end, " ", &hm->method);
>      > - s = skip(s, end, " ", &hm->uri);
>      > - s = skip(s, end, "\r\n", &hm->proto);
>      >
>      > - // Sanity check. Allow protocol/reason to be empty
>      > - if (hm->method.len == 0 || hm->uri.len == 0) return -1;
>      >
>      > - // If URI contains '?' character, setup query string
>      > - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len))
>     != NULL) {
>      > - hm->query.ptr = qs + 1;
>      > - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
>      > - hm->uri.len = (size_t) (qs - hm->uri.ptr);
>      > - }
>      >
>      > - mg_http_parse_headers(s, end, hm->headers,
>      > - sizeof(hm->headers) / sizeof(hm->headers[0]));
>      > - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
>      > - hm->body.len = (size_t) mg_to64(*cl);
>      > - hm->message.len = (size_t) req_len + hm->body.len;
>      > - }
>      > +struct packed_file {
>      > + const char *data;
>      > + size_t size;
>      > + size_t pos;
>      > +};
>      >
>      > - // mg_http_parse() is used to parse both HTTP requests and HTTP
>      > - // responses. If HTTP response does not have Content-Length
>     set, then
>      > - // body is read until socket is closed, i.e. body.len is
>     infinite (~0).
>      > - //
>      > - // For HTTP requests though, according to
>      > - // http://tools.ietf.org/html/rfc7231#section-8.1.3
>     <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
>      > - // only POST and PUT methods have defined body semantics.
>      > - // Therefore, if Content-Length is not specified and methods are
>      > - // not one of PUT or POST, set body length to 0.
>      > - //
>      > - // So, if it is HTTP request, and Content-Length is not set,
>      > - // and method is not (PUT or POST) then reset body length to zero.
>      > - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
>      > - if (hm->body.len == (size_t) ~0 && !is_response &&
>      > - mg_vcasecmp(&hm->method, "PUT") != 0 &&
>      > - mg_vcasecmp(&hm->method, "POST") != 0) {
>      > - hm->body.len = 0;
>      > - hm->message.len = (size_t) req_len;
>      > - }
>      > +#if MG_ENABLE_PACKED_FS
>      > +#else
>      > +const char *mg_unpack(const char *path, size_t *size, time_t
>     *mtime) {
>      > + *size = 0, *mtime = 0;
>      > + (void) path;
>      > + return NULL;
>      > +}
>      > +const char *mg_unlist(size_t no) {
>      > + (void) no;
>      > + return NULL;
>      > +}
>      > +#endif
>      >
>      > - // The 204 (No content) responses also have 0 body length
>      > - if (hm->body.len == (size_t) ~0 && is_response &&
>      > - mg_vcasecmp(&hm->uri, "204") == 0) {
>      > - hm->body.len = 0;
>      > - hm->message.len = (size_t) req_len;
>      > - }
>      > +struct mg_str mg_unpacked(const char *path) {
>      > + size_t len = 0;
>      > + const char *buf = mg_unpack(path, &len, NULL);
>      > + return mg_str_n(buf, len);
>      > +}
>      >
>      > - return req_len;
>      > +static int is_dir_prefix(const char *prefix, size_t n, const
>     char *path) {
>      > + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
>      > + return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
>      > + (n == 0 || path[n] == '/' || path[n - 1] == '/');
>      > }
>      >
>      > -static void mg_http_vprintf_chunk(struct mg_connection *c, const
>     char *fmt,
>      > - va_list *ap) {
>      > - size_t len = c->send.len;
>      > - mg_send(c, " \r\n", 10);
>      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>      > - if (c->send.len >= len + 10) {
>      > - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
>     - len - 10);
>      > - c->send.buf[len + 8] = '\r';
>      > - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
>     reset marker
>      > +static int packed_stat(const char *path, size_t *size, time_t
>     *mtime) {
>      > + const char *p;
>      > + size_t i, n = strlen(path);
>      > + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
>     file
>      > + // Scan all files. If `path` is a dir prefix for any of them,
>     it's a dir
>      > + for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
>      > + if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
>      > }
>      > - mg_send(c, "\r\n", 2);
>      > + return 0;
>      > }
>      >
>      > -void mg_http_printf_chunk(struct mg_connection *c, const char
>     *fmt, ...) {
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - mg_http_vprintf_chunk(c, fmt, &ap);
>      > - va_end(ap);
>      > +static void packed_list(const char *dir, void (*fn)(const char
>     *, void *),
>      > + void *userdata) {
>      > + char buf[MG_PATH_MAX], tmp[sizeof(buf)];
>      > + const char *path, *begin, *end;
>      > + size_t i, n = strlen(dir);
>      > + tmp[0] = '\0'; // Previously listed entry
>      > + for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
>      > + if (!is_dir_prefix(dir, n, path)) continue;
>      > + begin = &path[n + 1];
>      > + end = strchr(begin, '/');
>      > + if (end == NULL) end = begin + strlen(begin);
>      > + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
>      > + buf[sizeof(buf) - 1] = '\0';
>      > + // If this entry has been already listed, skip
>      > + // NOTE: we're assuming that file list is sorted alphabetically
>      > + if (strcmp(buf, tmp) == 0) continue;
>      > + fn(buf, userdata); // Not yet listed, call user function
>      > + strcpy(tmp, buf); // And save this entry as listed
>      > + }
>      > }
>      >
>      > -void mg_http_write_chunk(struct mg_connection *c, const char
>     *buf, size_t len) {
>      > - mg_printf(c, "%lx\r\n", (unsigned long) len);
>      > - mg_send(c, buf, len);
>      > - mg_send(c, "\r\n", 2);
>      > - if (len == 0) c->is_resp = 0;
>      > +static void *packed_open(const char *path, int flags) {
>      > + size_t size = 0;
>      > + const char *data = mg_unpack(path, &size, NULL);
>      > + struct packed_file *fp = NULL;
>      > + if (data == NULL) return NULL;
>      > + if (flags & MG_FS_WRITE) return NULL;
>      > + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) !=
>     NULL) {
>      > + fp->size = size;
>      > + fp->data = data;
>      > + }
>      > + return (void *) fp;
>      > }
>      >
>      > -// clang-format off
>      > -static const char *mg_http_status_code_str(int status_code) {
>      > - switch (status_code) {
>      > - case 100: return "Continue";
>      > - case 201: return "Created";
>      > - case 202: return "Accepted";
>      > - case 204: return "No Content";
>      > - case 206: return "Partial Content";
>      > - case 301: return "Moved Permanently";
>      > - case 302: return "Found";
>      > - case 304: return "Not Modified";
>      > - case 400: return "Bad Request";
>      > - case 401: return "Unauthorized";
>      > - case 403: return "Forbidden";
>      > - case 404: return "Not Found";
>      > - case 418: return "I'm a teapot";
>      > - case 500: return "Internal Server Error";
>      > - case 501: return "Not Implemented";
>      > - default: return "OK";
>      > - }
>      > +static void packed_close(void *fp) {
>      > + if (fp != NULL) free(fp);
>      > }
>      > -// clang-format on
>      >
>      > -void mg_http_reply(struct mg_connection *c, int code, const char
>     *headers,
>      > - const char *fmt, ...) {
>      > - va_list ap;
>      > - size_t len;
>      > - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
>      > - mg_http_status_code_str(code), headers == NULL ? "" : headers);
>      > - len = c->send.len;
>      > - va_start(ap, fmt);
>      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
>      > - va_end(ap);
>      > - if (c->send.len > 15) {
>      > - mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
>      > - (unsigned long) (c->send.len - len));
>      > - c->is_resp = 0;
>      > - c->send.buf[len - 4] = '\r'; // Change ending 0 to space
>      > - }
>      > - c->is_resp = 0;
>      > +static size_t packed_read(void *fd, void *buf, size_t len) {
>      > + struct packed_file *fp = (struct packed_file *) fd;
>      > + if (fp->pos + len > fp->size) len = fp->size - fp->pos;
>      > + memcpy(buf, &fp->data[fp->pos], len);
>      > + fp->pos += len;
>      > + return len;
>      > }
>      >
>      > -static void http_cb(struct mg_connection *, int, void *, void *);
>      > -static void restore_http_cb(struct mg_connection *c) {
>      > - mg_fs_close((struct mg_fd *) c->pfn_data);
>      > - c->pfn_data = NULL;
>      > - c->pfn = http_cb;
>      > - c->is_resp = 0;
>      > +static size_t packed_write(void *fd, const void *buf, size_t len) {
>      > + (void) fd, (void) buf, (void) len;
>      > + return 0;
>      > }
>      >
>      > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>     mtime);
>      > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>     mtime) {
>      > - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
>     (int64_t) size);
>      > - return buf;
>      > +static size_t packed_seek(void *fd, size_t offset) {
>      > + struct packed_file *fp = (struct packed_file *) fd;
>      > + fp->pos = offset;
>      > + if (fp->pos > fp->size) fp->pos = fp->size;
>      > + return fp->pos;
>      > }
>      >
>      > -static void static_cb(struct mg_connection *c, int ev, void
>     *ev_data,
>      > - void *fn_data) {
>      > - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
>      > - struct mg_fd *fd = (struct mg_fd *) fn_data;
>      > - // Read to send IO buffer directly, avoid extra on-stack buffer
>      > - size_t n, max = MG_IO_SIZE, space;
>      > - size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
>     sizeof(size_t)) /
>      > - sizeof(size_t) * sizeof(size_t)];
>      > - if (c->send.size < max) mg_iobuf_resize(&c->send, max);
>      > - if (c->send.len >= c->send.size) return; // Rate limit
>      > - if ((space = c->send.size - c->send.len) > *cl) space = *cl;
>      > - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
>      > - c->send.len += n;
>      > - *cl -= n;
>      > - if (n == 0) restore_http_cb(c);
>      > - } else if (ev == MG_EV_CLOSE) {
>      > - restore_http_cb(c);
>      > - }
>      > - (void) ev_data;
>      > +static bool packed_rename(const char *from, const char *to) {
>      > + (void) from, (void) to;
>      > + return false;
>      > }
>      >
>      > -// Known mime types. Keep it outside guess_content_type()
>     function, since
>      > -// some environments don't like it defined there.
>      > -// clang-format off
>      > -static struct mg_str s_known_types[] = {
>      > - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
>      > - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
>      > - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
>      > - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
>      > - MG_C_STR("gif"), MG_C_STR("image/gif"),
>      > - MG_C_STR("png"), MG_C_STR("image/png"),
>      > - MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
>      > - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
>      > - MG_C_STR("woff"), MG_C_STR("font/woff"),
>      > - MG_C_STR("ttf"), MG_C_STR("font/ttf"),
>      > - MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
>      > - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
>      > - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
>      > - MG_C_STR("csv"), MG_C_STR("text/csv"),
>      > - MG_C_STR("doc"), MG_C_STR("application/msword"),
>      > - MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
>      > - MG_C_STR("gz"), MG_C_STR("application/gzip"),
>      > - MG_C_STR("ico"), MG_C_STR("image/x-icon"),
>      > - MG_C_STR("json"), MG_C_STR("application/json"),
>      > - MG_C_STR("mov"), MG_C_STR("video/quicktime"),
>      > - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
>      > - MG_C_STR("mp4"), MG_C_STR("video/mp4"),
>      > - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
>      > - MG_C_STR("pdf"), MG_C_STR("application/pdf"),
>      > - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
>      > - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
>      > - MG_C_STR("wav"), MG_C_STR("audio/wav"),
>      > - MG_C_STR("webp"), MG_C_STR("image/webp"),
>      > - MG_C_STR("zip"), MG_C_STR("application/zip"),
>      > - MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
>      > - {0, 0},
>      > -};
>      > -// clang-format on
>      > +static bool packed_remove(const char *path) {
>      > + (void) path;
>      > + return false;
>      > +}
>      >
>      > -static struct mg_str guess_content_type(struct mg_str path,
>     const char *extra) {
>      > - struct mg_str k, v, s = mg_str(extra);
>      > - size_t i = 0;
>      > +static bool packed_mkdir(const char *path) {
>      > + (void) path;
>      > + return false;
>      > +}
>      >
>      > - // Shrink path to its extension only
>      > - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
>      > - path.ptr += path.len - i;
>      > - path.len = i;
>      > +struct mg_fs mg_fs_packed = {
>      > + packed_stat, packed_list, packed_open, packed_close, packed_read,
>      > + packed_write, packed_seek, packed_rename, packed_remove,
>     packed_mkdir};
>      >
>      > - // Process user-provided mime type overrides, if any
>      > - while (mg_commalist(&s, &k, &v)) {
>      > - if (mg_strcmp(path, k) == 0) return v;
>      > - }
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/fs_posix.c"
>      > +#endif
>      >
>      > - // Process built-in mime types
>      > - for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
>      > - if (mg_strcmp(path, s_known_types[i]) == 0) return
>     s_known_types[i + 1];
>      > - }
>      >
>      > - return mg_str("text/plain; charset=utf-8");
>      > -}
>      > +#if MG_ENABLE_POSIX_FS
>      >
>      > -static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
>      > - size_t i, numparsed = 0;
>      > - // MG_INFO(("%.*s", (int) s->len, s->ptr));
>      > - for (i = 0; i + 6 < s->len; i++) {
>      > - if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
>      > - struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
>      > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
>      > - *a = mg_to64(p);
>      > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
>      > - while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++,
>     p.len--;
>      > - if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
>      > - *b = mg_to64(p);
>      > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
>      > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
>      > - break;
>      > - }
>      > - }
>      > - return (int) numparsed;
>      > -}
>      > +#ifndef MG_STAT_STRUCT
>      > +#define MG_STAT_STRUCT stat
>      > +#endif
>      >
>      > -void mg_http_serve_file(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - const char *path,
>      > - const struct mg_http_serve_opts *opts) {
>      > - char etag[64], tmp[MG_PATH_MAX];
>      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > - struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path,
>     MG_FS_READ);
>      > - size_t size = 0;
>      > - time_t mtime = 0;
>      > - struct mg_str *inm = NULL;
>      > - struct mg_str mime = guess_content_type(mg_str(path),
>     opts->mime_types);
>      > - bool gzip = false;
>      > +#ifndef MG_STAT_FUNC
>      > +#define MG_STAT_FUNC stat
>      > +#endif
>      >
>      > - // If file does not exist, we try to open file PATH.gz - and if
>     such
>      > - // pre-compressed .gz file exists, serve it with the
>     Content-Encoding: gzip
>      > - // Note - we ignore Accept-Encoding, cause we don't have a choice
>      > - if (fd == NULL) {
>      > - MG_DEBUG(("NULL [%s]", path));
>      > - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
>      > - if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
>      > - gzip = true;
>      > - path = tmp;
>      > - } else if (opts->page404 != NULL) {
>      > - // No precompressed file, serve 404
>      > - fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
>      > - mime = guess_content_type(mg_str(path), opts->mime_types);
>      > - path = opts->page404;
>      > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
>      > +#if !defined(S_ISDIR)
>      > + MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
>     mtime));
>      > + return 0;
>      > +#else
>      > +#if MG_ARCH == MG_ARCH_WIN32
>      > + struct _stati64 st;
>      > + wchar_t tmp[MG_PATH_MAX];
>      > + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
>     sizeof(tmp[0]));
>      > + if (_wstati64(tmp, &st) != 0) return 0;
>      > + // If path is a symlink, windows reports 0 in st.st_size.
>      > + // Get a real file size by opening it and jumping to the end
>      > + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
>      > + FILE *fp = _wfopen(tmp, L"rb");
>      > + if (fp != NULL) {
>      > + fseek(fp, 0, SEEK_END);
>      > + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on
>     win10+
>      > + fclose(fp);
>      > }
>      > }
>      > +#else
>      > + struct MG_STAT_STRUCT st;
>      > + if (MG_STAT_FUNC(path, &st) != 0) return 0;
>      > +#endif
>      > + if (size) *size = (size_t) st.st_size;
>      > + if (mtime) *mtime = st.st_mtime;
>      > + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
>     MG_FS_DIR : 0);
>      > +#endif
>      > +}
>      >
>      > - if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
>      > - mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
>      > - mg_fs_close(fd);
>      > - // NOTE: mg_http_etag() call should go first!
>      > - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
>     NULL &&
>      > - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
>      > - mg_vcasecmp(inm, etag) == 0) {
>      > - mg_fs_close(fd);
>      > - mg_http_reply(c, 304, opts->extra_headers, "");
>      > - } else {
>      > - int n, status = 200;
>      > - char range[100];
>      > - int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
>      > +#if MG_ARCH == MG_ARCH_WIN32
>      > +struct dirent {
>      > + char d_name[MAX_PATH];
>      > +};
>      >
>      > - // Handle Range header
>      > - struct mg_str *rh = mg_http_get_header(hm, "Range");
>      > - range[0] = '\0';
>      > - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0
>     && r2 >= 0) {
>      > - // If range is specified like "400-", set second limit to
>     content len
>      > - if (n == 1) r2 = cl - 1;
>      > - if (r1 > r2 || r2 >= cl) {
>      > - status = 416;
>      > - cl = 0;
>      > - mg_snprintf(range, sizeof(range), "Content-Range: bytes
>     */%lld\r\n",
>      > - (int64_t) size);
>      > - } else {
>      > - status = 206;
>      > - cl = r2 - r1 + 1;
>      > - mg_snprintf(range, sizeof(range),
>      > - "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
>      > - (int64_t) size);
>      > - fs->sk(fd->fd, (size_t) r1);
>      > - }
>      > - }
>      > - mg_printf(c,
>      > - "HTTP/1.1 %d %s\r\n"
>      > - "Content-Type: %.*s\r\n"
>      > - "Etag: %s\r\n"
>      > - "Content-Length: %llu\r\n"
>      > - "%s%s%s\r\n",
>      > - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
>      > - etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
>      > - opts->extra_headers ? opts->extra_headers : "");
>      > - if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
>      > - c->is_draining = 1;
>      > - c->is_resp = 0;
>      > - mg_fs_close(fd);
>      > - } else {
>      > - // Track to-be-sent content length at the end of c->data, aligned
>      > - size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
>     sizeof(size_t)) /
>      > - sizeof(size_t) * sizeof(size_t)];
>      > - c->pfn = static_cb;
>      > - c->pfn_data = fd;
>      > - *clp = (size_t) cl;
>      > - }
>      > +typedef struct win32_dir {
>      > + HANDLE handle;
>      > + WIN32_FIND_DATAW info;
>      > + struct dirent result;
>      > +} DIR;
>      > +
>      > +#if 0
>      > +int gettimeofday(struct timeval *tv, void *tz) {
>      > + FILETIME ft;
>      > + unsigned __int64 tmpres = 0;
>      > +
>      > + if (tv != NULL) {
>      > + GetSystemTimeAsFileTime(&ft);
>      > + tmpres |= ft.dwHighDateTime;
>      > + tmpres <<= 32;
>      > + tmpres |= ft.dwLowDateTime;
>      > + tmpres /= 10; // convert into microseconds
>      > + tmpres -= (int64_t) 11644473600000000;
>      > + tv->tv_sec = (long) (tmpres / 1000000UL);
>      > + tv->tv_usec = (long) (tmpres % 1000000UL);
>      > }
>      > + (void) tz;
>      > + return 0;
>      > }
>      > +#endif
>      >
>      > -struct printdirentrydata {
>      > - struct mg_connection *c;
>      > - struct mg_http_message *hm;
>      > - const struct mg_http_serve_opts *opts;
>      > - const char *dir;
>      > -};
>      > +static int to_wchar(const char *path, wchar_t *wbuf, size_t
>     wbuf_len) {
>      > + int ret;
>      > + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
>      > + strncpy(buf, path, sizeof(buf));
>      > + buf[sizeof(buf) - 1] = '\0';
>      > + // Trim trailing slashes. Leave backslash for paths like "X:\"
>      > + p = buf + strlen(buf) - 1;
>      > + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
>     '/')) *p-- = '\0';
>      > + memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
>      > + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
>     wbuf_len);
>      > + // Convert back to Unicode. If doubly-converted string does not
>     match the
>      > + // original, something is fishy, reject.
>      > + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
>     sizeof(buf2),
>      > + NULL, NULL);
>      > + if (strcmp(buf, buf2) != 0) {
>      > + wbuf[0] = L'\0';
>      > + ret = 0;
>      > + }
>      > + return ret;
>      > +}
>      >
>      > -static void printdirentry(const char *name, void *userdata) {
>      > - struct printdirentrydata *d = (struct printdirentrydata *)
>     userdata;
>      > - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
>     d->opts->fs;
>      > - size_t size = 0;
>      > - time_t t = 0;
>      > - char path[MG_PATH_MAX], sz[40], mod[40];
>      > - int flags, n = 0;
>      > +DIR *opendir(const char *name) {
>      > + DIR *d = NULL;
>      > + wchar_t wpath[MAX_PATH];
>      > + DWORD attrs;
>      >
>      > - // MG_DEBUG(("[%s] [%s]", d->dir, name));
>      > - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
>      > - sizeof(path)) {
>      > - MG_ERROR(("%s truncated", name));
>      > - } else if ((flags = fs->st(path, &size, &t)) == 0) {
>      > - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
>      > + if (name == NULL) {
>      > + SetLastError(ERROR_BAD_ARGUMENTS);
>      > + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
>      > + SetLastError(ERROR_NOT_ENOUGH_MEMORY);
>      > } else {
>      > - const char *slash = flags & MG_FS_DIR ? "/" : "";
>      > - if (flags & MG_FS_DIR) {
>      > - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
>      > + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
>      > + attrs = GetFileAttributesW(wpath);
>      > + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
>      > + (void) wcscat(wpath, L"\\*");
>      > + d->handle = FindFirstFileW(wpath, &d->info);
>      > + d->result.d_name[0] = '\0';
>      > } else {
>      > - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
>      > + free(d);
>      > + d = NULL;
>      > }
>      > -#if defined(MG_HTTP_DIRLIST_TIME)
>      > - char time_str[30];
>      > - struct tm *time_info = localtime(&t);
>      > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>     time_info);
>      > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
>      > -#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
>      > - char time_str[30];
>      > - struct tm *time_info = gmtime(&t);
>      > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>     time_info);
>      > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
>      > -#else
>      > - mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
>      > -#endif
>      > - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
>      > - mg_printf(d->c,
>      > - " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
>      > - "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
>      > - n, path, slash, name, slash, (unsigned long) t, mod,
>      > - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
>      > }
>      > + return d;
>      > }
>      >
>      > -static void listdir(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - const struct mg_http_serve_opts *opts, char *dir) {
>      > - const char *sort_js_code =
>      > - "<script>function srt(tb, sc, so, d) {"
>      > - "var tr = Array.prototype.slice.call(tb.rows, 0),"
>      > - "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
>     b.cells[sc],"
>      > - "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
>      > - "t1 = a.cells[2].getAttribute('name'), "
>      > - "t2 = b.cells[2].getAttribute('name'); "
>      > - "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
>      > - "n1 ? parseInt(n2) - parseInt(n1) : "
>      > - "c1.textContent.trim().localeCompare(c2.textContent.trim()));
>     });";
>      > - const char *sort_js_code2 =
>      > - "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
>      > - "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
>      > - "};"
>      > - "window.onload = function() {"
>      > - "var tb = document.getElementById('tb');"
>      > - "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
>     [0, 2, 1];"
>      > - "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
>      > - "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
>     c, so); "
>      > - "sc = c; ev.preventDefault();}};"
>      > - "srt(tb, sc, so, true);"
>      > - "}"
>      > - "</script>";
>      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > - struct printdirentrydata d = {c, hm, opts, dir};
>      > - char tmp[10], buf[MG_PATH_MAX];
>      > - size_t off, n;
>      > - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf,
>     sizeof(buf), 0);
>      > - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
>     hm->uri;
>      > -
>      > - mg_printf(c,
>      > - "HTTP/1.1 200 OK\r\n"
>      > - "Content-Type: text/html; charset=utf-8\r\n"
>      > - "%s"
>      > - "Content-Length: \r\n\r\n",
>      > - opts->extra_headers == NULL ? "" : opts->extra_headers);
>      > - off = c->send.len; // Start of body
>      > - mg_printf(c,
>      > - "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
>      > - "<style>th,td {text-align: left; padding-right: 1em; "
>      > - "font-family: monospace; }</style></head>"
>      > - "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
>      > - "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
>      > - "<a href=\"#\" rel=\"1\">Modified</a></th>"
>      > - "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
>      > - "<tr><td colspan=\"3\"><hr></td></tr>"
>      > - "</thead>"
>      > - "<tbody id=\"tb\">\n",
>      > - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int)
>     uri.len,
>      > - uri.ptr);
>      > - mg_printf(c, "%s",
>      > - " <tr><td><a href=\"..\">..</a></td>"
>      > - "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
>      > -
>      > - fs->ls(dir, printdirentry, &d);
>      > - mg_printf(c,
>      > - "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
>      > - "</table><address>Mongoose v.%s</address></body></html>\n",
>      > - MG_VERSION);
>      > - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
>     (c->send.len - off));
>      > - if (n > sizeof(tmp)) n = 0;
>      > - memcpy(c->send.buf + off - 12, tmp, n); // Set content length
>      > - c->is_resp = 0; // Mark response end
>      > +int closedir(DIR *d) {
>      > + int result = 0;
>      > + if (d != NULL) {
>      > + if (d->handle != INVALID_HANDLE_VALUE)
>      > + result = FindClose(d->handle) ? 0 : -1;
>      > + free(d);
>      > + } else {
>      > + result = -1;
>      > + SetLastError(ERROR_BAD_ARGUMENTS);
>      > + }
>      > + return result;
>      > }
>      >
>      > -// Resolve requested file into `path` and return its fs->st()
>     result
>      > -static int uri_to_path2(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - struct mg_fs *fs, struct mg_str url, struct mg_str dir,
>      > - char *path, size_t path_size) {
>      > - int flags, tmp;
>      > - // Append URI to the root_dir, and sanitize it
>      > - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
>     dir.ptr);
>      > - if (n > path_size) n = path_size;
>      > - path[path_size - 1] = '\0';
>      > - if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
>      > - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len,
>     path + n,
>      > - path_size - n, 0);
>      > - path[path_size - 1] = '\0'; // Double-check
>      > - mg_remove_double_dots(path);
>      > - n = strlen(path);
>      > - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
>     trailing slashes
>      > - flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path,
>     NULL, NULL);
>      > - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
>     hm->uri.ptr, path,
>      > - flags));
>      > - if (flags == 0) {
>      > - // Do nothing - let's caller decide
>      > - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
>      > - hm->uri.ptr[hm->uri.len - 1] != '/') {
>      > - mg_printf(c,
>      > - "HTTP/1.1 301 Moved\r\n"
>      > - "Location: %.*s/\r\n"
>      > - "Content-Length: 0\r\n"
>      > +struct dirent *readdir(DIR *d) {
>      > + struct dirent *result = NULL;
>      > + if (d != NULL) {
>      > + memset(&d->result, 0, sizeof(d->result));
>      > + if (d->handle != INVALID_HANDLE_VALUE) {
>      > + result = &d->result;
>      > + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
>     result->d_name,
>      > + sizeof(result->d_name), NULL, NULL);
>      > + if (!FindNextFileW(d->handle, &d->info)) {
>      > + FindClose(d->handle);
>      > + d->handle = INVALID_HANDLE_VALUE;
>      > + }
>      > + } else {
>      > + SetLastError(ERROR_FILE_NOT_FOUND);
>      > + }
>      > + } else {
>      > + SetLastError(ERROR_BAD_ARGUMENTS);
>      > + }
>      > + return result;
>      > +}
>      > +#endif
>      > +
>      > +static void p_list(const char *dir, void (*fn)(const char *,
>     void *),
>      > + void *userdata) {
>      > +#if MG_ENABLE_DIRLIST
>      > + struct dirent *dp;
>      > + DIR *dirp;
>      > + if ((dirp = (opendir(dir))) == NULL) return;
>      > + while ((dp = readdir(dirp)) != NULL) {
>      > + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
>     continue;
>      > + fn(dp->d_name, userdata);
>      > + }
>      > + closedir(dirp);
>      > +#else
>      > + (void) dir, (void) fn, (void) userdata;
>      > +#endif
>      > +}
>      > +
>      > +static void *p_open(const char *path, int flags) {
>      > +#if MG_ARCH == MG_ARCH_WIN32
>      > + const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
>      > + wchar_t b1[MG_PATH_MAX], b2[10];
>      > + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
>     sizeof(b1[0]));
>      > + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
>     sizeof(b2[0]));
>      > + return (void *) _wfopen(b1, b2);
>      > +#else
>      > + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e
>     for CLOEXEC
>      > + return (void *) fopen(path, mode);
>      > +#endif
>      > +}
>      > +
>      > +static void p_close(void *fp) {
>      > + fclose((FILE *) fp);
>      > +}
>      > +
>      > +static size_t p_read(void *fp, void *buf, size_t len) {
>      > + return fread(buf, 1, len, (FILE *) fp);
>      > +}
>      > +
>      > +static size_t p_write(void *fp, const void *buf, size_t len) {
>      > + return fwrite(buf, 1, len, (FILE *) fp);
>      > +}
>      > +
>      > +static size_t p_seek(void *fp, size_t offset) {
>      > +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
>      > + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
>      > + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
>      > + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
>      > +#else
>      > + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
>      > +#endif
>      > + return (size_t) ftell((FILE *) fp);
>      > +}
>      > +
>      > +static bool p_rename(const char *from, const char *to) {
>      > + return rename(from, to) == 0;
>      > +}
>      > +
>      > +static bool p_remove(const char *path) {
>      > + return remove(path) == 0;
>      > +}
>      > +
>      > +static bool p_mkdir(const char *path) {
>      > + return mkdir(path, 0775) == 0;
>      > +}
>      > +
>      > +#else
>      > +
>      > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
>      > + (void) path, (void) size, (void) mtime;
>      > + return 0;
>      > +}
>      > +static void p_list(const char *path, void (*fn)(const char *,
>     void *),
>      > + void *userdata) {
>      > + (void) path, (void) fn, (void) userdata;
>      > +}
>      > +static void *p_open(const char *path, int flags) {
>      > + (void) path, (void) flags;
>      > + return NULL;
>      > +}
>      > +static void p_close(void *fp) {
>      > + (void) fp;
>      > +}
>      > +static size_t p_read(void *fd, void *buf, size_t len) {
>      > + (void) fd, (void) buf, (void) len;
>      > + return 0;
>      > +}
>      > +static size_t p_write(void *fd, const void *buf, size_t len) {
>      > + (void) fd, (void) buf, (void) len;
>      > + return 0;
>      > +}
>      > +static size_t p_seek(void *fd, size_t offset) {
>      > + (void) fd, (void) offset;
>      > + return (size_t) ~0;
>      > +}
>      > +static bool p_rename(const char *from, const char *to) {
>      > + (void) from, (void) to;
>      > + return false;
>      > +}
>      > +static bool p_remove(const char *path) {
>      > + (void) path;
>      > + return false;
>      > +}
>      > +static bool p_mkdir(const char *path) {
>      > + (void) path;
>      > + return false;
>      > +}
>      > +#endif
>      > +
>      > +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
>     p_read,
>      > + p_write, p_seek, p_rename, p_remove, p_mkdir};
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/http.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +static int mg_ncasecmp(const char *s1, const char *s2, size_t
>     len) {
>      > + int diff = 0;
>      > + if (len > 0) do {
>      > + int c = *s1++, d = *s2++;
>      > + if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
>      > + if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
>      > + diff = c - d;
>      > + } while (diff == 0 && s1[-1] != '\0' && --len > 0);
>      > + return diff;
>      > +}
>      > +
>      > +bool mg_to_size_t(struct mg_str str, size_t *val);
>      > +bool mg_to_size_t(struct mg_str str, size_t *val) {
>      > + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0,
>     ndigits = 0;
>      > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
>     '\t')) i++;
>      > + if (i < str.len && str.buf[i] == '-') return false;
>      > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
>      > + size_t digit = (size_t) (str.buf[i] - '0');
>      > + if (result > max2) return false; // Overflow
>      > + result *= 10;
>      > + if (result > max - digit) return false; // Overflow
>      > + result += digit;
>      > + i++, ndigits++;
>      > + }
>      > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
>     '\t')) i++;
>      > + if (ndigits == 0) return false; // #2322: Content-Length = 1 *
>     DIGIT
>      > + if (i != str.len) return false; // Ditto
>      > + *val = (size_t) result;
>      > + return true;
>      > +}
>      > +
>      > +// Chunk deletion marker is the MSB in the "processed" counter
>      > +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
>      > +
>      > +// Multipart POST example:
>      > +// --xyz
>      > +// Content-Disposition: form-data; name="val"
>      > +//
>      > +// abcdef
>      > +// --xyz
>      > +// Content-Disposition: form-data; name="foo"; filename="a.txt"
>      > +// Content-Type: text/plain
>      > +//
>      > +// hello world
>      > +//
>      > +// --xyz--
>      > +size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
>      > + struct mg_http_part *part) {
>      > + struct mg_str cd = mg_str_n("Content-Disposition", 19);
>      > + const char *s = body.buf;
>      > + size_t b = ofs, h1, h2, b1, b2, max = body.len;
>      > +
>      > + // Init part params
>      > + if (part != NULL) part->name = part->filename = part->body =
>     mg_str_n(0, 0);
>      > +
>      > + // Skip boundary
>      > + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
>      > + if (b <= ofs || b + 2 >= max) return 0;
>      > + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
>     s));
>      > +
>      > + // Skip headers
>      > + h1 = h2 = b + 2;
>      > + for (;;) {
>      > + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
>      > + if (h2 == h1) break;
>      > + if (h2 + 2 >= max) return 0;
>      > + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
>      > + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
>     ':' &&
>      > + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
>      > + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
>     cd.len + 2));
>      > + part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
>      > + part->filename = mg_http_get_header_var(v, mg_str_n("filename",
>     8));
>      > + }
>      > + h1 = h2 = h2 + 2;
>      > + }
>      > + b1 = b2 = h2 + 2;
>      > + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
>     + 1] == '\n' &&
>      > + memcmp(&s[b2 + 2], s, b - ofs) == 0))
>      > + b2++;
>      > +
>      > + if (b2 + 2 >= max) return 0;
>      > + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
>      > + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
>      > + return b2 + 2;
>      > +}
>      > +
>      > +void mg_http_bauth(struct mg_connection *c, const char *user,
>      > + const char *pass) {
>      > + struct mg_str u = mg_str(user), p = mg_str(pass);
>      > + size_t need = c->send.len + 36 + (u.len + p.len) * 2;
>      > + if (c->send.size < need) mg_iobuf_resize(&c->send, need);
>      > + if (c->send.size >= need) {
>      > + size_t i, n = 0;
>      > + char *buf = (char *) &c->send.buf[c->send.len];
>      > + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
>      > + for (i = 0; i < u.len; i++) {
>      > + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
>      > + }
>      > + if (p.len > 0) {
>      > + n = mg_base64_update(':', buf + 21, n);
>      > + for (i = 0; i < p.len; i++) {
>      > + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
>      > + }
>      > + }
>      > + n = mg_base64_final(buf + 21, n);
>      > + c->send.len += 21 + (size_t) n + 2;
>      > + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
>      > + } else {
>      > + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
>     need));
>      > + }
>      > +}
>      > +
>      > +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
>      > + struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
>      > + while (mg_span(buf, &entry, &buf, '&')) {
>      > + if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
>      > + mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
>      > + result = v;
>      > + break;
>      > + }
>      > + }
>      > + return result;
>      > +}
>      > +
>      > +int mg_http_get_var(const struct mg_str *buf, const char *name,
>     char *dst,
>      > + size_t dst_len) {
>      > + int len;
>      > + if (dst != NULL && dst_len > 0) {
>      > + dst[0] = '\0'; // If destination buffer is valid, always
>     nul-terminate it
>      > + }
>      > + if (dst == NULL || dst_len == 0) {
>      > + len = -2; // Bad destination
>      > + } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
>      > + len = -1; // Bad source
>      > + } else {
>      > + struct mg_str v = mg_http_var(*buf, mg_str(name));
>      > + if (v.buf == NULL) {
>      > + len = -4; // Name does not exist
>      > + } else {
>      > + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
>      > + if (len < 0) len = -3; // Failed to decode
>      > + }
>      > + }
>      > + return len;
>      > +}
>      > +
>      > +static bool isx(int c) {
>      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>      > + (c >= 'A' && c <= 'F');
>      > +}
>      > +
>      > +int mg_url_decode(const char *src, size_t src_len, char *dst,
>     size_t dst_len,
>      > + int is_form_url_encoded) {
>      > + size_t i, j;
>      > + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
>      > + if (src[i] == '%') {
>      > + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
>     src_len
>      > + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
>      > + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j],
>     sizeof(uint8_t));
>      > + i += 2;
>      > + } else {
>      > + return -1;
>      > + }
>      > + } else if (is_form_url_encoded && src[i] == '+') {
>      > + dst[j] = ' ';
>      > + } else {
>      > + dst[j] = src[i];
>      > + }
>      > + }
>      > + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
>      > + return i >= src_len && j < dst_len ? (int) j : -1;
>      > +}
>      > +
>      > +static bool isok(uint8_t c) {
>      > + return c == '\n' || c == '\r' || c >= ' ';
>      > +}
>      > +
>      > +int mg_http_get_request_len(const unsigned char *buf, size_t
>     buf_len) {
>      > + size_t i;
>      > + for (i = 0; i < buf_len; i++) {
>      > + if (!isok(buf[i])) return -1;
>      > + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
>      > + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
>     '\n'))
>      > + return (int) i + 1;
>      > + }
>      > + return 0;
>      > +}
>      > +struct mg_str *mg_http_get_header(struct mg_http_message *h,
>     const char *name) {
>      > + size_t i, n = strlen(name), max = sizeof(h->headers) /
>     sizeof(h->headers[0]);
>      > + for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
>      > + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
>      > + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
>      > + }
>      > + return NULL;
>      > +}
>      > +
>      > +// Is it a valid utf-8 continuation byte
>      > +static bool vcb(uint8_t c) {
>      > + return (c & 0xc0) == 0x80;
>      > +}
>      > +
>      > +// Get character length (valid utf-8). Used to parse method,
>     URI, headers
>      > +static size_t clen(const char *s, const char *end) {
>      > + const unsigned char *u = (unsigned char *) s, c = *u;
>      > + long n = (long) (end - s);
>      > + if (c > ' ' && c < '~') return 1; // Usual ascii printed char
>      > + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; //
>     2-byte UTF8
>      > + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2]))
>     return 3;
>      > + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) &&
>     vcb(u[3]))
>      > + return 4;
>      > + return 0;
>      > +}
>      > +
>      > +// Skip until the newline. Return advanced `s`, or NULL on error
>      > +static const char *skiptorn(const char *s, const char *end,
>     struct mg_str *v) {
>      > + v->buf = (char *) s;
>      > + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;
>     // To newline
>      > + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; //
>     Stray \r
>      > + if (s < end && s[0] == '\r') s++; // Skip \r
>      > + if (s >= end || *s++ != '\n') return NULL; // Skip \n
>      > + return s;
>      > +}
>      > +
>      > +static bool mg_http_parse_headers(const char *s, const char *end,
>      > + struct mg_http_header *h, size_t max_hdrs) {
>      > + size_t i, n;
>      > + for (i = 0; i < max_hdrs; i++) {
>      > + struct mg_str k = {NULL, 0}, v = {NULL, 0};
>      > + if (s >= end) return false;
>      > + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
>      > + k.buf = (char *) s;
>      > + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s +=
>     n, k.len += n;
>      > + if (k.len == 0) return false; // Empty name
>      > + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8
>      > + if (*s++ != ':') return false; // Invalid, not followed by :
>      > + // if (clen(s, end) == 0) return false; // Invalid UTF-8
>      > + while (s < end && s[0] == ' ') s++; // Skip spaces
>      > + if ((s = skiptorn(s, end, &v)) == NULL) return false;
>      > + while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim
>     spaces
>      > + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int)
>     v.len, v.buf));
>      > + h[i].name = k, h[i].value = v; // Success. Assign values
>      > + }
>      > + return true;
>      > +}
>      > +
>      > +int mg_http_parse(const char *s, size_t len, struct
>     mg_http_message *hm) {
>      > + int is_response, req_len = mg_http_get_request_len((unsigned
>     char *) s, len);
>      > + const char *end = s == NULL ? NULL : s + req_len, *qs; //
>     Cannot add to NULL
>      > + const struct mg_str *cl;
>      > + size_t n;
>      > +
>      > + memset(hm, 0, sizeof(*hm));
>      > + if (req_len <= 0) return req_len;
>      > +
>      > + hm->message.buf = hm->head.buf = (char *) s;
>      > + hm->body.buf = (char *) end;
>      > + hm->head.len = (size_t) req_len;
>      > + hm->message.len = hm->body.len = (size_t) -1; // Set body
>     length to infinite
>      > +
>      > + // Parse request line
>      > + hm->method.buf = (char *) s;
>      > + while (s < end && (n = clen(s, end)) > 0) s += n,
>     hm->method.len += n;
>      > + while (s < end && s[0] == ' ') s++; // Skip spaces
>      > + hm->uri.buf = (char *) s;
>      > + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len
>     += n;
>      > + while (s < end && s[0] == ' ') s++; // Skip spaces
>      > + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
>      > +
>      > + // If URI contains '?' character, setup query string
>      > + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len))
>     != NULL) {
>      > + hm->query.buf = (char *) qs + 1;
>      > + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
>      > + hm->uri.len = (size_t) (qs - hm->uri.buf);
>      > + }
>      > +
>      > + // Sanity check. Allow protocol/reason to be empty
>      > + // Do this check after hm->method.len and hm->uri.len are
>     finalised
>      > + if (hm->method.len == 0 || hm->uri.len == 0) return -1;
>      > +
>      > + if (!mg_http_parse_headers(s, end, hm->headers,
>      > + sizeof(hm->headers) / sizeof(hm->headers[0])))
>      > + return -1; // error when parsing
>      > + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
>      > + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
>      > + hm->message.len = (size_t) req_len + hm->body.len;
>      > + }
>      > +
>      > + // mg_http_parse() is used to parse both HTTP requests and HTTP
>      > + // responses. If HTTP response does not have Content-Length
>     set, then
>      > + // body is read until socket is closed, i.e. body.len is
>     infinite (~0).
>      > + //
>      > + // For HTTP requests though, according to
>      > + // http://tools.ietf.org/html/rfc7231#section-8.1.3
>     <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
>      > + // only POST and PUT methods have defined body semantics.
>      > + // Therefore, if Content-Length is not specified and methods are
>      > + // not one of PUT or POST, set body length to 0.
>      > + //
>      > + // So, if it is HTTP request, and Content-Length is not set,
>      > + // and method is not (PUT or POST) then reset body length to zero.
>      > + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
>      > + if (hm->body.len == (size_t) ~0 && !is_response &&
>      > + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
>      > + mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
>      > + hm->body.len = 0;
>      > + hm->message.len = (size_t) req_len;
>      > + }
>      > +
>      > + // The 204 (No content) responses also have 0 body length
>      > + if (hm->body.len == (size_t) ~0 && is_response &&
>      > + mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
>      > + hm->body.len = 0;
>      > + hm->message.len = (size_t) req_len;
>      > + }
>      > + if (hm->message.len < (size_t) req_len) return -1; // Overflow
>     protection
>      > +
>      > + return req_len;
>      > +}
>      > +
>      > +static void mg_http_vprintf_chunk(struct mg_connection *c, const
>     char *fmt,
>      > + va_list *ap) {
>      > + size_t len = c->send.len;
>      > + mg_send(c, " \r\n", 10);
>      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>      > + if (c->send.len >= len + 10) {
>      > + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
>     - len - 10);
>      > + c->send.buf[len + 8] = '\r';
>      > + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
>     reset marker
>      > + }
>      > + mg_send(c, "\r\n", 2);
>      > +}
>      > +
>      > +void mg_http_printf_chunk(struct mg_connection *c, const char
>     *fmt, ...) {
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + mg_http_vprintf_chunk(c, fmt, &ap);
>      > + va_end(ap);
>      > +}
>      > +
>      > +void mg_http_write_chunk(struct mg_connection *c, const char
>     *buf, size_t len) {
>      > + mg_printf(c, "%lx\r\n", (unsigned long) len);
>      > + mg_send(c, buf, len);
>      > + mg_send(c, "\r\n", 2);
>      > + if (len == 0) c->is_resp = 0;
>      > +}
>      > +
>      > +// clang-format off
>      > +static const char *mg_http_status_code_str(int status_code) {
>      > + switch (status_code) {
>      > + case 100: return "Continue";
>      > + case 101: return "Switching Protocols";
>      > + case 102: return "Processing";
>      > + case 200: return "OK";
>      > + case 201: return "Created";
>      > + case 202: return "Accepted";
>      > + case 203: return "Non-authoritative Information";
>      > + case 204: return "No Content";
>      > + case 205: return "Reset Content";
>      > + case 206: return "Partial Content";
>      > + case 207: return "Multi-Status";
>      > + case 208: return "Already Reported";
>      > + case 226: return "IM Used";
>      > + case 300: return "Multiple Choices";
>      > + case 301: return "Moved Permanently";
>      > + case 302: return "Found";
>      > + case 303: return "See Other";
>      > + case 304: return "Not Modified";
>      > + case 305: return "Use Proxy";
>      > + case 307: return "Temporary Redirect";
>      > + case 308: return "Permanent Redirect";
>      > + case 400: return "Bad Request";
>      > + case 401: return "Unauthorized";
>      > + case 402: return "Payment Required";
>      > + case 403: return "Forbidden";
>      > + case 404: return "Not Found";
>      > + case 405: return "Method Not Allowed";
>      > + case 406: return "Not Acceptable";
>      > + case 407: return "Proxy Authentication Required";
>      > + case 408: return "Request Timeout";
>      > + case 409: return "Conflict";
>      > + case 410: return "Gone";
>      > + case 411: return "Length Required";
>      > + case 412: return "Precondition Failed";
>      > + case 413: return "Payload Too Large";
>      > + case 414: return "Request-URI Too Long";
>      > + case 415: return "Unsupported Media Type";
>      > + case 416: return "Requested Range Not Satisfiable";
>      > + case 417: return "Expectation Failed";
>      > + case 418: return "I'm a teapot";
>      > + case 421: return "Misdirected Request";
>      > + case 422: return "Unprocessable Entity";
>      > + case 423: return "Locked";
>      > + case 424: return "Failed Dependency";
>      > + case 426: return "Upgrade Required";
>      > + case 428: return "Precondition Required";
>      > + case 429: return "Too Many Requests";
>      > + case 431: return "Request Header Fields Too Large";
>      > + case 444: return "Connection Closed Without Response";
>      > + case 451: return "Unavailable For Legal Reasons";
>      > + case 499: return "Client Closed Request";
>      > + case 500: return "Internal Server Error";
>      > + case 501: return "Not Implemented";
>      > + case 502: return "Bad Gateway";
>      > + case 503: return "Service Unavailable";
>      > + case 504: return "Gateway Timeout";
>      > + case 505: return "HTTP Version Not Supported";
>      > + case 506: return "Variant Also Negotiates";
>      > + case 507: return "Insufficient Storage";
>      > + case 508: return "Loop Detected";
>      > + case 510: return "Not Extended";
>      > + case 511: return "Network Authentication Required";
>      > + case 599: return "Network Connect Timeout Error";
>      > + default: return "";
>      > + }
>      > +}
>      > +// clang-format on
>      > +
>      > +void mg_http_reply(struct mg_connection *c, int code, const char
>     *headers,
>      > + const char *fmt, ...) {
>      > + va_list ap;
>      > + size_t len;
>      > + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
>      > + mg_http_status_code_str(code), headers == NULL ? "" : headers);
>      > + len = c->send.len;
>      > + va_start(ap, fmt);
>      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
>      > + va_end(ap);
>      > + if (c->send.len > 16) {
>      > + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11,
>     "%-10lu",
>      > + (unsigned long) (c->send.len - len));
>      > + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space
>      > + }
>      > + c->is_resp = 0;
>      > +}
>      > +
>      > +static void http_cb(struct mg_connection *, int, void *);
>      > +static void restore_http_cb(struct mg_connection *c) {
>      > + mg_fs_close((struct mg_fd *) c->pfn_data);
>      > + c->pfn_data = NULL;
>      > + c->pfn = http_cb;
>      > + c->is_resp = 0;
>      > +}
>      > +
>      > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>     mtime);
>      > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>     mtime) {
>      > + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
>     (int64_t) size);
>      > + return buf;
>      > +}
>      > +
>      > +static void static_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
>      > + struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
>      > + // Read to send IO buffer directly, avoid extra on-stack buffer
>      > + size_t n, max = MG_IO_SIZE, space;
>      > + size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
>     sizeof(size_t)) /
>      > + sizeof(size_t) * sizeof(size_t)];
>      > + if (c->send.size < max) mg_iobuf_resize(&c->send, max);
>      > + if (c->send.len >= c->send.size) return; // Rate limit
>      > + if ((space = c->send.size - c->send.len) > *cl) space = *cl;
>      > + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
>      > + c->send.len += n;
>      > + *cl -= n;
>      > + if (n == 0) restore_http_cb(c);
>      > + } else if (ev == MG_EV_CLOSE) {
>      > + restore_http_cb(c);
>      > + }
>      > + (void) ev_data;
>      > +}
>      > +
>      > +// Known mime types. Keep it outside guess_content_type()
>     function, since
>      > +// some environments don't like it defined there.
>      > +// clang-format off
>      > +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
>      > +static struct mg_str s_known_types[] = {
>      > + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
>      > + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
>      > + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
>      > + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
>      > + MG_C_STR("gif"), MG_C_STR("image/gif"),
>      > + MG_C_STR("png"), MG_C_STR("image/png"),
>      > + MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
>      > + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
>      > + MG_C_STR("woff"), MG_C_STR("font/woff"),
>      > + MG_C_STR("ttf"), MG_C_STR("font/ttf"),
>      > + MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
>      > + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
>      > + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
>      > + MG_C_STR("csv"), MG_C_STR("text/csv"),
>      > + MG_C_STR("doc"), MG_C_STR("application/msword"),
>      > + MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
>      > + MG_C_STR("gz"), MG_C_STR("application/gzip"),
>      > + MG_C_STR("ico"), MG_C_STR("image/x-icon"),
>      > + MG_C_STR("json"), MG_C_STR("application/json"),
>      > + MG_C_STR("mov"), MG_C_STR("video/quicktime"),
>      > + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
>      > + MG_C_STR("mp4"), MG_C_STR("video/mp4"),
>      > + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
>      > + MG_C_STR("pdf"), MG_C_STR("application/pdf"),
>      > + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
>      > + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
>      > + MG_C_STR("wav"), MG_C_STR("audio/wav"),
>      > + MG_C_STR("webp"), MG_C_STR("image/webp"),
>      > + MG_C_STR("zip"), MG_C_STR("application/zip"),
>      > + MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
>      > + {0, 0},
>      > +};
>      > +// clang-format on
>      > +
>      > +static struct mg_str guess_content_type(struct mg_str path,
>     const char *extra) {
>      > + struct mg_str entry, k, v, s = mg_str(extra);
>      > + size_t i = 0;
>      > +
>      > + // Shrink path to its extension only
>      > + while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
>      > + path.buf += path.len - i;
>      > + path.len = i;
>      > +
>      > + // Process user-provided mime type overrides, if any
>      > + while (mg_span(s, &entry, &s, ',')) {
>      > + if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0)
>     return v;
>      > + }
>      > +
>      > + // Process built-in mime types
>      > + for (i = 0; s_known_types[i].buf != NULL; i += 2) {
>      > + if (mg_strcmp(path, s_known_types[i]) == 0) return
>     s_known_types[i + 1];
>      > + }
>      > +
>      > + return mg_str("text/plain; charset=utf-8");
>      > +}
>      > +
>      > +static int getrange(struct mg_str *s, size_t *a, size_t *b) {
>      > + size_t i, numparsed = 0;
>      > + for (i = 0; i + 6 < s->len; i++) {
>      > + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
>      > + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
>      > + if (mg_span(v, &k, &v, '-')) {
>      > + if (mg_to_size_t(k, a)) numparsed++;
>      > + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
>      > + } else {
>      > + if (mg_to_size_t(v, a)) numparsed++;
>      > + }
>      > + break;
>      > + }
>      > + return (int) numparsed;
>      > +}
>      > +
>      > +void mg_http_serve_file(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + const char *path,
>      > + const struct mg_http_serve_opts *opts) {
>      > + char etag[64], tmp[MG_PATH_MAX];
>      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > + struct mg_fd *fd = NULL;
>      > + size_t size = 0;
>      > + time_t mtime = 0;
>      > + struct mg_str *inm = NULL;
>      > + struct mg_str mime = guess_content_type(mg_str(path),
>     opts->mime_types);
>      > + bool gzip = false;
>      > +
>      > + if (path != NULL) {
>      > + // If a browser sends us "Accept-Encoding: gzip", try to open
>     .gz first
>      > + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
>      > + if (ae != NULL) {
>      > + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
>      > + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
>      > + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
>      > + fd = mg_fs_open(fs, tmp, MG_FS_READ);
>      > + if (fd != NULL) gzip = true, path = tmp;
>      > + }
>      > + free(ae_);
>      > + }
>      > + // No luck opening .gz? Open what we've told to open
>      > + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
>      > + }
>      > +
>      > + // Failed to open, and page404 is configured? Open it, then
>      > + if (fd == NULL && opts->page404 != NULL) {
>      > + fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
>      > + path = opts->page404;
>      > + mime = guess_content_type(mg_str(path), opts->mime_types);
>      > + }
>      > +
>      > + if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
>      > + mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
>      > + mg_fs_close(fd);
>      > + // NOTE: mg_http_etag() call should go first!
>      > + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
>     NULL &&
>      > + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
>      > + mg_strcasecmp(*inm, mg_str(etag)) == 0) {
>      > + mg_fs_close(fd);
>      > + mg_http_reply(c, 304, opts->extra_headers, "");
>      > + } else {
>      > + int n, status = 200;
>      > + char range[100];
>      > + size_t r1 = 0, r2 = 0, cl = size;
>      > +
>      > + // Handle Range header
>      > + struct mg_str *rh = mg_http_get_header(hm, "Range");
>      > + range[0] = '\0';
>      > + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
>      > + // If range is specified like "400-", set second limit to
>     content len
>      > + if (n == 1) r2 = cl - 1;
>      > + if (r1 > r2 || r2 >= cl) {
>      > + status = 416;
>      > + cl = 0;
>      > + mg_snprintf(range, sizeof(range), "Content-Range: bytes
>     */%lld\r\n",
>      > + (int64_t) size);
>      > + } else {
>      > + status = 206;
>      > + cl = r2 - r1 + 1;
>      > + mg_snprintf(range, sizeof(range),
>      > + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
>      > + (uint64_t) (r1 + cl - 1), (uint64_t) size);
>      > + fs->sk(fd->fd, r1);
>      > + }
>      > + }
>      > + mg_printf(c,
>      > + "HTTP/1.1 %d %s\r\n"
>      > + "Content-Type: %.*s\r\n"
>      > + "Etag: %s\r\n"
>      > + "Content-Length: %llu\r\n"
>      > + "%s%s%s\r\n",
>      > + status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
>      > + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
>      > + range, opts->extra_headers ? opts->extra_headers : "");
>      > + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
>      > + c->is_draining = 1;
>      > + c->is_resp = 0;
>      > + mg_fs_close(fd);
>      > + } else {
>      > + // Track to-be-sent content length at the end of c->data, aligned
>      > + size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
>     sizeof(size_t)) /
>      > + sizeof(size_t) * sizeof(size_t)];
>      > + c->pfn = static_cb;
>      > + c->pfn_data = fd;
>      > + *clp = cl;
>      > + }
>      > + }
>      > +}
>      > +
>      > +struct printdirentrydata {
>      > + struct mg_connection *c;
>      > + struct mg_http_message *hm;
>      > + const struct mg_http_serve_opts *opts;
>      > + const char *dir;
>      > +};
>      > +
>      > +#if MG_ENABLE_DIRLIST
>      > +static void printdirentry(const char *name, void *userdata) {
>      > + struct printdirentrydata *d = (struct printdirentrydata *)
>     userdata;
>      > + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
>     d->opts->fs;
>      > + size_t size = 0;
>      > + time_t t = 0;
>      > + char path[MG_PATH_MAX], sz[40], mod[40];
>      > + int flags, n = 0;
>      > +
>      > + // MG_DEBUG(("[%s] [%s]", d->dir, name));
>      > + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
>      > + sizeof(path)) {
>      > + MG_ERROR(("%s truncated", name));
>      > + } else if ((flags = fs->st(path, &size, &t)) == 0) {
>      > + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
>      > + } else {
>      > + const char *slash = flags & MG_FS_DIR ? "/" : "";
>      > + if (flags & MG_FS_DIR) {
>      > + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
>      > + } else {
>      > + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
>      > + }
>      > +#if defined(MG_HTTP_DIRLIST_TIME_FMT)
>      > + {
>      > + char time_str[40];
>      > + struct tm *time_info = localtime(&t);
>      > + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>     time_info);
>      > + mg_snprintf(mod, sizeof(mod), "%s", time_str);
>      > + }
>      > +#else
>      > + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
>      > +#endif
>      > + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
>      > + mg_printf(d->c,
>      > + " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
>      > + "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
>      > + n, path, slash, name, slash, (unsigned long) t, mod,
>      > + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
>      > + }
>      > +}
>      > +
>      > +static void listdir(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + const struct mg_http_serve_opts *opts, char *dir) {
>      > + const char *sort_js_code =
>      > + "<script>function srt(tb, sc, so, d) {"
>      > + "var tr = Array.prototype.slice.call(tb.rows, 0),"
>      > + "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
>     b.cells[sc],"
>      > + "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
>      > + "t1 = a.cells[2].getAttribute('name'), "
>      > + "t2 = b.cells[2].getAttribute('name'); "
>      > + "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
>      > + "n1 ? parseInt(n2) - parseInt(n1) : "
>      > + "c1.textContent.trim().localeCompare(c2.textContent.trim()));
>     });";
>      > + const char *sort_js_code2 =
>      > + "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
>      > + "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
>      > + "};"
>      > + "window.onload = function() {"
>      > + "var tb = document.getElementById('tb');"
>      > + "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
>     [0, 2, 1];"
>      > + "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
>      > + "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
>     c, so); "
>      > + "sc = c; ev.preventDefault();}};"
>      > + "srt(tb, sc, so, true);"
>      > + "}"
>      > + "</script>";
>      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > + struct printdirentrydata d = {c, hm, opts, dir};
>      > + char tmp[10], buf[MG_PATH_MAX];
>      > + size_t off, n;
>      > + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf,
>     sizeof(buf), 0);
>      > + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
>     hm->uri;
>      > +
>      > + mg_printf(c,
>      > + "HTTP/1.1 200 OK\r\n"
>      > + "Content-Type: text/html; charset=utf-8\r\n"
>      > + "%s"
>      > + "Content-Length: \r\n\r\n",
>      > + opts->extra_headers == NULL ? "" : opts->extra_headers);
>      > + off = c->send.len; // Start of body
>      > + mg_printf(c,
>      > + "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
>      > + "<style>th,td {text-align: left; padding-right: 1em; "
>      > + "font-family: monospace; }</style></head>"
>      > + "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
>      > + "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
>      > + "<a href=\"#\" rel=\"1\">Modified</a></th>"
>      > + "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
>      > + "<tr><td colspan=\"3\"><hr></td></tr>"
>      > + "</thead>"
>      > + "<tbody id=\"tb\">\n",
>      > + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int)
>     uri.len,
>      > + uri.buf);
>      > + mg_printf(c, "%s",
>      > + " <tr><td><a href=\"..\">..</a></td>"
>      > + "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
>      > +
>      > + fs->ls(dir, printdirentry, &d);
>      > + mg_printf(c,
>      > + "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
>      > + "</table><address>Mongoose v.%s</address></body></html>\n",
>      > + MG_VERSION);
>      > + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
>     (c->send.len - off));
>      > + if (n > sizeof(tmp)) n = 0;
>      > + memcpy(c->send.buf + off - 12, tmp, n); // Set content length
>      > + c->is_resp = 0; // Mark response end
>      > +}
>      > +#endif
>      > +
>      > +// Resolve requested file into `path` and return its fs->st()
>     result
>      > +static int uri_to_path2(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + struct mg_fs *fs, struct mg_str url, struct mg_str dir,
>      > + char *path, size_t path_size) {
>      > + int flags, tmp;
>      > + // Append URI to the root_dir, and sanitize it
>      > + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
>     dir.buf);
>      > + if (n + 2 >= path_size) {
>      > + mg_http_reply(c, 400, "", "Exceeded path size");
>      > + return -1;
>      > + }
>      > + path[path_size - 1] = '\0';
>      > + // Terminate root dir with slash
>      > + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
>      > + if (url.len < hm->uri.len) {
>      > + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len,
>     path + n,
>      > + path_size - n, 0);
>      > + }
>      > + path[path_size - 1] = '\0'; // Double-check
>      > + if (!mg_path_is_sane(mg_str_n(path, path_size))) {
>      > + mg_http_reply(c, 400, "", "Invalid path");
>      > + return -1;
>      > + }
>      > + n = strlen(path);
>      > + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
>     trailing slashes
>      > + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
>      > + : fs->st(path, NULL, NULL);
>      > + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
>     hm->uri.buf, path,
>      > + flags));
>      > + if (flags == 0) {
>      > + // Do nothing - let's caller decide
>      > + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
>      > + hm->uri.buf[hm->uri.len - 1] != '/') {
>      > + mg_printf(c,
>      > + "HTTP/1.1 301 Moved\r\n"
>      > + "Location: %.*s/\r\n"
>      > + "Content-Length: 0\r\n"
>      > "\r\n",
>      > - (int) hm->uri.len, hm->uri.ptr);
>      > + (int) hm->uri.len, hm->uri.buf);
>      > c->is_resp = 0;
>      > flags = -1;
>      > } else if (flags & MG_FS_DIR) {
>      > @@ -2057,3318 +3036,11754 @@ static int uri_to_path2(struct
>     mg_connection *c, struct mg_http_message *hm,
>      > path[n + 1 + strlen(MG_HTTP_INDEX)] =
>      > '\0'; // Remove appended .gz in index file name
>      > } else {
>      > - path[n] = '\0'; // Remove appended index file name
>      > + path[n] = '\0'; // Remove appended index file name
>      > + }
>      > + }
>      > + return flags;
>      > +}
>      > +
>      > +static int uri_to_path(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + const struct mg_http_serve_opts *opts, char *path,
>      > + size_t path_size) {
>      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > + struct mg_str k, v, part, s = mg_str(opts->root_dir), u =
>     {NULL, 0}, p = u;
>      > + while (mg_span(s, &part, &s, ',')) {
>      > + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
>      > + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
>      > + if (hm->uri.len < k.len) continue;
>      > + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
>      > + u = k, p = v;
>      > + }
>      > + return uri_to_path2(c, hm, fs, u, p, path, path_size);
>      > +}
>      > +
>      > +void mg_http_serve_dir(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + const struct mg_http_serve_opts *opts) {
>      > + char path[MG_PATH_MAX];
>      > + const char *sp = opts->ssi_pattern;
>      > + int flags = uri_to_path(c, hm, opts, path, sizeof(path));
>      > + if (flags < 0) {
>      > + // Do nothing: the response has already been sent by uri_to_path()
>      > + } else if (flags & MG_FS_DIR) {
>      > +#if MG_ENABLE_DIRLIST
>      > + listdir(c, hm, opts, path);
>      > +#else
>      > + mg_http_reply(c, 403, "", "Forbidden\n");
>      > +#endif
>      > + } else if (flags && sp != NULL && mg_match(mg_str(path),
>     mg_str(sp), NULL)) {
>      > + mg_http_serve_ssi(c, opts->root_dir, path);
>      > + } else {
>      > + mg_http_serve_file(c, hm, path, opts);
>      > + }
>      > +}
>      > +
>      > +static bool mg_is_url_safe(int c) {
>      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
>      > + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
>     == '~';
>      > +}
>      > +
>      > +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
>     len) {
>      > + size_t i, n = 0;
>      > + for (i = 0; i < sl; i++) {
>      > + int c = *(unsigned char *) &s[i];
>      > + if (n + 4 >= len) return 0;
>      > + if (mg_is_url_safe(c)) {
>      > + buf[n++] = s[i];
>      > + } else {
>      > + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
>      > + n += 3;
>      > + }
>      > + }
>      > + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
>     the destination
>      > + if (len > 0) buf[len - 1] = '\0'; // Always.
>      > + return n;
>      > +}
>      > +
>      > +void mg_http_creds(struct mg_http_message *hm, char *user,
>     size_t userlen,
>      > + char *pass, size_t passlen) {
>      > + struct mg_str *v = mg_http_get_header(hm, "Authorization");
>      > + user[0] = pass[0] = '\0';
>      > + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
>      > + char buf[256];
>      > + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf,
>     sizeof(buf));
>      > + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
>      > + if (p != NULL) {
>      > + mg_snprintf(user, userlen, "%.*s", p - buf, buf);
>      > + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1,
>     p + 1);
>      > + }
>      > + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ",
>     7) == 0) {
>      > + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
>      > + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
>      > + struct mg_str t = mg_http_get_header_var(*v,
>     mg_str_n("access_token", 12));
>      > + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
>     t.buf);
>      > + } else {
>      > + mg_http_get_var(&hm->query, "access_token", pass, passlen);
>      > + }
>      > +}
>      > +
>      > +static struct mg_str stripquotes(struct mg_str s) {
>      > + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
>      > + ? mg_str_n(s.buf + 1, s.len - 2)
>      > + : s;
>      > +}
>      > +
>      > +struct mg_str mg_http_get_header_var(struct mg_str s, struct
>     mg_str v) {
>      > + size_t i;
>      > + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
>      > + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len)
>     == 0) {
>      > + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
>      > + int q = p < x && *p == '"' ? 1 : 0;
>      > + while (p < x &&
>      > + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
>      > + p++;
>      > + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int)
>     v.len,
>      > + // v.buf, (int) (p - b), b));
>      > + return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
>      > + }
>      > + }
>      > + return mg_str_n(NULL, 0);
>      > +}
>      > +
>      > +long mg_http_upload(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > + struct mg_fs *fs, const char *dir, size_t max_size) {
>      > + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
>      > + long res = 0, offset;
>      > + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
>      > + mg_http_get_var(&hm->query, "file", file, sizeof(file));
>      > + offset = strtol(buf, NULL, 0);
>      > + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
>      > + if (hm->body.len == 0) {
>      > + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
>      > + } else if (file[0] == '\0') {
>      > + mg_http_reply(c, 400, "", "file required");
>      > + res = -1;
>      > + } else if (mg_path_is_sane(mg_str(file)) == false) {
>      > + mg_http_reply(c, 400, "", "%s: invalid file", file);
>      > + res = -2;
>      > + } else if (offset < 0) {
>      > + mg_http_reply(c, 400, "", "offset required");
>      > + res = -3;
>      > + } else if ((size_t) offset + hm->body.len > max_size) {
>      > + mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
>      > + (unsigned long) max_size);
>      > + res = -4;
>      > + } else {
>      > + struct mg_fd *fd;
>      > + size_t current_size = 0;
>      > + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
>      > + if (offset == 0) fs->rm(path); // If offset if 0, truncate file
>      > + fs->st(path, &current_size, NULL);
>      > + if (offset > 0 && current_size != (size_t) offset) {
>      > + mg_http_reply(c, 400, "", "%s: offset mismatch", path);
>      > + res = -5;
>      > + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
>      > + mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
>      > + res = -6;
>      > + } else {
>      > + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
>      > + mg_fs_close(fd);
>      > + mg_http_reply(c, 200, "", "%ld", res);
>      > + }
>      > + }
>      > + return res;
>      > +}
>      > +
>      > +int mg_http_status(const struct mg_http_message *hm) {
>      > + return atoi(hm->uri.buf);
>      > +}
>      > +
>      > +static bool is_hex_digit(int c) {
>      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>      > + (c >= 'A' && c <= 'F');
>      > +}
>      > +
>      > +static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
>      > + int i = 0, n = 0;
>      > + if (len < 3) return 0;
>      > + while (i < len && is_hex_digit(buf[i])) i++;
>      > + if (i == 0) return -1; // Error, no length specified
>      > + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is
>     too big
>      > + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return
>     -1; // Error
>      > + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n,
>     sizeof(int)) == false)
>      > + return -1; // Decode chunk length, overflow
>      > + if (n < 0) return -1; // Error. TODO(): some checks now redundant
>      > + if (n > len - i - 4) return 0; // Chunk not yet fully buffered
>      > + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return
>     -1; // Error
>      > + *pl = i + 2, *dl = n;
>      > + return i + 2 + n + 2;
>      > +}
>      > +
>      > +static void http_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
>      > + struct mg_http_message hm;
>      > + size_t ofs = 0; // Parsing offset
>      > + while (c->is_resp == 0 && ofs < c->recv.len) {
>      > + const char *buf = (char *) c->recv.buf + ofs;
>      > + int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
>      > + struct mg_str *te; // Transfer - encoding header
>      > + bool is_chunked = false;
>      > + if (n < 0) {
>      > + // We don't use mg_error() here, to avoid closing pipelined
>     requests
>      > + // prematurely, see #2592
>      > + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
>      > + c->is_draining = 1;
>      > + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
>      > + c->recv.len = 0;
>      > + return;
>      > + }
>      > + if (n == 0) break; // Request is not buffered yet
>      > + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers
>      > + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
>      > + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG
>      > + hm.body.len = hm.message.len - (size_t) (hm.body.buf -
>     hm.message.buf);
>      > + }
>      > + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
>      > + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
>      > + is_chunked = true;
>      > + } else {
>      > + mg_error(c, "Invalid Transfer-Encoding"); // See #2460
>      > + return;
>      > + }
>      > + } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
>      > + // #2593: HTTP packets must contain either Transfer-Encoding or
>      > + // Content-length
>      > + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
>      > + bool require_content_len = false;
>      > + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST"))
>     == 0 ||
>      > + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
>      > + // POST and PUT should include an entity body. Therefore, they
>     should
>      > + // contain a Content-length header. Other requests can also
>     contain a
>      > + // body, but their content has no defined semantics (RFC 7231)
>      > + require_content_len = true;
>      > + } else if (is_response) {
>      > + // HTTP spec 7.2 Entity body: All other responses must include
>     a body
>      > + // or Content-Length header field defined with a value of 0.
>      > + int status = mg_http_status(&hm);
>      > + require_content_len = status >= 200 && status != 204 && status
>     != 304;
>      > + }
>      > + if (require_content_len) {
>      > + mg_http_reply(c, 411, "", "");
>      > + MG_ERROR(("%s", "Content length missing from request"));
>      > + }
>      > + }
>      > +
>      > + if (is_chunked) {
>      > + // For chunked data, strip off prefixes and suffixes from chunks
>      > + // and relocate them right after the headers, then report a
>     message
>      > + char *s = (char *) c->recv.buf + ofs + n;
>      > + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs -
>     (size_t) n);
>      > +
>      > + // Find zero-length chunk (the end of the body)
>      > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o
>     += cl;
>      > + if (cl == 0) break; // No zero-len chunk, buffer more data
>      > + if (cl < 0) {
>      > + mg_error(c, "Invalid chunk");
>      > + break;
>      > + }
>      > +
>      > + // Zero chunk found. Second pass: strip + relocate
>      > + o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
>      > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
>      > + memmove(s + hm.body.len, s + o + pl, (size_t) dl);
>      > + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t)
>     dl;
>      > + if (dl == 0) break;
>      > + }
>      > + ofs += (size_t) (n + o);
>      > + } else { // Normal, non-chunked data
>      > + size_t len = c->recv.len - ofs - (size_t) n;
>      > + if (hm.body.len > len) break; // Buffer more data
>      > + ofs += (size_t) n + hm.body.len;
>      > + }
>      > +
>      > + if (c->is_accepted) c->is_resp = 1; // Start generating response
>      > + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
>      > + }
>      > + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete
>     processed data
>      > + }
>      > + (void) ev_data;
>      > +}
>      > +
>      > +static void mg_hfn(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + if (ev == MG_EV_HTTP_MSG) {
>      > + struct mg_http_message *hm = (struct mg_http_message *) ev_data;
>      > + if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
>      > + mg_http_reply(c, 200, "", "ok\n");
>      > + c->is_draining = 1;
>      > + c->data[0] = 'X';
>      > + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
>      > + int level = (int) mg_json_get_long(hm->body, "$.level",
>     MG_LL_DEBUG);
>      > + mg_log_set(level);
>      > + mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
>      > + } else {
>      > + mg_http_reply(c, 200, "", "hi\n");
>      > + }
>      > + } else if (ev == MG_EV_CLOSE) {
>      > + if (c->data[0] == 'X') *(bool *) c->fn_data = true;
>      > + }
>      > +}
>      > +
>      > +void mg_hello(const char *url) {
>      > + struct mg_mgr mgr;
>      > + bool done = false;
>      > + mg_mgr_init(&mgr);
>      > + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
>      > + while (done == false) mg_mgr_poll(&mgr, 100);
>      > + mg_mgr_free(&mgr);
>      > +}
>      > +
>      > +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
>     char *url,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>      > + if (c != NULL) c->pfn = http_cb;
>      > + return c;
>      > +}
>      > +
>      > +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
>     char *url,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>      > + if (c != NULL) c->pfn = http_cb;
>      > + return c;
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/iobuf.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +static size_t roundup(size_t size, size_t align) {
>      > + return align == 0 ? size : (size + align - 1) / align * align;
>      > +}
>      > +
>      > +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
>      > + int ok = 1;
>      > + new_size = roundup(new_size, io->align);
>      > + if (new_size == 0) {
>      > + mg_bzero(io->buf, io->size);
>      > + free(io->buf);
>      > + io->buf = NULL;
>      > + io->len = io->size = 0;
>      > + } else if (new_size != io->size) {
>      > + // NOTE(lsm): do not use realloc here. Use calloc/free only, to
>     ease the
>      > + // porting to some obscure platforms like FreeRTOS
>      > + void *p = calloc(1, new_size);
>      > + if (p != NULL) {
>      > + size_t len = new_size < io->len ? new_size : io->len;
>      > + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
>      > + mg_bzero(io->buf, io->size);
>      > + free(io->buf);
>      > + io->buf = (unsigned char *) p;
>      > + io->size = new_size;
>      > + } else {
>      > + ok = 0;
>      > + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
>     new_size));
>      > + }
>      > + }
>      > + return ok;
>      > +}
>      > +
>      > +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
>      > + io->buf = NULL;
>      > + io->align = align;
>      > + io->size = io->len = 0;
>      > + return mg_iobuf_resize(io, size);
>      > +}
>      > +
>      > +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
>     *buf,
>      > + size_t len) {
>      > + size_t new_size = roundup(io->len + len, io->align);
>      > + mg_iobuf_resize(io, new_size); // Attempt to resize
>      > + if (new_size != io->size) len = 0; // Resize failure, append
>     nothing
>      > + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
>     io->len - ofs);
>      > + if (buf != NULL) memmove(io->buf + ofs, buf, len);
>      > + if (ofs > io->len) io->len += ofs - io->len;
>      > + io->len += len;
>      > + return len;
>      > +}
>      > +
>      > +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
>      > + if (ofs > io->len) ofs = io->len;
>      > + if (ofs + len > io->len) len = io->len - ofs;
>      > + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
>     io->len - ofs - len);
>      > + if (io->buf) mg_bzero(io->buf + io->len - len, len);
>      > + io->len -= len;
>      > + return len;
>      > +}
>      > +
>      > +void mg_iobuf_free(struct mg_iobuf *io) {
>      > + mg_iobuf_resize(io, 0);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/json.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +static const char *escapeseq(int esc) {
>      > + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
>      > +}
>      > +
>      > +static char json_esc(int c, int esc) {
>      > + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
>      > + for (p = esc1; *p != '\0'; p++) {
>      > + if (*p == c) return esc2[p - esc1];
>      > + }
>      > + return 0;
>      > +}
>      > +
>      > +static int mg_pass_string(const char *s, int len) {
>      > + int i;
>      > + for (i = 0; i < len; i++) {
>      > + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
>      > + i++;
>      > + } else if (s[i] == '\0') {
>      > + return MG_JSON_INVALID;
>      > + } else if (s[i] == '"') {
>      > + return i;
>      > + }
>      > + }
>      > + return MG_JSON_INVALID;
>      > +}
>      > +
>      > +static double mg_atod(const char *p, int len, int *numlen) {
>      > + double d = 0.0;
>      > + int i = 0, sign = 1;
>      > +
>      > + // Sign
>      > + if (i < len && *p == '-') {
>      > + sign = -1, i++;
>      > + } else if (i < len && *p == '+') {
>      > + i++;
>      > + }
>      > +
>      > + // Decimal
>      > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>      > + d *= 10.0;
>      > + d += p[i] - '0';
>      > + }
>      > + d *= sign;
>      > +
>      > + // Fractional
>      > + if (i < len && p[i] == '.') {
>      > + double frac = 0.0, base = 0.1;
>      > + i++;
>      > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>      > + frac += base * (p[i] - '0');
>      > + base /= 10.0;
>      > + }
>      > + d += frac * sign;
>      > + }
>      > +
>      > + // Exponential
>      > + if (i < len && (p[i] == 'e' || p[i] == 'E')) {
>      > + int j, exp = 0, minus = 0;
>      > + i++;
>      > + if (i < len && p[i] == '-') minus = 1, i++;
>      > + if (i < len && p[i] == '+') i++;
>      > + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
>      > + exp = exp * 10 + (p[i++] - '0');
>      > + if (minus) exp = -exp;
>      > + for (j = 0; j < exp; j++) d *= 10.0;
>      > + for (j = 0; j < -exp; j++) d /= 10.0;
>      > + }
>      > +
>      > + if (numlen != NULL) *numlen = i;
>      > + return d;
>      > +}
>      > +
>      > +// Iterate over object or array elements
>      > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
>     *key,
>      > + struct mg_str *val) {
>      > + if (ofs >= obj.len) {
>      > + ofs = 0; // Out of boundaries, stop scanning
>      > + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
>      > + ofs = 0; // Not an array or object, stop
>      > + } else {
>      > + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
>      > + if (ofs == 0) ofs++, sub.buf++, sub.len--;
>      > + if (*obj.buf == '[') { // Iterate over an array
>      > + int n = 0, o = mg_json_get(sub, "$", &n);
>      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>      > + ofs = 0; // Error parsing key, stop scanning
>      > + } else {
>      > + if (key) *key = mg_str_n(NULL, 0);
>      > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
>      > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
>      > + }
>      > + } else { // Iterate over an object
>      > + int n = 0, o = mg_json_get(sub, "$", &n);
>      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>      > + ofs = 0; // Error parsing key, stop scanning
>      > + } else {
>      > + if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
>      > + sub.buf += o + n, sub.len -= (size_t) (o + n);
>      > + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
>      > + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
>      > + n = 0, o = mg_json_get(sub, "$", &n);
>      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>      > + ofs = 0; // Error parsing value, stop scanning
>      > + } else {
>      > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
>      > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
>      > + }
>      > + }
>      > + }
>      > + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
>      > + while (ofs && ofs < obj.len &&
>      > + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
>      > + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
>      > + ofs++;
>      > + }
>      > + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
>      > + if (ofs > obj.len) ofs = 0;
>      > + }
>      > + return ofs;
>      > +}
>      > +
>      > +int mg_json_get(struct mg_str json, const char *path, int
>     *toklen) {
>      > + const char *s = json.buf;
>      > + int len = (int) json.len;
>      > + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
>     S_VALUE;
>      > + unsigned char nesting[MG_JSON_MAX_DEPTH];
>      > + int i = 0; // Current offset in `s`
>      > + int j = 0; // Offset in `s` we're looking for (return value)
>      > + int depth = 0; // Current depth (nesting level)
>      > + int ed = 0; // Expected depth
>      > + int pos = 1; // Current position in `path`
>      > + int ci = -1, ei = -1; // Current and expected index in array
>      > +
>      > + if (toklen) *toklen = 0;
>      > + if (path[0] != '$') return MG_JSON_INVALID;
>      > +
>      > +#define MG_CHECKRET(x) \
>      > + do { \
>      > + if (depth == ed && path[pos] == '\0' && ci == ei) { \
>      > + if (toklen) *toklen = i - j + 1; \
>      > + return j; \
>      > + } \
>      > + } while (0)
>      > +
>      > +// In the ascii table, the distance between `[` and `]` is 2.
>      > +// Ditto for `{` and `}`. Hence +2 in the code below.
>      > +#define MG_EOO(x) \
>      > + do { \
>      > + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
>      > + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
>      > + depth--; \
>      > + MG_CHECKRET(x); \
>      > + } while (0)
>      > +
>      > + for (i = 0; i < len; i++) {
>      > + unsigned char c = ((unsigned char *) s)[i];
>      > + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
>      > + switch (expecting) {
>      > + case S_VALUE:
>      > + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
>     ci, ei);
>      > + if (depth == ed) j = i;
>      > + if (c == '{') {
>      > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>      > + if (depth == ed && path[pos] == '.' && ci == ei) {
>      > + // If we start the object, reset array indices
>      > + ed++, pos++, ci = ei = -1;
>      > + }
>      > + nesting[depth++] = c;
>      > + expecting = S_KEY;
>      > + break;
>      > + } else if (c == '[') {
>      > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>      > + if (depth == ed && path[pos] == '[' && ei == ci) {
>      > + ed++, pos++, ci = 0;
>      > + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
>      > + ei *= 10;
>      > + ei += path[pos] - '0';
>      > + }
>      > + if (path[pos] != 0) pos++;
>      > + }
>      > + nesting[depth++] = c;
>      > + break;
>      > + } else if (c == ']' && depth > 0) { // Empty array
>      > + MG_EOO(']');
>      > + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
>     == 0) {
>      > + i += 3;
>      > + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
>     == 0) {
>      > + i += 3;
>      > + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
>     == 0) {
>      > + i += 4;
>      > + } else if (c == '-' || ((c >= '0' && c <= '9'))) {
>      > + int numlen = 0;
>      > + mg_atod(&s[i], len - i, &numlen);
>      > + i += numlen - 1;
>      > + } else if (c == '"') {
>      > + int n = mg_pass_string(&s[i + 1], len - i - 1);
>      > + if (n < 0) return n;
>      > + i += n + 1;
>      > + } else {
>      > + return MG_JSON_INVALID;
>      > + }
>      > + MG_CHECKRET('V');
>      > + if (depth == ed && ei >= 0) ci++;
>      > + expecting = S_COMMA_OR_EOO;
>      > + break;
>      > +
>      > + case S_KEY:
>      > + if (c == '"') {
>      > + int n = mg_pass_string(&s[i + 1], len - i - 1);
>      > + if (n < 0) return n;
>      > + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
>      > + if (depth < ed) return MG_JSON_NOT_FOUND;
>      > + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
>      > + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos,
>     path, n,
>      > + // &s[i + 1], n, depth, ed, ci, ei);
>      > + // NOTE(cpq): in the check sequence below is important.
>      > + // strncmp() must go first: it fails fast if the remaining length
>      > + // of the path is smaller than `n`.
>      > + if (depth == ed && path[pos - 1] == '.' &&
>      > + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
>      > + (path[pos + n] == '\0' || path[pos + n] == '.' ||
>      > + path[pos + n] == '[')) {
>      > + pos += n;
>      > + }
>      > + i += n + 1;
>      > + expecting = S_COLON;
>      > + } else if (c == '}') { // Empty object
>      > + MG_EOO('}');
>      > + expecting = S_COMMA_OR_EOO;
>      > + if (depth == ed && ei >= 0) ci++;
>      > + } else {
>      > + return MG_JSON_INVALID;
>      > + }
>      > + break;
>      > +
>      > + case S_COLON:
>      > + if (c == ':') {
>      > + expecting = S_VALUE;
>      > + } else {
>      > + return MG_JSON_INVALID;
>      > + }
>      > + break;
>      > +
>      > + case S_COMMA_OR_EOO:
>      > + if (depth <= 0) {
>      > + return MG_JSON_INVALID;
>      > + } else if (c == ',') {
>      > + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
>      > + } else if (c == ']' || c == '}') {
>      > + if (depth == ed && c == '}' && path[pos - 1] == '.')
>      > + return MG_JSON_NOT_FOUND;
>      > + if (depth == ed && c == ']' && path[pos - 1] == ',')
>      > + return MG_JSON_NOT_FOUND;
>      > + MG_EOO('O');
>      > + if (depth == ed && ei >= 0) ci++;
>      > + } else {
>      > + return MG_JSON_INVALID;
>      > + }
>      > + break;
>      > + }
>      > + }
>      > + return MG_JSON_NOT_FOUND;
>      > +}
>      > +
>      > +struct mg_str mg_json_get_tok(struct mg_str json, const char
>     *path) {
>      > + int len = 0, ofs = mg_json_get(json, path, &len);
>      > + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
>      > + (size_t) (len < 0 ? 0 : len));
>      > +}
>      > +
>      > +bool mg_json_get_num(struct mg_str json, const char *path,
>     double *v) {
>      > + int n, toklen, found = 0;
>      > + if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
>      > + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <=
>     '9'))) {
>      > + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
>      > + found = 1;
>      > + }
>      > + return found;
>      > +}
>      > +
>      > +bool mg_json_get_bool(struct mg_str json, const char *path, bool
>     *v) {
>      > + int found = 0, off = mg_json_get(json, path, NULL);
>      > + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
>      > + if (v != NULL) *v = json.buf[off] == 't';
>      > + found = 1;
>      > + }
>      > + return found;
>      > +}
>      > +
>      > +bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
>      > + size_t i, j;
>      > + for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
>      > + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
>      > + // \uXXXX escape. We process simple one-byte chars \u00xx
>     within ASCII
>      > + // range. More complex chars would require dragging in a UTF8
>     library,
>      > + // which is too much for us
>      > + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
>      > + sizeof(uint8_t)) == false)
>      > + return false;
>      > + i += 5;
>      > + } else if (s.buf[i] == '\\' && i + 1 < s.len) {
>      > + char c = json_esc(s.buf[i + 1], 0);
>      > + if (c == 0) return false;
>      > + to[j] = c;
>      > + i++;
>      > + } else {
>      > + to[j] = s.buf[i];
>      > + }
>      > + }
>      > + if (j >= n) return false;
>      > + if (n > 0) to[j] = '\0';
>      > + return true;
>      > +}
>      > +
>      > +char *mg_json_get_str(struct mg_str json, const char *path) {
>      > + char *result = NULL;
>      > + int len = 0, off = mg_json_get(json, path, &len);
>      > + if (off >= 0 && len > 1 && json.buf[off] == '"') {
>      > + if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
>      > + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len -
>     2)),
>      > + result, (size_t) len)) {
>      > + free(result);
>      > + result = NULL;
>      > + }
>      > + }
>      > + return result;
>      > +}
>      > +
>      > +char *mg_json_get_b64(struct mg_str json, const char *path, int
>     *slen) {
>      > + char *result = NULL;
>      > + int len = 0, off = mg_json_get(json, path, &len);
>      > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
>      > + (result = (char *) calloc(1, (size_t) len)) != NULL) {
>      > + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len -
>     2), result,
>      > + (size_t) len);
>      > + if (slen != NULL) *slen = (int) k;
>      > + }
>      > + return result;
>      > +}
>      > +
>      > +char *mg_json_get_hex(struct mg_str json, const char *path, int
>     *slen) {
>      > + char *result = NULL;
>      > + int len = 0, off = mg_json_get(json, path, &len);
>      > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
>      > + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
>      > + int i;
>      > + for (i = 0; i < len - 2; i += 2) {
>      > + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16,
>     &result[i >> 1],
>      > + sizeof(uint8_t));
>      > + }
>      > + result[len / 2 - 1] = '\0';
>      > + if (slen != NULL) *slen = len / 2 - 1;
>      > + }
>      > + return result;
>      > +}
>      > +
>      > +long mg_json_get_long(struct mg_str json, const char *path, long
>     dflt) {
>      > + double dv;
>      > + long result = dflt;
>      > + if (mg_json_get_num(json, path, &dv)) result = (long) dv;
>      > + return result;
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/log.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +int mg_log_level = MG_LL_INFO;
>      > +static mg_pfn_t s_log_func = mg_pfn_stdout;
>      > +static void *s_log_func_param = NULL;
>      > +
>      > +void mg_log_set_fn(mg_pfn_t fn, void *param) {
>      > + s_log_func = fn;
>      > + s_log_func_param = param;
>      > +}
>      > +
>      > +static void logc(unsigned char c) {
>      > + s_log_func((char) c, s_log_func_param);
>      > +}
>      > +
>      > +static void logs(const char *buf, size_t len) {
>      > + size_t i;
>      > + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
>      > +}
>      > +
>      > +#if MG_ENABLE_CUSTOM_LOG
>      > +// Let user define their own mg_log_prefix() and mg_log()
>      > +#else
>      > +void mg_log_prefix(int level, const char *file, int line, const
>     char *fname) {
>      > + const char *p = strrchr(file, '/');
>      > + char buf[41];
>      > + size_t n;
>      > + if (p == NULL) p = strrchr(file, '\\');
>      > + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s",
>     mg_millis(), level,
>      > + p == NULL ? file : p + 1, line, fname);
>      > + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
>      > + while (n < sizeof(buf)) buf[n++] = ' ';
>      > + logs(buf, n - 1);
>      > +}
>      > +
>      > +void mg_log(const char *fmt, ...) {
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
>      > + va_end(ap);
>      > + logs("\r\n", 2);
>      > +}
>      > +#endif
>      > +
>      > +static unsigned char nibble(unsigned c) {
>      > + return (unsigned char) (c < 10 ? c + '0' : c + 'W');
>      > +}
>      > +
>      > +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
>      > +void mg_hexdump(const void *buf, size_t len) {
>      > + const unsigned char *p = (const unsigned char *) buf;
>      > + unsigned char ascii[16], alen = 0;
>      > + size_t i;
>      > + for (i = 0; i < len; i++) {
>      > + if ((i % 16) == 0) {
>      > + // Print buffered ascii chars
>      > + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
>     alen = 0;
>      > + // Print hex address, then \t
>      > + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
>      > + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
>      > + }
>      > + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
>     nibbles, e.g. c5
>      > + logc(' '); // Space after hex number
>      > + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
>     buf
>      > + }
>      > + while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
>      > + logs(" ", 2), logs((char *) ascii, 16), logc('\n');
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/md5.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +// This code implements the MD5 message-digest algorithm.
>      > +// The algorithm is due to Ron Rivest. This code was
>      > +// written by Colin Plumb in 1993, no copyright is claimed.
>      > +// This code is in the public domain; do with it what you wish.
>      > +//
>      > +// Equivalent code is available from RSA Data Security, Inc.
>      > +// This code has been tested against that, and is equivalent,
>      > +// except that you don't need to include two pages of legalese
>      > +// with every copy.
>      > +//
>      > +// To compute the message digest of a chunk of bytes, declare an
>      > +// MD5Context structure, pass it to MD5Init, call MD5Update as
>      > +// needed on buffers full of bytes, and then call MD5Final, which
>      > +// will fill a supplied 16-byte array with the digest.
>      > +
>      > +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
>      > +
>      > +static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
>      > + if (MG_BIG_ENDIAN) {
>      > + do {
>      > + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
>      > + ((unsigned) buf[1] << 8 | buf[0]);
>      > + *(uint32_t *) buf = t;
>      > + buf += 4;
>      > + } while (--longs);
>      > + } else {
>      > + (void) buf, (void) longs; // Little endian. Do nothing
>      > + }
>      > +}
>      > +
>      > +#define F1(x, y, z) (z ^ (x & (y ^ z)))
>      > +#define F2(x, y, z) F1(z, x, y)
>      > +#define F3(x, y, z) (x ^ y ^ z)
>      > +#define F4(x, y, z) (y ^ (x | ~z))
>      > +
>      > +#define MD5STEP(f, w, x, y, z, data, s) \
>      > + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
>      > +
>      > +/*
>      > + * Start MD5 accumulation. Set bit count to 0 and buffer to
>     mysterious
>      > + * initialization constants.
>      > + */
>      > +void mg_md5_init(mg_md5_ctx *ctx) {
>      > + ctx->buf[0] = 0x67452301;
>      > + ctx->buf[1] = 0xefcdab89;
>      > + ctx->buf[2] = 0x98badcfe;
>      > + ctx->buf[3] = 0x10325476;
>      > +
>      > + ctx->bits[0] = 0;
>      > + ctx->bits[1] = 0;
>      > +}
>      > +
>      > +static void mg_md5_transform(uint32_t buf[4], uint32_t const
>     in[16]) {
>      > + uint32_t a, b, c, d;
>      > +
>      > + a = buf[0];
>      > + b = buf[1];
>      > + c = buf[2];
>      > + d = buf[3];
>      > +
>      > + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
>      > + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
>      > + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
>      > + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
>      > + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
>      > + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
>      > + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
>      > + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
>      > + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
>      > + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
>      > + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
>      > + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
>      > + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
>      > + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
>      > + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
>      > + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
>      > +
>      > + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
>      > + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
>      > + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
>      > + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
>      > + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
>      > + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
>      > + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
>      > + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
>      > + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
>      > + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
>      > + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
>      > + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
>      > + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
>      > + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
>      > + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
>      > + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
>      > +
>      > + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
>      > + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
>      > + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
>      > + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
>      > + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
>      > + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
>      > + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
>      > + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
>      > + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
>      > + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
>      > + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
>      > + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
>      > + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
>      > + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
>      > + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
>      > + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
>      > +
>      > + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
>      > + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
>      > + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
>      > + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
>      > + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
>      > + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
>      > + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
>      > + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
>      > + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
>      > + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
>      > + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
>      > + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
>      > + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
>      > + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
>      > + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
>      > + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
>      > +
>      > + buf[0] += a;
>      > + buf[1] += b;
>      > + buf[2] += c;
>      > + buf[3] += d;
>      > +}
>      > +
>      > +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
>     size_t len) {
>      > + uint32_t t;
>      > +
>      > + t = ctx->bits[0];
>      > + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
>     ctx->bits[1]++;
>      > + ctx->bits[1] += (uint32_t) len >> 29;
>      > +
>      > + t = (t >> 3) & 0x3f;
>      > +
>      > + if (t) {
>      > + unsigned char *p = (unsigned char *) ctx->in + t;
>      > +
>      > + t = 64 - t;
>      > + if (len < t) {
>      > + memcpy(p, buf, len);
>      > + return;
>      > + }
>      > + memcpy(p, buf, t);
>      > + mg_byte_reverse(ctx->in, 16);
>      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > + buf += t;
>      > + len -= t;
>      > + }
>      > +
>      > + while (len >= 64) {
>      > + memcpy(ctx->in, buf, 64);
>      > + mg_byte_reverse(ctx->in, 16);
>      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > + buf += 64;
>      > + len -= 64;
>      > + }
>      > +
>      > + memcpy(ctx->in, buf, len);
>      > +}
>      > +
>      > +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
>      > + unsigned count;
>      > + unsigned char *p;
>      > + uint32_t *a;
>      > +
>      > + count = (ctx->bits[0] >> 3) & 0x3F;
>      > +
>      > + p = ctx->in + count;
>      > + *p++ = 0x80;
>      > + count = 64 - 1 - count;
>      > + if (count < 8) {
>      > + memset(p, 0, count);
>      > + mg_byte_reverse(ctx->in, 16);
>      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > + memset(ctx->in, 0, 56);
>      > + } else {
>      > + memset(p, 0, count - 8);
>      > + }
>      > + mg_byte_reverse(ctx->in, 14);
>      > +
>      > + a = (uint32_t *) ctx->in;
>      > + a[14] = ctx->bits[0];
>      > + a[15] = ctx->bits[1];
>      > +
>      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > + mg_byte_reverse((unsigned char *) ctx->buf, 4);
>      > + memcpy(digest, ctx->buf, 16);
>      > + memset((char *) ctx, 0, sizeof(*ctx));
>      > +}
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/mqtt.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +#define MQTT_CLEAN_SESSION 0x02
>      > +#define MQTT_HAS_WILL 0x04
>      > +#define MQTT_WILL_RETAIN 0x20
>      > +#define MQTT_HAS_PASSWORD 0x40
>      > +#define MQTT_HAS_USER_NAME 0x80
>      > +
>      > +struct mg_mqtt_pmap {
>      > + uint8_t id;
>      > + uint8_t type;
>      > +};
>      > +
>      > +static const struct mg_mqtt_pmap s_prop_map[] = {
>      > + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
>      > + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
>      > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
>      > + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
>      > + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
>      > + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
>      > + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
>      > + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
>      > + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
>      > + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
>      > + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
>      > + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
>      > + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
>      > + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE,
>     MQTT_PROP_TYPE_BYTE},
>      > + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
>      > +
>      > +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
>     uint8_t flags,
>      > + uint32_t len) {
>      > + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
>      > + buf[0] = (uint8_t) ((cmd << 4) | flags);
>      > + do {
>      > + *vlen = len % 0x80;
>      > + len /= 0x80;
>      > + if (len > 0) *vlen |= 0x80;
>      > + vlen++;
>      > + } while (len > 0 && vlen < &buf[sizeof(buf)]);
>      > + mg_send(c, buf, (size_t) (vlen - buf));
>      > +}
>      > +
>      > +static void mg_send_u16(struct mg_connection *c, uint16_t value) {
>      > + mg_send(c, &value, sizeof(value));
>      > +}
>      > +
>      > +static void mg_send_u32(struct mg_connection *c, uint32_t value) {
>      > + mg_send(c, &value, sizeof(value));
>      > +}
>      > +
>      > +static uint8_t varint_size(size_t length) {
>      > + uint8_t bytes_needed = 0;
>      > + do {
>      > + bytes_needed++;
>      > + length /= 0x80;
>      > + } while (length > 0);
>      > + return bytes_needed;
>      > +}
>      > +
>      > +static size_t encode_varint(uint8_t *buf, size_t value) {
>      > + size_t len = 0;
>      > +
>      > + do {
>      > + uint8_t b = (uint8_t) (value % 128);
>      > + value /= 128;
>      > + if (value > 0) b |= 0x80;
>      > + buf[len++] = b;
>      > + } while (value > 0);
>      > +
>      > + return len;
>      > +}
>      > +
>      > +static size_t decode_varint(const uint8_t *buf, size_t len,
>     size_t *value) {
>      > + size_t multiplier = 1, offset;
>      > + *value = 0;
>      > +
>      > + for (offset = 0; offset < 4 && offset < len; offset++) {
>      > + uint8_t encoded_byte = buf[offset];
>      > + *value += (encoded_byte & 0x7f) * multiplier;
>      > + multiplier *= 128;
>      > +
>      > + if ((encoded_byte & 0x80) == 0) return offset + 1;
>      > + }
>      > +
>      > + return 0;
>      > +}
>      > +
>      > +static int mqtt_prop_type_by_id(uint8_t prop_id) {
>      > + size_t i, num_properties = sizeof(s_prop_map) /
>     sizeof(s_prop_map[0]);
>      > + for (i = 0; i < num_properties; ++i) {
>      > + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
>      > + }
>      > + return -1; // Property ID not found
>      > +}
>      > +
>      > +// Returns the size of the properties section, without the
>      > +// size of the content's length
>      > +static size_t get_properties_length(struct mg_mqtt_prop *props,
>     size_t count) {
>      > + size_t i, size = 0;
>      > + for (i = 0; i < count; i++) {
>      > + size++; // identifier
>      > + switch (mqtt_prop_type_by_id(props[i].id)) {
>      > + case MQTT_PROP_TYPE_STRING_PAIR:
>      > + size += (uint32_t) (props[i].val.len + props[i].key.len +
>      > + 2 * sizeof(uint16_t));
>      > + break;
>      > + case MQTT_PROP_TYPE_STRING:
>      > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
>      > + break;
>      > + case MQTT_PROP_TYPE_BINARY_DATA:
>      > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
>      > + break;
>      > + case MQTT_PROP_TYPE_VARIABLE_INT:
>      > + size += varint_size((uint32_t) props[i].iv);
>      > + break;
>      > + case MQTT_PROP_TYPE_INT:
>      > + size += (uint32_t) sizeof(uint32_t);
>      > + break;
>      > + case MQTT_PROP_TYPE_SHORT:
>      > + size += (uint32_t) sizeof(uint16_t);
>      > + break;
>      > + case MQTT_PROP_TYPE_BYTE:
>      > + size += (uint32_t) sizeof(uint8_t);
>      > + break;
>      > + default:
>      > + return size; // cannot parse further down
>      > + }
>      > + }
>      > +
>      > + return size;
>      > +}
>      > +
>      > +// returns the entire size of the properties section, including the
>      > +// size of the variable length of the content
>      > +static size_t get_props_size(struct mg_mqtt_prop *props, size_t
>     count) {
>      > + size_t size = get_properties_length(props, count);
>      > + size += varint_size(size);
>      > + return size;
>      > +}
>      > +
>      > +static void mg_send_mqtt_properties(struct mg_connection *c,
>      > + struct mg_mqtt_prop *props, size_t nprops) {
>      > + size_t total_size = get_properties_length(props, nprops);
>      > + uint8_t buf_v[4] = {0, 0, 0, 0};
>      > + uint8_t buf[4] = {0, 0, 0, 0};
>      > + size_t i, len = encode_varint(buf, total_size);
>      > +
>      > + mg_send(c, buf, (size_t) len);
>      > + for (i = 0; i < nprops; i++) {
>      > + mg_send(c, &props[i].id, sizeof(props[i].id));
>      > + switch (mqtt_prop_type_by_id(props[i].id)) {
>      > + case MQTT_PROP_TYPE_STRING_PAIR:
>      > + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
>      > + mg_send(c, props[i].key.buf, props[i].key.len);
>      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>      > + mg_send(c, props[i].val.buf, props[i].val.len);
>      > + break;
>      > + case MQTT_PROP_TYPE_BYTE:
>      > + mg_send(c, &props[i].iv, sizeof(uint8_t));
>      > + break;
>      > + case MQTT_PROP_TYPE_SHORT:
>      > + mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
>      > + break;
>      > + case MQTT_PROP_TYPE_INT:
>      > + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
>      > + break;
>      > + case MQTT_PROP_TYPE_STRING:
>      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>      > + mg_send(c, props[i].val.buf, props[i].val.len);
>      > + break;
>      > + case MQTT_PROP_TYPE_BINARY_DATA:
>      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>      > + mg_send(c, props[i].val.buf, props[i].val.len);
>      > + break;
>      > + case MQTT_PROP_TYPE_VARIABLE_INT:
>      > + len = encode_varint(buf_v, props[i].iv);
>      > + mg_send(c, buf_v, (size_t) len);
>      > + break;
>      > + }
>      > + }
>      > +}
>      > +
>      > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct
>     mg_mqtt_prop *prop,
>      > + size_t ofs) {
>      > + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
>      > + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
>      > + size_t new_pos = ofs, len;
>      > + prop->id = i[0];
>      > +
>      > + if (ofs >= msg->dgram.len || ofs >= msg->props_start +
>     msg->props_size)
>      > + return 0;
>      > + i++, new_pos++;
>      > +
>      > + switch (mqtt_prop_type_by_id(prop->id)) {
>      > + case MQTT_PROP_TYPE_STRING_PAIR:
>      > + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>      > + prop->key.buf = (char *) i + 2;
>      > + i += 2 + prop->key.len;
>      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>      > + prop->val.buf = (char *) i + 2;
>      > + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
>      > + break;
>      > + case MQTT_PROP_TYPE_BYTE:
>      > + prop->iv = (uint8_t) i[0];
>      > + new_pos++;
>      > + break;
>      > + case MQTT_PROP_TYPE_SHORT:
>      > + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>      > + new_pos += sizeof(uint16_t);
>      > + break;
>      > + case MQTT_PROP_TYPE_INT:
>      > + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
>      > + ((uint32_t) i[2] << 8) | i[3];
>      > + new_pos += sizeof(uint32_t);
>      > + break;
>      > + case MQTT_PROP_TYPE_STRING:
>      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>      > + prop->val.buf = (char *) i + 2;
>      > + new_pos += 2 + prop->val.len;
>      > + break;
>      > + case MQTT_PROP_TYPE_BINARY_DATA:
>      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>      > + prop->val.buf = (char *) i + 2;
>      > + new_pos += 2 + prop->val.len;
>      > + break;
>      > + case MQTT_PROP_TYPE_VARIABLE_INT:
>      > + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
>      > + new_pos = (!len) ? 0 : new_pos + len;
>      > + break;
>      > + default:
>      > + new_pos = 0;
>      > + }
>      > +
>      > + return new_pos;
>      > +}
>      > +
>      > +void mg_mqtt_login(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts) {
>      > + char client_id[21];
>      > + struct mg_str cid = opts->client_id;
>      > + size_t total_len = 7 + 1 + 2 + 2;
>      > + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
>      > +
>      > + if (cid.len == 0) {
>      > + mg_random_str(client_id, sizeof(client_id) - 1);
>      > + client_id[sizeof(client_id) - 1] = '\0';
>      > + cid = mg_str(client_id);
>      > + }
>      > +
>      > + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
>     (3.1.1)
>      > + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
>      > + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags
>      > + if (opts->user.len > 0) {
>      > + total_len += 2 + (uint32_t) opts->user.len;
>      > + hdr[7] |= MQTT_HAS_USER_NAME;
>      > + }
>      > + if (opts->pass.len > 0) {
>      > + total_len += 2 + (uint32_t) opts->pass.len;
>      > + hdr[7] |= MQTT_HAS_PASSWORD;
>      > + }
>      > + if (opts->topic.len > 0) { // allow zero-length msgs,
>     message.len is size_t
>      > + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t)
>     opts->message.len;
>      > + hdr[7] |= MQTT_HAS_WILL;
>      > + }
>      > + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
>      > + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
>      > + total_len += (uint32_t) cid.len;
>      > + if (c->is_mqtt5) {
>      > + total_len += get_props_size(opts->props, opts->num_props);
>      > + if (hdr[7] & MQTT_HAS_WILL)
>      > + total_len += get_props_size(opts->will_props,
>     opts->num_will_props);
>      > + }
>      > +
>      > + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
>      > + mg_send(c, hdr, sizeof(hdr));
>      > + // keepalive == 0 means "do not disconnect us!"
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
>      > +
>      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>     opts->num_props);
>      > +
>      > + mg_send_u16(c, mg_htons((uint16_t) cid.len));
>      > + mg_send(c, cid.buf, cid.len);
>      > +
>      > + if (hdr[7] & MQTT_HAS_WILL) {
>      > + if (c->is_mqtt5)
>      > + mg_send_mqtt_properties(c, opts->will_props,
>     opts->num_will_props);
>      > +
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>      > + mg_send(c, opts->topic.buf, opts->topic.len);
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
>      > + mg_send(c, opts->message.buf, opts->message.len);
>      > + }
>      > + if (opts->user.len > 0) {
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
>      > + mg_send(c, opts->user.buf, opts->user.len);
>      > + }
>      > + if (opts->pass.len > 0) {
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
>      > + mg_send(c, opts->pass.buf, opts->pass.len);
>      > + }
>      > +}
>      > +
>      > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts) {
>      > + uint16_t id = opts->retransmit_id;
>      > + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) |
>     (opts->retain ? 1 : 0));
>      > + size_t len = 2 + opts->topic.len + opts->message.len;
>      > + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
>      > + (char *) opts->topic.buf, (int) opts->message.len,
>      > + (char *) opts->message.buf));
>      > + if (opts->qos > 0) len += 2;
>      > + if (c->is_mqtt5) len += get_props_size(opts->props,
>     opts->num_props);
>      > +
>      > + if (opts->qos > 0 && id != 0) flags |= 1 << 3;
>      > + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>      > + mg_send(c, opts->topic.buf, opts->topic.len);
>      > + if (opts->qos > 0) { // need to send 'id' field
>      > + if (id == 0) { // generate new one if not resending
>      > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>      > + id = c->mgr->mqtt_id;
>      > + }
>      > + mg_send_u16(c, mg_htons(id));
>      > + }
>      > +
>      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>     opts->num_props);
>      > +
>      > + if (opts->message.len > 0) mg_send(c, opts->message.buf,
>     opts->message.len);
>      > + return id;
>      > +}
>      > +
>      > +void mg_mqtt_sub(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts) {
>      > + uint8_t qos_ = opts->qos & 3;
>      > + size_t plen = c->is_mqtt5 ? get_props_size(opts->props,
>     opts->num_props) : 0;
>      > + size_t len = 2 + opts->topic.len + 2 + 1 + plen;
>      > +
>      > + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
>      > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>      > + mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>     opts->num_props);
>      > +
>      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>      > + mg_send(c, opts->topic.buf, opts->topic.len);
>      > + mg_send(c, &qos_, sizeof(qos_));
>      > +}
>      > +
>      > +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
>      > + struct mg_mqtt_message *m) {
>      > + uint8_t lc = 0, *p, *end;
>      > + uint32_t n = 0, len_len = 0;
>      > +
>      > + memset(m, 0, sizeof(*m));
>      > + m->dgram.buf = (char *) buf;
>      > + if (len < 2) return MQTT_INCOMPLETE;
>      > + m->cmd = (uint8_t) (buf[0] >> 4);
>      > + m->qos = (buf[0] >> 1) & 3;
>      > +
>      > + n = len_len = 0;
>      > + p = (uint8_t *) buf + 1;
>      > + while ((size_t) (p - buf) < len) {
>      > + lc = *((uint8_t *) p++);
>      > + n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
>      > + len_len++;
>      > + if (!(lc & 0x80)) break;
>      > + if (len_len >= 4) return MQTT_MALFORMED;
>      > + }
>      > + end = p + n;
>      > + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
>      > + m->dgram.len = (size_t) (end - buf);
>      > +
>      > + switch (m->cmd) {
>      > + case MQTT_CMD_CONNACK:
>      > + if (end - p < 2) return MQTT_MALFORMED;
>      > + m->ack = p[1];
>      > + break;
>      > + case MQTT_CMD_PUBACK:
>      > + case MQTT_CMD_PUBREC:
>      > + case MQTT_CMD_PUBREL:
>      > + case MQTT_CMD_PUBCOMP:
>      > + case MQTT_CMD_SUBSCRIBE:
>      > + case MQTT_CMD_SUBACK:
>      > + case MQTT_CMD_UNSUBSCRIBE:
>      > + case MQTT_CMD_UNSUBACK:
>      > + if (p + 2 > end) return MQTT_MALFORMED;
>      > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > + p += 2;
>      > + break;
>      > + case MQTT_CMD_PUBLISH: {
>      > + if (p + 2 > end) return MQTT_MALFORMED;
>      > + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > + m->topic.buf = (char *) p + 2;
>      > + p += 2 + m->topic.len;
>      > + if (p > end) return MQTT_MALFORMED;
>      > + if (m->qos > 0) {
>      > + if (p + 2 > end) return MQTT_MALFORMED;
>      > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > + p += 2;
>      > + }
>      > + if (p > end) return MQTT_MALFORMED;
>      > + if (version == 5 && p + 2 < end) {
>      > + len_len =
>      > + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
>      > + if (!len_len) return MQTT_MALFORMED;
>      > + m->props_start = (size_t) (p + len_len - buf);
>      > + p += len_len + m->props_size;
>      > + }
>      > + if (p > end) return MQTT_MALFORMED;
>      > + m->data.buf = (char *) p;
>      > + m->data.len = (size_t) (end - p);
>      > + break;
>      > + }
>      > + default:
>      > + break;
>      > + }
>      > + return MQTT_OK;
>      > +}
>      > +
>      > +static void mqtt_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + if (ev == MG_EV_READ) {
>      > + for (;;) {
>      > + uint8_t version = c->is_mqtt5 ? 5 : 4;
>      > + struct mg_mqtt_message mm;
>      > + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
>      > + if (rc == MQTT_MALFORMED) {
>      > + MG_ERROR(("%lu MQTT malformed message", c->id));
>      > + c->is_closing = 1;
>      > + break;
>      > + } else if (rc == MQTT_OK) {
>      > + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
>      > + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
>      > + switch (mm.cmd) {
>      > + case MQTT_CMD_CONNACK:
>      > + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
>      > + if (mm.ack == 0) {
>      > + MG_DEBUG(("%lu Connected", c->id));
>      > + } else {
>      > + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
>      > + c->is_closing = 1;
>      > + }
>      > + break;
>      > + case MQTT_CMD_PUBLISH: {
>      > + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
>      > + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
>      > + if (mm.qos > 0) {
>      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>      > + uint32_t remaining_len = sizeof(id);
>      > + if (c->is_mqtt5) remaining_len += 2; // 3.4.2
>      > +
>      > + mg_mqtt_send_header(
>      > + c,
>      > + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
>      > + 0, remaining_len);
>      > + mg_send(c, &id, sizeof(id));
>      > +
>      > + if (c->is_mqtt5) {
>      > + uint16_t zero = 0;
>      > + mg_send(c, &zero, sizeof(zero));
>      > + }
>      > + }
>      > + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff
>      > + break;
>      > + }
>      > + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable
>     header rc
>      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>      > + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1
>      > + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
>      > + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2
>      > + break;
>      > + }
>      > + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable
>     header rc
>      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>      > + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1
>      > + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
>      > + mg_send(c, &id, sizeof(id));
>      > + break;
>      > + }
>      > + }
>      > + mg_call(c, MG_EV_MQTT_CMD, &mm);
>      > + mg_iobuf_del(&c->recv, 0, mm.dgram.len);
>      > + } else {
>      > + break;
>      > + }
>      > + }
>      > + }
>      > + (void) ev_data;
>      > +}
>      > +
>      > +void mg_mqtt_ping(struct mg_connection *nc) {
>      > + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
>      > +}
>      > +
>      > +void mg_mqtt_pong(struct mg_connection *nc) {
>      > + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
>      > +}
>      > +
>      > +void mg_mqtt_disconnect(struct mg_connection *c,
>      > + const struct mg_mqtt_opts *opts) {
>      > + size_t len = 0;
>      > + if (c->is_mqtt5) len = 1 + get_props_size(opts->props,
>     opts->num_props);
>      > + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
>      > +
>      > + if (c->is_mqtt5) {
>      > + uint8_t zero = 0;
>      > + mg_send(c, &zero, sizeof(zero)); // reason code
>      > + mg_send_mqtt_properties(c, opts->props, opts->num_props);
>      > + }
>      > +}
>      > +
>      > +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
>     char *url,
>      > + const struct mg_mqtt_opts *opts,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>      > + if (c != NULL) {
>      > + struct mg_mqtt_opts empty;
>      > + memset(&empty, 0, sizeof(empty));
>      > + mg_mqtt_login(c, opts == NULL ? &empty : opts);
>      > + c->pfn = mqtt_cb;
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>     char *url,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>      > + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
>      > + return c;
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/net.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +size_t mg_vprintf(struct mg_connection *c, const char *fmt,
>     va_list *ap) {
>      > + size_t old = c->send.len;
>      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>      > + return c->send.len - old;
>      > +}
>      > +
>      > +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
>      > + size_t len = 0;
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + len = mg_vprintf(c, fmt, &ap);
>      > + va_end(ap);
>      > + return len;
>      > +}
>      > +
>      > +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
>      > + uint32_t localhost = mg_htonl(0x7f000001);
>      > + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
>      > + memcpy(addr->ip, &localhost, sizeof(uint32_t));
>      > + addr->is_ip6 = false;
>      > + return true;
>      > +}
>      > +
>      > +static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
>      > + if (str.len > 0) return false;
>      > + memset(addr->ip, 0, sizeof(addr->ip));
>      > + addr->is_ip6 = false;
>      > + return true;
>      > +}
>      > +
>      > +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
>      > + uint8_t data[4] = {0, 0, 0, 0};
>      > + size_t i, num_dots = 0;
>      > + for (i = 0; i < str.len; i++) {
>      > + if (str.buf[i] >= '0' && str.buf[i] <= '9') {
>      > + int octet = data[num_dots] * 10 + (str.buf[i] - '0');
>      > + if (octet > 255) return false;
>      > + data[num_dots] = (uint8_t) octet;
>      > + } else if (str.buf[i] == '.') {
>      > + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return
>     false;
>      > + num_dots++;
>      > + } else {
>      > + return false;
>      > + }
>      > + }
>      > + if (num_dots != 3 || str.buf[i - 1] == '.') return false;
>      > + memcpy(&addr->ip, data, sizeof(data));
>      > + addr->is_ip6 = false;
>      > + return true;
>      > +}
>      > +
>      > +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
>      > + int i;
>      > + uint32_t ipv4;
>      > + if (str.len < 14) return false;
>      > + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] !=
>     ':') return false;
>      > + for (i = 2; i < 6; i++) {
>      > + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
>      > + }
>      > + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
>      > + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return
>     false;
>      > + memcpy(&ipv4, addr->ip, sizeof(ipv4));
>      > + memset(addr->ip, 0, sizeof(addr->ip));
>      > + addr->ip[10] = addr->ip[11] = 255;
>      > + memcpy(&addr->ip[12], &ipv4, 4);
>      > + addr->is_ip6 = true;
>      > + return true;
>      > +}
>      > +
>      > +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
>      > + size_t i, j = 0, n = 0, dc = 42;
>      > + addr->scope_id = 0;
>      > + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
>      > + if (mg_v4mapped(str, addr)) return true;
>      > + for (i = 0; i < str.len; i++) {
>      > + if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
>      > + (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
>      > + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
>      > + unsigned long val; // TODO(): This loops on chars, refactor
>      > + if (i > j + 3) return false;
>      > + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1),
>     &str.buf[j]));
>      > + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val,
>     sizeof(val));
>      > + addr->ip[n] = (uint8_t) ((val >> 8) & 255);
>      > + addr->ip[n + 1] = (uint8_t) (val & 255);
>      > + } else if (str.buf[i] == ':') {
>      > + j = i + 1;
>      > + if (i > 0 && str.buf[i - 1] == ':') {
>      > + dc = n; // Double colon
>      > + if (i > 1 && str.buf[i - 2] == ':') return false;
>      > + } else if (i > 0) {
>      > + n += 2;
>      > + }
>      > + if (n > 14) return false;
>      > + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing ::
>      > + } else if (str.buf[i] == '%') { // Scope ID, last in string
>      > + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i -
>     1), 10,
>      > + &addr->scope_id, sizeof(uint8_t));
>      > + } else {
>      > + return false;
>      > + }
>      > + }
>      > + if (n < 14 && dc == 42) return false;
>      > + if (n < 14) {
>      > + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
>      > + memset(&addr->ip[dc], 0, 14 - n);
>      > + }
>      > +
>      > + addr->is_ip6 = true;
>      > + return true;
>      > +}
>      > +
>      > +bool mg_aton(struct mg_str str, struct mg_addr *addr) {
>      > + // MG_INFO(("[%.*s]", (int) str.len, str.buf));
>      > + return mg_atone(str, addr) || mg_atonl(str, addr) ||
>     mg_aton4(str, addr) ||
>      > + mg_aton6(str, addr);
>      > +}
>      > +
>      > +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
>      > + struct mg_connection *c =
>      > + (struct mg_connection *) calloc(1, sizeof(*c) +
>     mgr->extraconnsize);
>      > + if (c != NULL) {
>      > + c->mgr = mgr;
>      > + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
>      > + c->id = ++mgr->nextid;
>      > + MG_PROF_INIT(c);
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +void mg_close_conn(struct mg_connection *c) {
>      > + mg_resolve_cancel(c); // Close any pending DNS query
>      > + LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
>      > + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
>      > + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
>      > + // Order of operations is important. `MG_EV_CLOSE` event must
>     be fired
>      > + // before we deallocate received data, see #1331
>      > + mg_call(c, MG_EV_CLOSE, NULL);
>      > + MG_DEBUG(("%lu %ld closed", c->id, c->fd));
>      > + MG_PROF_DUMP(c);
>      > + MG_PROF_FREE(c);
>      > +
>      > + mg_tls_free(c);
>      > + mg_iobuf_free(&c->recv);
>      > + mg_iobuf_free(&c->send);
>      > + mg_iobuf_free(&c->rtls);
>      > + mg_bzero((unsigned char *) c, sizeof(*c));
>      > + free(c);
>      > +}
>      > +
>      > +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
>     *url,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = NULL;
>      > + if (url == NULL || url[0] == '\0') {
>      > + MG_ERROR(("null url"));
>      > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > + MG_ERROR(("OOM"));
>      > + } else {
>      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > + c->is_udp = (strncmp(url, "udp:", 4) == 0);
>      > + c->fd = (void *) (size_t) MG_INVALID_SOCKET;
>      > + c->fn = fn;
>      > + c->is_client = true;
>      > + c->fn_data = fn_data;
>      > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
>      > + mg_call(c, MG_EV_OPEN, (void *) url);
>      > + mg_resolve(c, url);
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
>     *url,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = NULL;
>      > + if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > + MG_ERROR(("OOM %s", url));
>      > + } else if (!mg_open_listener(c, url)) {
>      > + MG_ERROR(("Failed: %s, errno %d", url, errno));
>      > + MG_PROF_FREE(c);
>      > + free(c);
>      > + c = NULL;
>      > + } else {
>      > + c->is_listening = 1;
>      > + c->is_udp = strncmp(url, "udp:", 4) == 0;
>      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > + c->fn = fn;
>      > + c->fn_data = fn_data;
>      > + mg_call(c, MG_EV_OPEN, NULL);
>      > + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must
>      > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
>      > + mg_event_handler_t fn, void *fn_data) {
>      > + struct mg_connection *c = mg_alloc_conn(mgr);
>      > + if (c != NULL) {
>      > + c->fd = (void *) (size_t) fd;
>      > + c->fn = fn;
>      > + c->fn_data = fn_data;
>      > + MG_EPOLL_ADD(c);
>      > + mg_call(c, MG_EV_OPEN, NULL);
>      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>     milliseconds,
>      > + unsigned flags, void (*fn)(void *), void *arg) {
>      > + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
>      > + if (t != NULL) {
>      > + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
>      > + t->id = mgr->timerid++;
>      > + }
>      > + return t;
>      > +}
>      > +
>      > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>      > + if (c->rtls.len == 0) return MG_IO_WAIT;
>      > + if (len > c->rtls.len) len = c->rtls.len;
>      > + memcpy(buf, c->rtls.buf, len);
>      > + mg_iobuf_del(&c->rtls, 0, len);
>      > + return (long) len;
>      > +}
>      > +
>      > +void mg_mgr_free(struct mg_mgr *mgr) {
>      > + struct mg_connection *c;
>      > + struct mg_timer *tmp, *t = mgr->timers;
>      > + while (t != NULL) tmp = t->next, free(t), t = tmp;
>      > + mgr->timers = NULL; // Important. Next call to poll won't touch
>     timers
>      > + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
>      > + mg_mgr_poll(mgr, 0);
>      > +#if MG_ENABLE_FREERTOS_TCP
>      > + FreeRTOS_DeleteSocketSet(mgr->ss);
>      > +#endif
>      > + MG_DEBUG(("All connections closed"));
>      > +#if MG_ENABLE_EPOLL
>      > + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
>      > +#endif
>      > + mg_tls_ctx_free(mgr);
>      > +}
>      > +
>      > +void mg_mgr_init(struct mg_mgr *mgr) {
>      > + memset(mgr, 0, sizeof(*mgr));
>      > +#if MG_ENABLE_EPOLL
>      > + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
>      > + MG_ERROR(("epoll_create1 errno %d", errno));
>      > +#else
>      > + mgr->epoll_fd = -1;
>      > +#endif
>      > +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > + // clang-format off
>      > + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
>      > + // clang-format on
>      > +#elif MG_ENABLE_FREERTOS_TCP
>      > + mgr->ss = FreeRTOS_CreateSocketSet();
>      > +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
>      > + // Ignore SIGPIPE signal, so if client cancels the request, it
>      > + // won't kill the whole process.
>      > + signal(SIGPIPE, SIG_IGN);
>      > +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
>      > + MG_TCPIP_DRIVER_INIT(mgr);
>      > +#endif
>      > + mgr->pipe = MG_INVALID_SOCKET;
>      > + mgr->dnstimeout = 3000;
>      > + mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
>      > + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
>      > + mg_tls_ctx_init(mgr);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/net_builtin.c"
>      > +#endif
>      > +
>      > +
>      > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
>      > +#define MG_EPHEMERAL_PORT_BASE 32768
>      > +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
>      > +
>      > +#ifndef MIP_TCP_KEEPALIVE_MS
>      > +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
>      > +#endif
>      > +
>      > +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
>      > +#define MIP_TCP_ARP_MS 100 // Timeout for ARP response
>      > +#define MIP_TCP_SYN_MS 15000 // Timeout for connection
>     establishment
>      > +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection
>      > +#define MIP_TCP_WIN 6000 // TCP window size
>      > +
>      > +struct connstate {
>      > + uint32_t seq, ack; // TCP seq/ack counters
>      > + uint64_t timer; // TCP keep-alive / ACK timer
>      > + uint32_t acked; // Last ACK-ed number
>      > + size_t unacked; // Not acked bytes
>      > + uint8_t mac[6]; // Peer MAC address
>      > + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
>      > +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
>     send keepalive
>      > +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
>     soon
>      > +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response
>      > +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
>      > +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating
>     the connection
>      > + uint8_t tmiss; // Number of keep-alive misses
>      > + struct mg_iobuf raw; // For TLS only. Incoming raw data
>      > +};
>      > +
>      > +#pragma pack(push, 1)
>      > +
>      > +struct lcp {
>      > + uint8_t addr, ctrl, proto[2], code, id, len[2];
>      > +};
>      > +
>      > +struct eth {
>      > + uint8_t dst[6]; // Destination MAC address
>      > + uint8_t src[6]; // Source MAC address
>      > + uint16_t type; // Ethernet type
>      > +};
>      > +
>      > +struct ip {
>      > + uint8_t ver; // Version
>      > + uint8_t tos; // Unused
>      > + uint16_t len; // Length
>      > + uint16_t id; // Unused
>      > + uint16_t frag; // Fragmentation
>      > +#define IP_FRAG_OFFSET_MSK 0xFF1F
>      > +#define IP_MORE_FRAGS_MSK 0x20
>      > + uint8_t ttl; // Time to live
>      > + uint8_t proto; // Upper level protocol
>      > + uint16_t csum; // Checksum
>      > + uint32_t src; // Source IP
>      > + uint32_t dst; // Destination IP
>      > +};
>      > +
>      > +struct ip6 {
>      > + uint8_t ver; // Version
>      > + uint8_t opts[3]; // Options
>      > + uint16_t len; // Length
>      > + uint8_t proto; // Upper level protocol
>      > + uint8_t ttl; // Time to live
>      > + uint8_t src[16]; // Source IP
>      > + uint8_t dst[16]; // Destination IP
>      > +};
>      > +
>      > +struct icmp {
>      > + uint8_t type;
>      > + uint8_t code;
>      > + uint16_t csum;
>      > +};
>      > +
>      > +struct arp {
>      > + uint16_t fmt; // Format of hardware address
>      > + uint16_t pro; // Format of protocol address
>      > + uint8_t hlen; // Length of hardware address
>      > + uint8_t plen; // Length of protocol address
>      > + uint16_t op; // Operation
>      > + uint8_t sha[6]; // Sender hardware address
>      > + uint32_t spa; // Sender protocol address
>      > + uint8_t tha[6]; // Target hardware address
>      > + uint32_t tpa; // Target protocol address
>      > +};
>      > +
>      > +struct tcp {
>      > + uint16_t sport; // Source port
>      > + uint16_t dport; // Destination port
>      > + uint32_t seq; // Sequence number
>      > + uint32_t ack; // Acknowledgement number
>      > + uint8_t off; // Data offset
>      > + uint8_t flags; // TCP flags
>      > +#define TH_FIN 0x01
>      > +#define TH_SYN 0x02
>      > +#define TH_RST 0x04
>      > +#define TH_PUSH 0x08
>      > +#define TH_ACK 0x10
>      > +#define TH_URG 0x20
>      > +#define TH_ECE 0x40
>      > +#define TH_CWR 0x80
>      > + uint16_t win; // Window
>      > + uint16_t csum; // Checksum
>      > + uint16_t urp; // Urgent pointer
>      > +};
>      > +
>      > +struct udp {
>      > + uint16_t sport; // Source port
>      > + uint16_t dport; // Destination port
>      > + uint16_t len; // UDP length
>      > + uint16_t csum; // UDP checksum
>      > +};
>      > +
>      > +struct dhcp {
>      > + uint8_t op, htype, hlen, hops;
>      > + uint32_t xid;
>      > + uint16_t secs, flags;
>      > + uint32_t ciaddr, yiaddr, siaddr, giaddr;
>      > + uint8_t hwaddr[208];
>      > + uint32_t magic;
>      > + uint8_t options[32];
>      > +};
>      > +
>      > +#pragma pack(pop)
>      > +
>      > +struct pkt {
>      > + struct mg_str raw; // Raw packet data
>      > + struct mg_str pay; // Payload data
>      > + struct eth *eth;
>      > + struct llc *llc;
>      > + struct arp *arp;
>      > + struct ip *ip;
>      > + struct ip6 *ip6;
>      > + struct icmp *icmp;
>      > + struct tcp *tcp;
>      > + struct udp *udp;
>      > + struct dhcp *dhcp;
>      > +};
>      > +
>      > +static void send_syn(struct mg_connection *c);
>      > +
>      > +static void mkpay(struct pkt *pkt, void *p) {
>      > + pkt->pay =
>      > + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] -
>     (char *) p));
>      > +}
>      > +
>      > +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
>      > + size_t i;
>      > + const uint8_t *p = (const uint8_t *) buf;
>      > + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t)
>     (p[i] << 8);
>      > + return sum;
>      > +}
>      > +
>      > +static uint16_t csumfin(uint32_t sum) {
>      > + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
>      > + return mg_htons(~sum & 0xffff);
>      > +}
>      > +
>      > +static uint16_t ipcsum(const void *buf, size_t len) {
>      > + uint32_t sum = csumup(0, buf, len);
>      > + return csumfin(sum);
>      > +}
>      > +
>      > +static void settmout(struct mg_connection *c, uint8_t type) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS
>      > + : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
>      > + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
>      > + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
>      > + : MIP_TCP_KEEPALIVE_MS;
>      > + s->timer = ifp->now + n;
>      > + s->ttype = type;
>      > + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
>      > +}
>      > +
>      > +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
>      > + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
>      > + if (n == len) ifp->nsent++;
>      > + return n;
>      > +}
>      > +
>      > +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
>      > + struct eth *eth = (struct eth *) ifp->tx.buf;
>      > + struct arp *arp = (struct arp *) (eth + 1);
>      > + memset(eth->dst, 255, sizeof(eth->dst));
>      > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
>      > + eth->type = mg_htons(0x806);
>      > + memset(arp, 0, sizeof(*arp));
>      > + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
>      > + arp->plen = 4;
>      > + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
>      > + memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
>      > + ether_output(ifp, PDIFF(eth, arp + 1));
>      > +}
>      > +
>      > +static void onstatechange(struct mg_tcpip_if *ifp) {
>      > + if (ifp->state == MG_TCPIP_STATE_READY) {
>      > + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
>      > + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
>      > + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
>      > + arp_ask(ifp, ifp->gw);
>      > + } else if (ifp->state == MG_TCPIP_STATE_UP) {
>      > + MG_ERROR(("Link up"));
>      > + srand((unsigned int) mg_millis());
>      > + } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
>      > + MG_ERROR(("Link down"));
>      > + }
>      > +}
>      > +
>      > +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>      > + uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
>      > + size_t plen) {
>      > + struct eth *eth = (struct eth *) ifp->tx.buf;
>      > + struct ip *ip = (struct ip *) (eth + 1);
>      > + memcpy(eth->dst, mac_dst, sizeof(eth->dst));
>      > + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC
>      > + eth->type = mg_htons(0x800);
>      > + memset(ip, 0, sizeof(*ip));
>      > + ip->ver = 0x45; // Version 4, header length 5 words
>      > + ip->frag = 0x40; // Don't fragment
>      > + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
>      > + ip->ttl = 64;
>      > + ip->proto = proto;
>      > + ip->src = ip_src;
>      > + ip->dst = ip_dst;
>      > + ip->csum = ipcsum(ip, sizeof(*ip));
>      > + return ip;
>      > +}
>      > +
>      > +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>     uint32_t ip_src,
>      > + uint16_t sport, uint32_t ip_dst, uint16_t dport,
>      > + const void *buf, size_t len) {
>      > + struct ip *ip =
>      > + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
>      > + struct udp *udp = (struct udp *) (ip + 1);
>      > + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
>      > + udp->sport = sport;
>      > + udp->dport = dport;
>      > + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
>      > + udp->csum = 0;
>      > + uint32_t cs = csumup(0, udp, sizeof(*udp));
>      > + cs = csumup(cs, buf, len);
>      > + cs = csumup(cs, &ip->src, sizeof(ip->src));
>      > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>      > + cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
>      > + udp->csum = csumfin(cs);
>      > + memmove(udp + 1, buf, len);
>      > + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
>      > + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
>     sizeof(*udp) + len);
>      > +}
>      > +
>      > +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>     uint32_t ip_src,
>      > + uint32_t ip_dst, uint8_t *opts, size_t optslen,
>      > + bool ciaddr) {
>      > + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
>     <https://datatracker.ietf.org/doc/html/rfc2132#section-9.6>
>      > + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>      > + dhcp.magic = mg_htonl(0x63825363);
>      > + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
>      > + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
>      > + memcpy(&dhcp.options, opts, optslen);
>      > + if (ciaddr) dhcp.ciaddr = ip_src;
>      > + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst,
>     mg_htons(67), &dhcp,
>      > + sizeof(dhcp));
>      > +}
>      > +
>      > +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
>      > +
>      > +// RFC-2131 #4.3.6, #4.4.1
>      > +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp,
>     uint32_t ip_req,
>      > + uint32_t ip_srv) {
>      > + uint8_t opts[] = {
>      > + 53, 1, 3, // Type: DHCP request
>      > + 55, 2, 1, 3, // GW and mask
>      > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
>      > + 54, 4, 0, 0, 0, 0, // DHCP server ID
>      > + 50, 4, 0, 0, 0, 0, // Requested IP
>      > + 255 // End of options
>      > + };
>      > + memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
>      > + memcpy(opts + 20, &ip_req, sizeof(ip_req));
>      > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
>     sizeof(opts), false);
>      > + MG_DEBUG(("DHCP req sent"));
>      > +}
>      > +
>      > +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
>      > +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t
>     *mac_dst,
>      > + uint32_t ip_src, uint32_t ip_dst) {
>      > + uint8_t opts[] = {
>      > + 53, 1, 3, // Type: DHCP request
>      > + 255 // End of options
>      > + };
>      > + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
>      > + MG_DEBUG(("DHCP req sent"));
>      > +}
>      > +
>      > +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
>      > + uint8_t opts[] = {
>      > + 53, 1, 1, // Type: DHCP discover
>      > + 55, 2, 1, 3, // Parameters: ip, mask
>      > + 255 // End of options
>      > + };
>      > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
>     sizeof(opts), false);
>      > + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac,
>     ifp->mac));
>      > +}
>      > +
>      > +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
>     pkt *pkt,
>      > + bool lsn) {
>      > + struct mg_connection *c = NULL;
>      > + for (c = mgr->conns; c != NULL; c = c->next) {
>      > + if (c->is_arplooking && pkt->arp &&
>      > + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
>      > + break;
>      > + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
>     break;
>      > + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
>      > + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
>      > + break;
>      > + }
>      > + return c;
>      > +}
>      > +
>      > +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
>      > + // ARP request. Make a response, then send
>      > + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op),
>     mg_print_ip4,
>      > + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
>      > + struct eth *eth = (struct eth *) ifp->tx.buf;
>      > + struct arp *arp = (struct arp *) (eth + 1);
>      > + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
>      > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
>      > + eth->type = mg_htons(0x806);
>      > + *arp = *pkt->arp;
>      > + arp->op = mg_htons(2);
>      > + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
>      > + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
>      > + arp->tpa = pkt->arp->spa;
>      > + arp->spa = ifp->ip;
>      > + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa,
>     mg_print_mac,
>      > + &ifp->mac));
>      > + ether_output(ifp, PDIFF(eth, arp + 1));
>      > + } else if (pkt->arp->op == mg_htons(2)) {
>      > + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
>     0) return;
>      > + if (pkt->arp->spa == ifp->gw) {
>      > + // Got response for the GW ARP request. Set ifp->gwmac
>      > + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
>      > + } else {
>      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>      > + if (c != NULL && c->is_arplooking) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
>      > + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4,
>     c->rem.ip,
>      > + mg_print_mac, s->mac));
>      > + c->is_arplooking = 0;
>      > + send_syn(c);
>      > + settmout(c, MIP_TTYPE_SYN);
>      > + }
>      > + }
>      > + }
>      > +}
>      > +
>      > +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + // MG_DEBUG(("ICMP %d", (int) len));
>      > + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
>     ifp->ip) {
>      > + size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
>     sizeof(struct icmp);
>      > + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
>      > + if (plen > space) plen = space;
>      > + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip,
>     pkt->ip->src,
>      > + sizeof(struct icmp) + plen);
>      > + struct icmp *icmp = (struct icmp *) (ip + 1);
>      > + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
>      > + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX
>      > + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
>      > + ether_output(ifp, hlen + plen);
>      > + }
>      > +}
>      > +
>      > +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt
>     *pkt) {
>      > + uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
>      > + uint8_t msgtype = 0, state = ifp->state;
>      > + // perform size check first, then access fields
>      > + uint8_t *p = pkt->dhcp->options,
>      > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
>      > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>      > + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2,
>     sizeof(pkt->dhcp->xid))) return;
>      > + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9
>      > + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
>     Mask
>      > + memcpy(&mask, p + 2, sizeof(mask));
>      > + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
>     { // GW
>      > + memcpy(&gw, p + 2, sizeof(gw));
>      > + ip = pkt->dhcp->yiaddr;
>      > + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
>      > + memcpy(&lease, p + 2, sizeof(lease));
>      > + lease = mg_ntohl(lease);
>      > + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type
>      > + msgtype = p[2];
>      > + }
>      > + p += p[1] + 2;
>      > + }
>      > + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
>      > + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP
>      > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
>      > + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP &&
>     ip && gw &&
>      > + lease) { // DHCPOFFER
>      > + // select IP, (4.4.1) (fallback to IP source addr on foul play)
>      > + tx_dhcp_request_sel(ifp, ip,
>      > + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
>      > + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
>      > + } else if (msgtype == 5) { // DHCPACK
>      > + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { //
>     got an IP
>      > + ifp->lease_expire = ifp->now + lease * 1000;
>      > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
>     1000));
>      > + // assume DHCP server = router until ARP resolves
>      > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
>      > + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
>      > + ifp->state = MG_TCPIP_STATE_READY; // BOUND state
>      > + uint64_t rand;
>      > + mg_random(&rand, sizeof(rand));
>      > + srand((unsigned int) (rand + mg_millis()));
>      > + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip)
>     { // renew
>      > + ifp->lease_expire = ifp->now + lease * 1000;
>      > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
>     1000));
>      > + } // TODO(): accept provided T1/T2 and store server IP for
>     renewal (4.4)
>      > + }
>      > + if (ifp->state != state) onstatechange(ifp);
>      > +}
>      > +
>      > +// Simple DHCP server that assigns a next IP address: ifp->ip + 1
>      > +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt
>     *pkt) {
>      > + uint8_t op = 0, *p = pkt->dhcp->options,
>      > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
>      > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>      > + // struct dhcp *req = pkt->dhcp;
>      > + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>      > + res.yiaddr = ifp->ip;
>      > + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
>      > + while (p + 1 < end && p[0] != 255) { // Parse options
>      > + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
>      > + op = p[2];
>      > + }
>      > + p += p[1] + 2;
>      > + }
>      > + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
>      > + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
>     DHCP ACK
>      > + uint8_t opts[] = {
>      > + 53, 1, msg, // Message type
>      > + 1, 4, 0, 0, 0, 0, // Subnet mask
>      > + 54, 4, 0, 0, 0, 0, // Server ID
>      > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
>      > + 51, 4, 255, 255, 255, 255, // Lease time
>      > + 255 // End of options
>      > + };
>      > + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
>      > + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
>      > + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
>      > + memcpy(&res.options, opts, sizeof(opts));
>      > + res.magic = pkt->dhcp->magic;
>      > + res.xid = pkt->dhcp->xid;
>      > + if (ifp->enable_get_gateway) {
>      > + ifp->gw = res.yiaddr;
>      > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
>      > + }
>      > + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
>      > + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
>      > + }
>      > +}
>      > +
>      > +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
>      > + if (c == NULL) {
>      > + // No UDP listener on this port. Should send ICMP, but keep
>     silent.
>      > + } else {
>      > + c->rem.port = pkt->udp->sport;
>      > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
>      > + if (c->recv.len >= MG_MAX_RECV_SIZE) {
>      > + mg_error(c, "max_recv_buf_size reached");
>      > + } else if (c->recv.size - c->recv.len < pkt->pay.len &&
>      > + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
>      > + mg_error(c, "oom");
>      > + } else {
>      > + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
>      > + c->recv.len += pkt->pay.len;
>      > + mg_call(c, MG_EV_READ, &pkt->pay.len);
>      > + }
>      > + }
>      > +}
>      > +
>      > +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac,
>     uint32_t dst_ip,
>      > + uint8_t flags, uint16_t sport, uint16_t dport,
>      > + uint32_t seq, uint32_t ack, const void *buf, size_t len) {
>      > +#if 0
>      > + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460,
>     SACK permitted
>      > + if (flags & TH_SYN) {
>      > + // Handshake? Set MSS
>      > + buf = opts;
>      > + len = sizeof(opts);
>      > + }
>      > +#endif
>      > + struct ip *ip =
>      > + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
>      > + struct tcp *tcp = (struct tcp *) (ip + 1);
>      > + memset(tcp, 0, sizeof(*tcp));
>      > + if (buf != NULL && len) memmove(tcp + 1, buf, len);
>      > + tcp->sport = sport;
>      > + tcp->dport = dport;
>      > + tcp->seq = seq;
>      > + tcp->ack = ack;
>      > + tcp->flags = flags;
>      > + tcp->win = mg_htons(MIP_TCP_WIN);
>      > + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
>      > + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header
>     size 28 bytes
>      > +
>      > + uint32_t cs = 0;
>      > + uint16_t n = (uint16_t) (sizeof(*tcp) + len);
>      > + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
>     (n & 255)};
>      > + cs = csumup(cs, tcp, n);
>      > + cs = csumup(cs, &ip->src, sizeof(ip->src));
>      > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>      > + cs = csumup(cs, pseudo, sizeof(pseudo));
>      > + tcp->csum = csumfin(cs);
>      > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4,
>     &ip->src,
>      > + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
>      > + mg_ntohs(tcp->dport), tcp->flags, len));
>      > + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
>      > + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
>      > +}
>      > +
>      > +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
>      > + uint8_t flags, uint32_t seq, const void *buf,
>      > + size_t len) {
>      > + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
>      > + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags,
>     pkt->tcp->dport,
>      > + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
>      > + buf, len);
>      > +}
>      > +
>      > +static struct mg_connection *accept_conn(struct mg_connection *lsn,
>      > + struct pkt *pkt) {
>      > + struct mg_connection *c = mg_alloc_conn(lsn->mgr);
>      > + if (c == NULL) {
>      > + MG_ERROR(("OOM"));
>      > + return NULL;
>      > + }
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>     mg_ntohl(pkt->tcp->seq);
>      > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
>      > + settmout(c, MIP_TTYPE_KEEPALIVE);
>      > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
>      > + c->rem.port = pkt->tcp->sport;
>      > + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
>      > + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
>      > + c->is_accepted = 1;
>      > + c->is_hexdumping = lsn->is_hexdumping;
>      > + c->pfn = lsn->pfn;
>      > + c->loc = lsn->loc;
>      > + c->pfn_data = lsn->pfn_data;
>      > + c->fn = lsn->fn;
>      > + c->fn_data = lsn->fn_data;
>      > + mg_call(c, MG_EV_OPEN, NULL);
>      > + mg_call(c, MG_EV_ACCEPT, NULL);
>      > + return c;
>      > +}
>      > +
>      > +static size_t trim_len(struct mg_connection *c, size_t len) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60,
>     udp_h_len = 8;
>      > + size_t max_headers_len =
>      > + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len :
>     tcp_max_h_len);
>      > + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len
>     - eth_h_len;
>      > +
>      > + // If the frame exceeds the available buffer, trim the length
>      > + if (len + max_headers_len > ifp->tx.len) {
>      > + len = ifp->tx.len - max_headers_len;
>      > + }
>      > + // Ensure the MTU isn't lower than the minimum allowed value
>      > + if (ifp->mtu < min_mtu) {
>      > + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
>      > + ifp->mtu = (uint16_t) min_mtu;
>      > + }
>      > + // If the total packet size exceeds the MTU, trim the length
>      > + if (len + max_headers_len - eth_h_len > ifp->mtu) {
>      > + len = ifp->mtu - max_headers_len + eth_h_len;
>      > + if (c->is_udp) {
>      > + MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
>      > + }
>      > + }
>      > +
>      > + return len;
>      > +}
>      > +
>      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + uint32_t dst_ip = *(uint32_t *) c->rem.ip;
>      > + len = trim_len(c, len);
>      > + if (c->is_udp) {
>      > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port,
>     buf, len);
>      > + } else {
>      > + size_t sent =
>      > + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port,
>     c->rem.port,
>      > + mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
>      > + if (sent == 0) {
>      > + return MG_IO_WAIT;
>      > + } else if (sent == (size_t) -1) {
>      > + return MG_IO_ERR;
>      > + } else {
>      > + s->seq += (uint32_t) len;
>      > + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
>      > + }
>      > + }
>      > + return (long) len;
>      > +}
>      > +
>      > +static void handle_tls_recv(struct mg_connection *c, struct
>     mg_iobuf *io) {
>      > + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
>      > + if (n == MG_IO_ERR) {
>      > + mg_error(c, "TLS recv error");
>      > + } else if (n > 0) {
>      > + // Decrypted successfully - trigger MG_EV_READ
>      > + io->len += (size_t) n;
>      > + mg_call(c, MG_EV_READ, &n);
>      > + }
>      > +}
>      > +
>      > +static void read_conn(struct mg_connection *c, struct pkt *pkt) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
>      > + uint32_t seq = mg_ntohl(pkt->tcp->seq);
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + if (pkt->tcp->flags & TH_FIN) {
>      > + // If we initiated the closure, we reply with ACK upon
>     receiving FIN
>      > + // If we didn't initiate it, we reply with FIN as part of the
>     normal TCP
>      > + // closure process
>      > + uint8_t flags = TH_ACK;
>      > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
>      > + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
>      > + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
>      > + s->seq++; // Yes. Increment our SEQ
>      > + } else { // Otherwise,
>      > + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
>      > + }
>      > + } else {
>      > + flags |= TH_FIN;
>      > + c->is_draining = 1;
>      > + settmout(c, MIP_TTYPE_FIN);
>      > + }
>      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
>      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
>     "", 0);
>      > + } else if (pkt->pay.len == 0) {
>      > + // TODO(cpq): handle this peer's ACK
>      > + } else if (seq != s->ack) {
>      > + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) +
>     pkt->pay.len);
>      > + if (s->ack == ack) {
>      > + MG_VERBOSE(("ignoring duplicate pkt"));
>      > + } else {
>      > + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
>      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
>     TH_ACK,
>      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
>      > + 0);
>      > + }
>      > + } else if (io->size - io->len < pkt->pay.len &&
>      > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>      > + mg_error(c, "oom");
>      > + } else {
>      > + // Copy TCP payload into the IO buffer. If the connection is
>     plain text,
>      > + // we copy to c->recv. If the connection is TLS, this data is
>     encrypted,
>      > + // therefore we copy that encrypted data to the c->rtls
>     iobuffer instead,
>      > + // and then call mg_tls_recv() to decrypt it. NOTE:
>     mg_tls_recv() will
>      > + // call back mg_io_recv() which grabs raw data from c->rtls
>      > + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
>      > + io->len += pkt->pay.len;
>      > +
>      > + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
>     s->ack));
>      > + // Advance ACK counter
>      > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
>      > + s->unacked += pkt->pay.len;
>      > + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
>      > + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
>      > + // Send ACK immediately
>      > + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
>      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
>     TH_ACK,
>      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
>     NULL,
>      > + 0);
>      > + s->unacked = 0;
>      > + s->acked = s->ack;
>      > + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c,
>     MIP_TTYPE_KEEPALIVE);
>      > + } else {
>      > + // if not already running, setup a timer to send an ACK later
>      > + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
>      > + }
>      > +
>      > + if (c->is_tls && c->is_tls_hs) {
>      > + mg_tls_handshake(c);
>      > + } else if (c->is_tls) {
>      > + // TLS connection. Make room for decrypted data in c->recv
>      > + io = &c->recv;
>      > + if (io->size - io->len < pkt->pay.len &&
>      > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>      > + mg_error(c, "oom");
>      > + } else {
>      > + // Decrypt data directly into c->recv
>      > + handle_tls_recv(c, io);
>      > + }
>      > + } else {
>      > + // Plain text connection, data is already in c->recv, trigger
>      > + // MG_EV_READ
>      > + mg_call(c, MG_EV_READ, &pkt->pay.len);
>      > + }
>      > + }
>      > +}
>      > +
>      > +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>      > + struct connstate *s = c == NULL ? NULL : (struct connstate *)
>     (c + 1);
>      > +#if 0
>      > + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
>     pkt->pay.len));
>      > +#endif
>      > + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN
>     | TH_ACK)) {
>      > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>     mg_ntohl(pkt->tcp->seq) + 1;
>      > + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
>      > + c->is_connecting = 0; // Client connected
>      > + settmout(c, MIP_TTYPE_KEEPALIVE);
>      > + mg_call(c, MG_EV_CONNECT, NULL); // Let user know
>      > + } else if (c != NULL && c->is_connecting && pkt->tcp->flags !=
>     TH_ACK) {
>      > + // mg_hexdump(pkt->raw.buf, pkt->raw.len);
>      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > + } else if (c != NULL && pkt->tcp->flags & TH_RST) {
>      > + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>      > + } else if (c != NULL) {
>      > +#if 0
>      > + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
>      > + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
>      > + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
>      > + mg_hexdump(pkt->pay.buf, pkt->pay.len);
>      > +#endif
>      > + s->tmiss = 0; // Reset missed keep-alive counter
>      > + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
>      > + settmout(c,
>      > + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
>      > + read_conn(c, pkt); // Override timer with ACK timeout if needed
>      > + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
>      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > + } else if (pkt->tcp->flags & TH_RST) {
>      > + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>      > + // ignore RST if not connected
>      > + } else if (pkt->tcp->flags & TH_SYN) {
>      > + // Use peer's source port as ISN, in order to recognise the
>     handshake
>      > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
>      > + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
>      > + } else if (pkt->tcp->flags & TH_FIN) {
>      > + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
>     + 1U) {
>      > + accept_conn(c, pkt);
>      > + } else if (!c->is_accepted) { // no peer
>      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > + } else {
>      > + // MG_VERBOSE(("dropped silently.."));
>      > + }
>      > +}
>      > +
>      > +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag &
>     IP_FRAG_OFFSET_MSK) {
>      > + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
>      > + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
>      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>      > + if (c) mg_error(c, "Received fragmented packet");
>      > + } else if (pkt->ip->proto == 1) {
>      > + pkt->icmp = (struct icmp *) (pkt->ip + 1);
>      > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>      > + mkpay(pkt, pkt->icmp + 1);
>      > + rx_icmp(ifp, pkt);
>      > + } else if (pkt->ip->proto == 17) {
>      > + pkt->udp = (struct udp *) (pkt->ip + 1);
>      > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
>      > + mkpay(pkt, pkt->udp + 1);
>      > + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4,
>     &pkt->ip->src,
>      > + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
>      > + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
>      > + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
>      > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>      > + mkpay(pkt, pkt->dhcp + 1);
>      > + rx_dhcp_client(ifp, pkt);
>      > + } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
>     mg_htons(67)) {
>      > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>      > + mkpay(pkt, pkt->dhcp + 1);
>      > + rx_dhcp_server(ifp, pkt);
>      > + } else {
>      > + rx_udp(ifp, pkt);
>      > + }
>      > + } else if (pkt->ip->proto == 6) {
>      > + pkt->tcp = (struct tcp *) (pkt->ip + 1);
>      > + if (pkt->pay.len < sizeof(*pkt->tcp)) return;
>      > + mkpay(pkt, pkt->tcp + 1);
>      > + uint16_t iplen = mg_ntohs(pkt->ip->len);
>      > + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
>      >> 4) * 4U));
>      > + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
>      > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4,
>     &pkt->ip->src,
>      > + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
>      > + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
>      > + rx_tcp(ifp, pkt);
>      > + }
>      > +}
>      > +
>      > +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>      > + // MG_DEBUG(("IP %d", (int) len));
>      > + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
>      > + pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
>      > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>      > + mkpay(pkt, pkt->icmp + 1);
>      > + rx_icmp(ifp, pkt);
>      > + } else if (pkt->ip6->proto == 17) {
>      > + pkt->udp = (struct udp *) (pkt->ip6 + 1);
>      > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
>      > + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
>      > + // mg_htons(udp->dport)));
>      > + mkpay(pkt, pkt->udp + 1);
>      > + }
>      > +}
>      > +
>      > +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf,
>     size_t len) {
>      > + struct pkt pkt;
>      > + memset(&pkt, 0, sizeof(pkt));
>      > + pkt.raw.buf = (char *) buf;
>      > + pkt.raw.len = len;
>      > + pkt.eth = (struct eth *) buf;
>      > + // mg_hexdump(buf, len > 16 ? 16: len);
>      > + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
>      > + if (ifp->enable_mac_check &&
>      > + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
>      > + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
>      > + return;
>      > + if (ifp->enable_crc32_check && len > 4) {
>      > + len -= 4; // TODO(scaprile): check on bigendian
>      > + uint32_t crc = mg_crc32(0, (const char *) buf, len);
>      > + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc)))
>     return;
>      > + }
>      > + if (pkt.eth->type == mg_htons(0x806)) {
>      > + pkt.arp = (struct arp *) (pkt.eth + 1);
>      > + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
>     // Truncated
>      > + rx_arp(ifp, &pkt);
>      > + } else if (pkt.eth->type == mg_htons(0x86dd)) {
>      > + pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
>      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
>     // Truncated
>      > + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
>      > + mkpay(&pkt, pkt.ip6 + 1);
>      > + rx_ip6(ifp, &pkt);
>      > + } else if (pkt.eth->type == mg_htons(0x800)) {
>      > + pkt.ip = (struct ip *) (pkt.eth + 1);
>      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>     // Truncated
>      > + // Truncate frame to what IP header tells us
>      > + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
>     pkt.raw.len) {
>      > + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
>      > + }
>      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>     // Truncated
>      > + if ((pkt.ip->ver >> 4) != 4) return; // Not IP
>      > + mkpay(&pkt, pkt.ip + 1);
>      > + rx_ip(ifp, &pkt);
>      > + } else {
>      > + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
>      > + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ?
>     32 : len);
>      > + }
>      > +}
>      > +
>      > +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
>      > + struct mg_connection *c;
>      > + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
>     1000, now);
>      > + ifp->now = now;
>      > +
>      > +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
>      > + if (expired_1000ms) {
>      > + const char *names[] = {"down", "up", "req", "ready"};
>      > + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
>      > + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
>      > + ifp->ndrop, ifp->nerr));
>      > + }
>      > +#endif
>      > + // Handle physical interface up/down status
>      > + if (expired_1000ms && ifp->driver->up) {
>      > + bool up = ifp->driver->up(ifp);
>      > + bool current = ifp->state != MG_TCPIP_STATE_DOWN;
>      > + if (up != current) {
>      > + ifp->state = up == false ? MG_TCPIP_STATE_DOWN
>      > + : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
>      > + : MG_TCPIP_STATE_READY;
>      > + if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
>      > + onstatechange(ifp);
>      > + }
>      > + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is
>     down"));
>      > + }
>      > + if (ifp->state == MG_TCPIP_STATE_DOWN) return;
>      > +
>      > + // DHCP RFC-2131 (4.4)
>      > + if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
>      > + tx_dhcp_discover(ifp); // INIT (4.4.1)
>      > + } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
>      > + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
>      > + if (ifp->now >= ifp->lease_expire) {
>      > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired,
>     release IP
>      > + onstatechange(ifp);
>      > + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
>      > + ((ifp->now / 1000) % 60) == 0) {
>      > + // hack: 30 min before deadline, try to rebind (4.3.6) every min
>      > + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip,
>     0xffffffff);
>      > + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
>      > + }
>      > +
>      > + // Read data from the network
>      > + if (ifp->driver->rx != NULL) { // Polling driver. We must call it
>      > + size_t len =
>      > + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
>      > + if (len > 0) {
>      > + ifp->nrecv++;
>      > + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
>      > + }
>      > + } else { // Interrupt-based driver. Fills recv queue itself
>      > + char *buf;
>      > + size_t len = mg_queue_next(&ifp->recv_queue, &buf);
>      > + if (len > 0) {
>      > + mg_tcpip_rx(ifp, buf, len);
>      > + mg_queue_del(&ifp->recv_queue, len);
>      > + }
>      > + }
>      > +
>      > + // Process timeouts
>      > + for (c = ifp->mgr->conns; c != NULL; c = c->next) {
>      > + if (c->is_udp || c->is_listening || c->is_resolving) continue;
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + if (now > s->timer) {
>      > + if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
>      > + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
>      > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
>      > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>      > + s->acked = s->ack;
>      > + } else if (s->ttype == MIP_TTYPE_ARP) {
>      > + mg_error(c, "ARP timeout");
>      > + } else if (s->ttype == MIP_TTYPE_SYN) {
>      > + mg_error(c, "Connection timeout");
>      > + } else if (s->ttype == MIP_TTYPE_FIN) {
>      > + c->is_closing = 1;
>      > + continue;
>      > + } else {
>      > + if (s->tmiss++ > 2) {
>      > + mg_error(c, "keepalive");
>      > + } else {
>      > + MG_VERBOSE(("%lu keepalive", c->id));
>      > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
>      > + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
>      > + }
>      > + }
>      > +
>      > + settmout(c, MIP_TTYPE_KEEPALIVE);
>      > + }
>      > + }
>      > +}
>      > +
>      > +// This function executes in interrupt context, thus it should
>     copy data
>      > +// somewhere fast. Note that newlib's malloc is not thread safe,
>     thus use
>      > +// our lock-free queue with preallocated buffer to copy data and
>     return asap
>      > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
>     *ifp) {
>      > + char *p;
>      > + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
>      > + memcpy(p, buf, len);
>      > + mg_queue_add(&ifp->recv_queue, len);
>      > + ifp->nrecv++;
>      > + } else {
>      > + ifp->ndrop++;
>      > + }
>      > +}
>      > +
>      > +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
>      > + // If MAC address is not set, make a random one
>      > + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
>      > + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
>      > + ifp->mac[0] = 0x02; // Locally administered, unicast
>      > + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
>      > + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac,
>     ifp->mac));
>      > + }
>      > +
>      > + if (ifp->driver->init && !ifp->driver->init(ifp)) {
>      > + MG_ERROR(("driver init failed"));
>      > + } else {
>      > + size_t framesize = 1540;
>      > + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len =
>     framesize;
>      > + if (ifp->recv_queue.size == 0)
>      > + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
>      > + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
>      > + ifp->timer_1000ms = mg_millis();
>      > + mgr->priv = ifp;
>      > + ifp->mgr = mgr;
>      > + ifp->mtu = MG_TCPIP_MTU_DEFAULT;
>      > + mgr->extraconnsize = sizeof(struct connstate);
>      > + if (ifp->ip == 0) ifp->enable_dhcp_client = true;
>      > + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
>      > + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to
>     65535
>      > + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
>      > + // MG_EPHEMERAL_PORT_BASE to 65535
>      > + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL)
>     MG_ERROR(("OOM"));
>      > + }
>      > +}
>      > +
>      > +void mg_tcpip_free(struct mg_tcpip_if *ifp) {
>      > + free(ifp->recv_queue.buf);
>      > + free(ifp->tx.buf);
>      > +}
>      > +
>      > +static void send_syn(struct mg_connection *c) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port,
>     isn, 0, NULL,
>      > + 0);
>      > +}
>      > +
>      > +void mg_connect_resolved(struct mg_connection *c) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + c->is_resolving = 0;
>      > + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport =
>     MG_EPHEMERAL_PORT_BASE;
>      > + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
>      > + c->loc.port = mg_htons(ifp->eport++);
>      > + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc,
>     mg_print_ip_port,
>      > + &c->rem));
>      > + mg_call(c, MG_EV_RESOLVE, NULL);
>      > + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip |
>     ~ifp->mask))) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast
>      > + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip &
>     ifp->mask))) {
>      > + // If we're in the same LAN, fire an ARP lookup.
>      > + MG_DEBUG(("%lu ARP lookup...", c->id));
>      > + arp_ask(ifp, rem_ip);
>      > + settmout(c, MIP_TTYPE_ARP);
>      > + c->is_arplooking = 1;
>      > + c->is_connecting = 1;
>      > + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
>      > + struct connstate *s = (struct connstate *) (c + 1); // 224 to
>     239, E0 to EF
>      > + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group
>      > + memcpy(s->mac, mcastp, 3);
>      > + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb
>      > + s->mac[3] &= 0x7F;
>      > + } else {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
>      > + if (c->is_udp) {
>      > + mg_call(c, MG_EV_CONNECT, NULL);
>      > + } else {
>      > + send_syn(c);
>      > + settmout(c, MIP_TTYPE_SYN);
>      > + c->is_connecting = 1;
>      > + }
>      > + }
>      > +}
>      > +
>      > +bool mg_open_listener(struct mg_connection *c, const char *url) {
>      > + c->loc.port = mg_htons(mg_url_port(url));
>      > + return true;
>      > +}
>      > +
>      > +static void write_conn(struct mg_connection *c) {
>      > + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
>      > + : mg_io_send(c, c->send.buf, c->send.len);
>      > + if (len == MG_IO_ERR) {
>      > + mg_error(c, "tx err");
>      > + } else if (len > 0) {
>      > + mg_iobuf_del(&c->send, 0, (size_t) len);
>      > + mg_call(c, MG_EV_WRITE, &len);
>      > + }
>      > +}
>      > +
>      > +static void init_closure(struct mg_connection *c) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + if (c->is_udp == false && c->is_listening == false &&
>      > + c->is_connecting == false) { // For TCP conns,
>      > + struct mg_tcpip_if *ifp =
>      > + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port,
>     c->rem.port,
>      > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>      > + settmout(c, MIP_TTYPE_FIN);
>      > + }
>      > +}
>      > +
>      > +static void close_conn(struct mg_connection *c) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + mg_iobuf_free(&s->raw); // For TLS connections, release raw data
>      > + mg_close_conn(c);
>      > +}
>      > +
>      > +static bool can_write(struct mg_connection *c) {
>      > + return c->is_connecting == 0 && c->is_resolving == 0 &&
>     c->send.len > 0 &&
>      > + c->is_tls_hs == 0 && c->is_arplooking == 0;
>      > +}
>      > +
>      > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
>      > + struct mg_connection *c, *tmp;
>      > + uint64_t now = mg_millis();
>      > + mg_timer_poll(&mgr->timers, now);
>      > + if (ifp == NULL || ifp->driver == NULL) return;
>      > + mg_tcpip_poll(ifp, now);
>      > + for (c = mgr->conns; c != NULL; c = tmp) {
>      > + tmp = c->next;
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + mg_call(c, MG_EV_POLL, &now);
>      > + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
>      > + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>      > + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>      > + if (c->is_tls && mg_tls_pending(c) > 0)
>      > + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
>      > + if (can_write(c)) write_conn(c);
>      > + if (c->is_draining && c->send.len == 0 && s->ttype !=
>     MIP_TTYPE_FIN)
>      > + init_closure(c);
>      > + if (c->is_closing) close_conn(c);
>      > + }
>      > + (void) ms;
>      > +}
>      > +
>      > +bool mg_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>      > + bool res = false;
>      > + uint32_t rem_ip;
>      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>      > + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
>      > + mg_error(c, "net down");
>      > + } else if (c->is_udp) {
>      > + struct connstate *s = (struct connstate *) (c + 1);
>      > + len = trim_len(c, len); // Trimming length if necessary
>      > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port,
>     buf, len);
>      > + res = true;
>      > + } else {
>      > + res = mg_iobuf_add(&c->send, c->send.len, buf, len);
>      > + }
>      > + return res;
>      > +}
>      > +#endif // MG_ENABLE_TCPIP
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/ota_dummy.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +#if MG_OTA == MG_OTA_NONE
>      > +bool mg_ota_begin(size_t new_firmware_size) {
>      > + (void) new_firmware_size;
>      > + return true;
>      > +}
>      > +bool mg_ota_write(const void *buf, size_t len) {
>      > + (void) buf, (void) len;
>      > + return true;
>      > +}
>      > +bool mg_ota_end(void) {
>      > + return true;
>      > +}
>      > +bool mg_ota_commit(void) {
>      > + return true;
>      > +}
>      > +bool mg_ota_rollback(void) {
>      > + return true;
>      > +}
>      > +int mg_ota_status(int fw) {
>      > + (void) fw;
>      > + return 0;
>      > +}
>      > +uint32_t mg_ota_crc32(int fw) {
>      > + (void) fw;
>      > + return 0;
>      > +}
>      > +uint32_t mg_ota_timestamp(int fw) {
>      > + (void) fw;
>      > + return 0;
>      > +}
>      > +size_t mg_ota_size(int fw) {
>      > + (void) fw;
>      > + return 0;
>      > +}
>      > +MG_IRAM void mg_ota_boot(void) {
>      > +}
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/ota_esp32.c"
>      > +#endif
>      > +
>      > +
>      > +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
>      > +
>      > +static const esp_partition_t *s_ota_update_partition;
>      > +static esp_ota_handle_t s_ota_update_handle;
>      > +static bool s_ota_success;
>      > +
>      > +// Those empty macros do nothing, but mark places in the code
>     which could
>      > +// potentially trigger a watchdog reboot due to the log flash
>     erase operation
>      > +#define disable_wdt()
>      > +#define enable_wdt()
>      > +
>      > +bool mg_ota_begin(size_t new_firmware_size) {
>      > + if (s_ota_update_partition != NULL) {
>      > + MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
>      > + return false;
>      > + } else {
>      > + s_ota_success = false;
>      > + disable_wdt();
>      > + s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
>      > + esp_err_t err = esp_ota_begin(s_ota_update_partition,
>     new_firmware_size,
>      > + &s_ota_update_handle);
>      > + enable_wdt();
>      > + MG_DEBUG(("esp_ota_begin(): %d", err));
>      > + s_ota_success = (err == ESP_OK);
>      > + }
>      > + return s_ota_success;
>      > +}
>      > +
>      > +bool mg_ota_write(const void *buf, size_t len) {
>      > + disable_wdt();
>      > + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
>      > + enable_wdt();
>      > + MG_INFO(("esp_ota_write(): %d", err));
>      > + s_ota_success = err == ESP_OK;
>      > + return s_ota_success;
>      > +}
>      > +
>      > +bool mg_ota_end(void) {
>      > + esp_err_t err = esp_ota_end(s_ota_update_handle);
>      > + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
>      > + if (s_ota_success && err == ESP_OK) {
>      > + err = esp_ota_set_boot_partition(s_ota_update_partition);
>      > + s_ota_success = (err == ESP_OK);
>      > + }
>      > + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
>      > + s_ota_update_partition = NULL;
>      > + return s_ota_success;
>      > +}
>      > +
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/ota_flash.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +// This OTA implementation uses the internal flash API outlined
>     in device.h
>      > +// It splits flash into 2 equal partitions, and stores OTA
>     status in the
>      > +// last sector of the partition.
>      > +
>      > +#if MG_OTA == MG_OTA_FLASH
>      > +
>      > +#define MG_OTADATA_KEY 0xb07afed0
>      > +
>      > +static char *s_addr; // Current address to write to
>      > +static size_t s_size; // Firmware size to flash. In-progress
>     indicator
>      > +static uint32_t s_crc32; // Firmware checksum
>      > +
>      > +struct mg_otadata {
>      > + uint32_t crc32, size, timestamp, status;
>      > +};
>      > +
>      > +bool mg_ota_begin(size_t new_firmware_size) {
>      > + bool ok = false;
>      > + if (s_size) {
>      > + MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
>      > + } else {
>      > + size_t half = mg_flash_size() / 2, max = half -
>     mg_flash_sector_size();
>      > + s_crc32 = 0;
>      > + s_addr = (char *) mg_flash_start() + half;
>      > + MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
>      > + if (new_firmware_size < max) {
>      > + ok = true;
>      > + s_size = new_firmware_size;
>      > + MG_INFO(("Starting OTA, firmware size %lu", s_size));
>      > + } else {
>      > + MG_ERROR(("Firmware %lu is too big to fit %lu",
>     new_firmware_size, max));
>      > + }
>      > + }
>      > + return ok;
>      > +}
>      > +
>      > +bool mg_ota_write(const void *buf, size_t len) {
>      > + bool ok = false;
>      > + if (s_size == 0) {
>      > + MG_ERROR(("OTA is not started, call mg_ota_begin()"));
>      > + } else {
>      > + size_t align = mg_flash_write_align();
>      > + size_t len_aligned_down = MG_ROUND_DOWN(len, align);
>      > + if (len_aligned_down) ok = mg_flash_write(s_addr, buf,
>     len_aligned_down);
>      > + if (len_aligned_down < len) {
>      > + size_t left = len - len_aligned_down;
>      > + char tmp[align];
>      > + memset(tmp, 0xff, sizeof(tmp));
>      > + memcpy(tmp, (char *) buf + len_aligned_down, left);
>      > + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
>      > + }
>      > + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
>      > + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
>      > + s_addr += len;
>      > + }
>      > + return ok;
>      > +}
>      > +
>      > +MG_IRAM static uint32_t mg_fwkey(int fw) {
>      > + uint32_t key = MG_OTADATA_KEY + fw;
>      > + int bank = mg_flash_bank();
>      > + if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
>      > + if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
>      > + return key;
>      > +}
>      > +
>      > +bool mg_ota_end(void) {
>      > + char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
>      > + bool ok = false;
>      > + if (s_size) {
>      > + size_t size = s_addr - base;
>      > + uint32_t crc32 = mg_crc32(0, base, s_size);
>      > + if (size == s_size && crc32 == s_crc32) {
>      > + uint32_t now = (uint32_t) (mg_now() / 1000);
>      > + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
>      > + uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
>      > + ok = mg_flash_save(NULL, key, &od, sizeof(od));
>      > + }
>      > + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32,
>     crc32, s_size,
>      > + size, ok ? "ok" : "fail"));
>      > + s_size = 0;
>      > + if (ok) ok = mg_flash_swap_bank();
>      > + }
>      > + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
>      > + return ok;
>      > +}
>      > +
>      > +MG_IRAM static struct mg_otadata mg_otadata(int fw) {
>      > + uint32_t key = mg_fwkey(fw);
>      > + struct mg_otadata od = {};
>      > + MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ?
>     "curr" : "prev"));
>      > + mg_flash_load(NULL, key, &od, sizeof(od));
>      > + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw,
>     bank, key));
>      > + // mg_hexdump(&od, sizeof(od));
>      > + return od;
>      > +}
>      > +
>      > +int mg_ota_status(int fw) {
>      > + struct mg_otadata od = mg_otadata(fw);
>      > + return od.status;
>      > +}
>      > +uint32_t mg_ota_crc32(int fw) {
>      > + struct mg_otadata od = mg_otadata(fw);
>      > + return od.crc32;
>      > +}
>      > +uint32_t mg_ota_timestamp(int fw) {
>      > + struct mg_otadata od = mg_otadata(fw);
>      > + return od.timestamp;
>      > +}
>      > +size_t mg_ota_size(int fw) {
>      > + struct mg_otadata od = mg_otadata(fw);
>      > + return od.size;
>      > +}
>      > +
>      > +MG_IRAM bool mg_ota_commit(void) {
>      > + bool ok = true;
>      > + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
>      > + if (od.status != MG_OTA_COMMITTED) {
>      > + od.status = MG_OTA_COMMITTED;
>      > + MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
>      > + ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od,
>     sizeof(od));
>      > + }
>      > + return ok;
>      > +}
>      > +
>      > +bool mg_ota_rollback(void) {
>      > + MG_DEBUG(("Rolling firmware back"));
>      > + if (mg_flash_bank() == 0) {
>      > + // No dual bank support. Mark previous firmware as FIRST_BOOT
>      > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
>      > + prev.status = MG_OTA_FIRST_BOOT;
>      > + return mg_flash_save(NULL, MG_OTADATA_KEY +
>     MG_FIRMWARE_PREVIOUS, &prev,
>      > + sizeof(prev));
>      > + } else {
>      > + return mg_flash_swap_bank();
>      > + }
>      > +}
>      > +
>      > +MG_IRAM void mg_ota_boot(void) {
>      > + MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
>      > + struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
>      > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
>      > +
>      > + if (curr.status == MG_OTA_FIRST_BOOT) {
>      > + if (prev.status == MG_OTA_UNAVAILABLE) {
>      > + MG_INFO(("Setting previous firmware state to committed"));
>      > + prev.status = MG_OTA_COMMITTED;
>      > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev,
>     sizeof(prev));
>      > + }
>      > + curr.status = MG_OTA_UNCOMMITTED;
>      > + MG_INFO(("First boot, setting status to UNCOMMITTED"));
>      > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr,
>     sizeof(curr));
>      > + } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank()
>     == 0) {
>      > + // Swap paritions. Pray power does not disappear
>      > + size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
>      > + char *partition1 = mg_flash_start();
>      > + char *partition2 = mg_flash_start() + fs / 2;
>      > + size_t ofs, max = fs / 2 - ss; // Set swap size to the whole
>     partition
>      > +
>      > + if (curr.status != MG_OTA_UNAVAILABLE &&
>      > + prev.status != MG_OTA_UNAVAILABLE) {
>      > + // We know exact sizes of both firmwares.
>      > + // Shrink swap size to the MAX(firmware1, firmware2)
>      > + size_t sz = curr.size > prev.size ? curr.size : prev.size;
>      > + if (sz > 0 && sz < max) max = sz;
>      > + }
>      > +
>      > + // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
>      > + prev.status = MG_OTA_UNCOMMITTED;
>      > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
>      > + sizeof(prev));
>      > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
>      > + sizeof(curr));
>      > +
>      > + MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max
>     / ss));
>      > + MG_INFO(("Do NOT power off..."));
>      > + mg_log_level = MG_LL_NONE;
>      > +
>      > + // We use the last sector of partition2 for OTA data/config
>     storage
>      > + // Therefore we can use last sector of partition1 for swapping
>      > + char *tmpsector = partition1 + fs / 2 - ss; // Last sector of
>     partition1
>      > + (void) tmpsector;
>      > + for (ofs = 0; ofs < max; ofs += ss) {
>      > + // mg_flash_erase(tmpsector);
>      > + mg_flash_write(tmpsector, partition1 + ofs, ss);
>      > + // mg_flash_erase(partition1 + ofs);
>      > + mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
>      > + // mg_flash_erase(partition2 + ofs);
>      > + mg_flash_write(partition2 + ofs, tmpsector, ss);
>      > + }
>      > + mg_device_reset();
>      > + }
>      > +}
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/printf.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt,
>     va_list *ap) {
>      > + size_t len = mg_snprintf(NULL, 0, fmt, ap);
>      > + char *buf;
>      > + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
>      > + len = 0; // Nah. Not enough space
>      > + } else {
>      > + len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
>      > + mg_queue_add(q, len);
>      > + }
>      > + return len;
>      > +}
>      > +
>      > +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
>      > + va_list ap;
>      > + size_t len;
>      > + va_start(ap, fmt);
>      > + len = mg_queue_vprintf(q, fmt, &ap);
>      > + va_end(ap);
>      > + return len;
>      > +}
>      > +
>      > +static void mg_pfn_iobuf_private(char ch, void *param, bool
>     expand) {
>      > + struct mg_iobuf *io = (struct mg_iobuf *) param;
>      > + if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
>     io->len + 2);
>      > + if (io->len + 2 <= io->size) {
>      > + io->buf[io->len++] = (uint8_t) ch;
>      > + io->buf[io->len] = 0;
>      > + } else if (io->len < io->size) {
>      > + io->buf[io->len++] = 0; // Guarantee to 0-terminate
>      > + }
>      > +}
>      > +
>      > +static void mg_putchar_iobuf_static(char ch, void *param) {
>      > + mg_pfn_iobuf_private(ch, param, false);
>      > +}
>      > +
>      > +void mg_pfn_iobuf(char ch, void *param) {
>      > + mg_pfn_iobuf_private(ch, param, true);
>      > +}
>      > +
>      > +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>     va_list *ap) {
>      > + struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
>      > + size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
>      > + if (n < len) buf[n] = '\0';
>      > + return n;
>      > +}
>      > +
>      > +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
>      > + va_list ap;
>      > + size_t n;
>      > + va_start(ap, fmt);
>      > + n = mg_vsnprintf(buf, len, fmt, &ap);
>      > + va_end(ap);
>      > + return n;
>      > +}
>      > +
>      > +char *mg_vmprintf(const char *fmt, va_list *ap) {
>      > + struct mg_iobuf io = {0, 0, 0, 256};
>      > + mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
>      > + return (char *) io.buf;
>      > +}
>      > +
>      > +char *mg_mprintf(const char *fmt, ...) {
>      > + char *s;
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + s = mg_vmprintf(fmt, &ap);
>      > + va_end(ap);
>      > + return s;
>      > +}
>      > +
>      > +void mg_pfn_stdout(char c, void *param) {
>      > + putchar(c);
>      > + (void) param;
>      > +}
>      > +
>      > +static size_t print_ip4(void (*out)(char, void *), void *arg,
>     uint8_t *p) {
>      > + return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2],
>     p[3]);
>      > +}
>      > +
>      > +static size_t print_ip6(void (*out)(char, void *), void *arg,
>     uint16_t *p) {
>      > + return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]",
>     mg_ntohs(p[0]),
>      > + mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
>      > + mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
>      > + mg_ntohs(p[7]));
>      > +}
>      > +
>      > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + uint8_t *p = va_arg(*ap, uint8_t *);
>      > + return print_ip4(out, arg, p);
>      > +}
>      > +
>      > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + uint16_t *p = va_arg(*ap, uint16_t *);
>      > + return print_ip6(out, arg, p);
>      > +}
>      > +
>      > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
>     *ap) {
>      > + struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
>      > + if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *)
>     addr->ip);
>      > + return print_ip4(out, arg, (uint8_t *) &addr->ip);
>      > +}
>      > +
>      > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + struct mg_addr *a = va_arg(*ap, struct mg_addr *);
>      > + return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a,
>     mg_ntohs(a->port));
>      > +}
>      > +
>      > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + uint8_t *p = va_arg(*ap, uint8_t *);
>      > + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x",
>     p[0], p[1], p[2],
>      > + p[3], p[4], p[5]);
>      > +}
>      > +
>      > +static char mg_esc(int c, bool esc) {
>      > + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
>      > + for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
>      > + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
>      > + }
>      > + return 0;
>      > +}
>      > +
>      > +static char mg_escape(int c) {
>      > + return mg_esc(c, true);
>      > +}
>      > +
>      > +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
>      > + size_t len) {
>      > + size_t i = 0, extra = 0;
>      > + for (i = 0; i < len && buf[i] != '\0'; i++) {
>      > + char c = mg_escape(buf[i]);
>      > + if (c) {
>      > + out('\\', ptr), out(c, ptr), extra++;
>      > + } else {
>      > + out(buf[i], ptr);
>      > + }
>      > + }
>      > + return i + extra;
>      > +}
>      > +
>      > +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t
>     *buf,
>      > + size_t len) {
>      > + size_t i, j, n = 0;
>      > + const char *t =
>      > +
>     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
>      > + for (i = 0; i < len; i += 3) {
>      > + uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
>      > + c3 = i + 2 < len ? buf[i + 2] : 0;
>      > + char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
>     '='};
>      > + if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
>      > + if (i + 2 < len) tmp[3] = t[c3 & 63];
>      > + for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j],
>     arg);
>      > + n += j;
>      > + }
>      > + return n;
>      > +}
>      > +
>      > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + size_t bl = (size_t) va_arg(*ap, int);
>      > + uint8_t *p = va_arg(*ap, uint8_t *);
>      > + const char *hex = "0123456789abcdef";
>      > + size_t j;
>      > + for (j = 0; j < bl; j++) {
>      > + out(hex[(p[j] >> 4) & 0x0F], arg);
>      > + out(hex[p[j] & 0x0F], arg);
>      > + }
>      > + return 2 * bl;
>      > +}
>      > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + size_t len = (size_t) va_arg(*ap, int);
>      > + uint8_t *buf = va_arg(*ap, uint8_t *);
>      > + return bcpy(out, arg, buf, len);
>      > +}
>      > +
>      > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
>     va_list *ap) {
>      > + size_t len = (size_t) va_arg(*ap, int);
>      > + char *p = va_arg(*ap, char *);
>      > + if (len == 0) len = p == NULL ? 0 : strlen(p);
>      > + return qcpy(out, arg, p, len);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/queue.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +#if (defined(__GNUC__) && (__GNUC__ > 4) || \
>      > + (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >=
>     1)) || \
>      > + defined(__clang__)
>      > +#define MG_MEMORY_BARRIER() __sync_synchronize()
>      > +#elif defined(_MSC_VER) && _MSC_VER >= 1700
>      > +#define MG_MEMORY_BARRIER() MemoryBarrier()
>      > +#elif !defined(MG_MEMORY_BARRIER)
>      > +#define MG_MEMORY_BARRIER()
>      > +#endif
>      > +
>      > +// Every message in a queue is prepended by a 32-bit message
>     length (ML).
>      > +// If ML is 0, then it is the end, and reader must wrap to the
>     beginning.
>      > +//
>      > +// Queue when q->tail <= q->head:
>      > +// |----- free -----| ML | message1 | ML | message2 | ----- free
>     ------|
>      > +// ^ ^ ^ ^
>      > +// buf tail head len
>      > +//
>      > +// Queue when q->tail > q->head:
>      > +// | ML | message2 |----- free ------| ML | message1 | 0 |----
>     free ----|
>      > +// ^ ^ ^ ^
>      > +// buf head tail len
>      > +
>      > +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
>      > + q->size = size;
>      > + q->buf = buf;
>      > + q->head = q->tail = 0;
>      > +}
>      > +
>      > +static size_t mg_queue_read_len(struct mg_queue *q) {
>      > + uint32_t n = 0;
>      > + MG_MEMORY_BARRIER();
>      > + memcpy(&n, q->buf + q->tail, sizeof(n));
>      > + assert(q->tail + n + sizeof(n) <= q->size);
>      > + return n;
>      > +}
>      > +
>      > +static void mg_queue_write_len(struct mg_queue *q, size_t len) {
>      > + uint32_t n = (uint32_t) len;
>      > + memcpy(q->buf + q->head, &n, sizeof(n));
>      > + MG_MEMORY_BARRIER();
>      > +}
>      > +
>      > +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
>      > + size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0
>     marker
>      > + if (q->head >= q->tail && q->head + len + hs <= q->size) {
>      > + space = q->size - q->head - hs; // There is enough space
>      > + } else if (q->head >= q->tail && q->tail > hs) {
>      > + mg_queue_write_len(q, 0); // Not enough space ahead
>      > + q->head = 0; // Wrap head to the beginning
>      > + }
>      > + if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
>      > + if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
>      > + return space;
>      > +}
>      > +
>      > +size_t mg_queue_next(struct mg_queue *q, char **buf) {
>      > + size_t len = 0;
>      > + if (q->tail != q->head) {
>      > + len = mg_queue_read_len(q);
>      > + if (len == 0) { // Zero (head wrapped) ?
>      > + q->tail = 0; // Reset tail to the start
>      > + if (q->head > q->tail) len = mg_queue_read_len(q); // Read again
>      > + }
>      > + }
>      > + if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
>      > + assert(q->tail + len <= q->size);
>      > + return len;
>      > +}
>      > +
>      > +void mg_queue_add(struct mg_queue *q, size_t len) {
>      > + assert(len > 0);
>      > + mg_queue_write_len(q, len);
>      > + assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
>      > + q->head += len + sizeof(uint32_t);
>      > +}
>      > +
>      > +void mg_queue_del(struct mg_queue *q, size_t len) {
>      > + q->tail += len + sizeof(uint32_t);
>      > + assert(q->tail + sizeof(uint32_t) <= q->size);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/rpc.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
>      > + void (*fn)(struct mg_rpc_req *), void *fn_data) {
>      > + struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
>      > + if (rpc != NULL) {
>      > + rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
>      > + rpc->method.len = method.len;
>      > + rpc->fn = fn;
>      > + rpc->fn_data = fn_data;
>      > + rpc->next = *head, *head = rpc;
>      > + }
>      > +}
>      > +
>      > +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
>     mg_rpc_req *)) {
>      > + struct mg_rpc *r;
>      > + while ((r = *head) != NULL) {
>      > + if (r->fn == fn || fn == NULL) {
>      > + *head = r->next;
>      > + free((void *) r->method.buf);
>      > + free(r);
>      > + } else {
>      > + head = &(*head)->next;
>      > + }
>      > + }
>      > +}
>      > +
>      > +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
>     method) {
>      > + struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
>      > + while (h != NULL && !mg_match(method, h->method, NULL)) h =
>     h->next;
>      > + if (h != NULL) {
>      > + r->rpc = h;
>      > + h->fn(r);
>      > + } else {
>      > + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
>     method.buf);
>      > + }
>      > +}
>      > +
>      > +void mg_rpc_process(struct mg_rpc_req *r) {
>      > + int len, off = mg_json_get(r->frame, "$.method", &len);
>      > + if (off > 0 && r->frame.buf[off] == '"') {
>      > + struct mg_str method = mg_str_n(&r->frame.buf[off + 1],
>     (size_t) len - 2);
>      > + mg_rpc_call(r, method);
>      > + } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
>      > + (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
>      > + mg_rpc_call(r, mg_str("")); // JSON response! call "" method
>     handler
>      > + } else {
>      > + mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
>      > + r->frame.buf); // Invalid
>      > + }
>      > +}
>      > +
>      > +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
>     *ap) {
>      > + int len, off = mg_json_get(r->frame, "$.id", &len);
>      > + if (off > 0) {
>      > + mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc,
>     0, "id", len,
>      > + &r->frame.buf[off], mg_print_esc, 0, "result");
>      > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>      > + mg_xprintf(r->pfn, r->pfn_data, "}");
>      > + }
>      > +}
>      > +
>      > +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + mg_rpc_vok(r, fmt, &ap);
>      > + va_end(ap);
>      > +}
>      > +
>      > +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
>     *fmt, va_list *ap) {
>      > + int len, off = mg_json_get(r->frame, "$.id", &len);
>      > + mg_xprintf(r->pfn, r->pfn_data, "{");
>      > + if (off > 0) {
>      > + mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0,
>     "id", len,
>      > + &r->frame.buf[off]);
>      > + }
>      > + mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc,
>     0, "error",
>      > + mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
>      > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>      > + mg_xprintf(r->pfn, r->pfn_data, "}}");
>      > +}
>      > +
>      > +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
>     ...) {
>      > + va_list ap;
>      > + va_start(ap, fmt);
>      > + mg_rpc_verr(r, code, fmt, &ap);
>      > + va_end(ap);
>      > +}
>      > +
>      > +static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
>     va_list *ap) {
>      > + struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
>     **);
>      > + size_t len = 0;
>      > + for (h = *head; h != NULL; h = h->next) {
>      > + if (h->method.len == 0) continue; // Ignore response handler
>      > + len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
>      > + mg_print_esc, (int) h->method.len, h->method.buf);
>      > + }
>      > + return len;
>      > +}
>      > +
>      > +void mg_rpc_list(struct mg_rpc_req *r) {
>      > + mg_rpc_ok(r, "[%M]", print_methods, r->head);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/sha1.c"
>      > +#endif
>      > +/* Copyright(c) By Steve Reid <st...@edmweb.com> */
>      > +/* 100% Public Domain */
>      > +
>      > +
>      > +
>      > +union char64long16 {
>      > + unsigned char c[64];
>      > + uint32_t l[16];
>      > +};
>      > +
>      > +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
>     - (bits))))
>      > +
>      > +static uint32_t blk0(union char64long16 *block, int i) {
>      > + if (MG_BIG_ENDIAN) {
>      > + } else {
>      > + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
>      > + (rol(block->l[i], 8) & 0x00FF00FF);
>      > + }
>      > + return block->l[i];
>      > +}
>      > +
>      > +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
>     define R0~R4) */
>      > +#undef blk
>      > +#undef R0
>      > +#undef R1
>      > +#undef R2
>      > +#undef R3
>      > +#undef R4
>      > +
>      > +#define blk(i) \
>      > + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
>     8) & 15] ^ \
>      > + block->l[(i + 2) & 15] ^ block->l[i & 15], \
>      > + 1))
>      > +#define R0(v, w, x, y, z, i) \
>      > + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
>     5); \
>      > + w = rol(w, 30);
>      > +#define R1(v, w, x, y, z, i) \
>      > + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
>      > + w = rol(w, 30);
>      > +#define R2(v, w, x, y, z, i) \
>      > + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
>      > + w = rol(w, 30);
>      > +#define R3(v, w, x, y, z, i) \
>      > + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
>     5); \
>      > + w = rol(w, 30);
>      > +#define R4(v, w, x, y, z, i) \
>      > + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
>      > + w = rol(w, 30);
>      > +
>      > +static void mg_sha1_transform(uint32_t state[5],
>      > + const unsigned char *buffer) {
>      > + uint32_t a, b, c, d, e;
>      > + union char64long16 block[1];
>      > +
>      > + memcpy(block, buffer, 64);
>      > + a = state[0];
>      > + b = state[1];
>      > + c = state[2];
>      > + d = state[3];
>      > + e = state[4];
>      > + R0(a, b, c, d, e, 0);
>      > + R0(e, a, b, c, d, 1);
>      > + R0(d, e, a, b, c, 2);
>      > + R0(c, d, e, a, b, 3);
>      > + R0(b, c, d, e, a, 4);
>      > + R0(a, b, c, d, e, 5);
>      > + R0(e, a, b, c, d, 6);
>      > + R0(d, e, a, b, c, 7);
>      > + R0(c, d, e, a, b, 8);
>      > + R0(b, c, d, e, a, 9);
>      > + R0(a, b, c, d, e, 10);
>      > + R0(e, a, b, c, d, 11);
>      > + R0(d, e, a, b, c, 12);
>      > + R0(c, d, e, a, b, 13);
>      > + R0(b, c, d, e, a, 14);
>      > + R0(a, b, c, d, e, 15);
>      > + R1(e, a, b, c, d, 16);
>      > + R1(d, e, a, b, c, 17);
>      > + R1(c, d, e, a, b, 18);
>      > + R1(b, c, d, e, a, 19);
>      > + R2(a, b, c, d, e, 20);
>      > + R2(e, a, b, c, d, 21);
>      > + R2(d, e, a, b, c, 22);
>      > + R2(c, d, e, a, b, 23);
>      > + R2(b, c, d, e, a, 24);
>      > + R2(a, b, c, d, e, 25);
>      > + R2(e, a, b, c, d, 26);
>      > + R2(d, e, a, b, c, 27);
>      > + R2(c, d, e, a, b, 28);
>      > + R2(b, c, d, e, a, 29);
>      > + R2(a, b, c, d, e, 30);
>      > + R2(e, a, b, c, d, 31);
>      > + R2(d, e, a, b, c, 32);
>      > + R2(c, d, e, a, b, 33);
>      > + R2(b, c, d, e, a, 34);
>      > + R2(a, b, c, d, e, 35);
>      > + R2(e, a, b, c, d, 36);
>      > + R2(d, e, a, b, c, 37);
>      > + R2(c, d, e, a, b, 38);
>      > + R2(b, c, d, e, a, 39);
>      > + R3(a, b, c, d, e, 40);
>      > + R3(e, a, b, c, d, 41);
>      > + R3(d, e, a, b, c, 42);
>      > + R3(c, d, e, a, b, 43);
>      > + R3(b, c, d, e, a, 44);
>      > + R3(a, b, c, d, e, 45);
>      > + R3(e, a, b, c, d, 46);
>      > + R3(d, e, a, b, c, 47);
>      > + R3(c, d, e, a, b, 48);
>      > + R3(b, c, d, e, a, 49);
>      > + R3(a, b, c, d, e, 50);
>      > + R3(e, a, b, c, d, 51);
>      > + R3(d, e, a, b, c, 52);
>      > + R3(c, d, e, a, b, 53);
>      > + R3(b, c, d, e, a, 54);
>      > + R3(a, b, c, d, e, 55);
>      > + R3(e, a, b, c, d, 56);
>      > + R3(d, e, a, b, c, 57);
>      > + R3(c, d, e, a, b, 58);
>      > + R3(b, c, d, e, a, 59);
>      > + R4(a, b, c, d, e, 60);
>      > + R4(e, a, b, c, d, 61);
>      > + R4(d, e, a, b, c, 62);
>      > + R4(c, d, e, a, b, 63);
>      > + R4(b, c, d, e, a, 64);
>      > + R4(a, b, c, d, e, 65);
>      > + R4(e, a, b, c, d, 66);
>      > + R4(d, e, a, b, c, 67);
>      > + R4(c, d, e, a, b, 68);
>      > + R4(b, c, d, e, a, 69);
>      > + R4(a, b, c, d, e, 70);
>      > + R4(e, a, b, c, d, 71);
>      > + R4(d, e, a, b, c, 72);
>      > + R4(c, d, e, a, b, 73);
>      > + R4(b, c, d, e, a, 74);
>      > + R4(a, b, c, d, e, 75);
>      > + R4(e, a, b, c, d, 76);
>      > + R4(d, e, a, b, c, 77);
>      > + R4(c, d, e, a, b, 78);
>      > + R4(b, c, d, e, a, 79);
>      > + state[0] += a;
>      > + state[1] += b;
>      > + state[2] += c;
>      > + state[3] += d;
>      > + state[4] += e;
>      > + /* Erase working structures. The order of operations is important,
>      > + * used to ensure that compiler doesn't optimize those out. */
>      > + memset(block, 0, sizeof(block));
>      > + a = b = c = d = e = 0;
>      > + (void) a;
>      > + (void) b;
>      > + (void) c;
>      > + (void) d;
>      > + (void) e;
>      > +}
>      > +
>      > +void mg_sha1_init(mg_sha1_ctx *context) {
>      > + context->state[0] = 0x67452301;
>      > + context->state[1] = 0xEFCDAB89;
>      > + context->state[2] = 0x98BADCFE;
>      > + context->state[3] = 0x10325476;
>      > + context->state[4] = 0xC3D2E1F0;
>      > + context->count[0] = context->count[1] = 0;
>      > +}
>      > +
>      > +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
>     *data,
>      > + size_t len) {
>      > + size_t i, j;
>      > +
>      > + j = context->count[0];
>      > + if ((context->count[0] += (uint32_t) len << 3) < j)
>     context->count[1]++;
>      > + context->count[1] += (uint32_t) (len >> 29);
>      > + j = (j >> 3) & 63;
>      > + if ((j + len) > 63) {
>      > + memcpy(&context->buffer[j], data, (i = 64 - j));
>      > + mg_sha1_transform(context->state, context->buffer);
>      > + for (; i + 63 < len; i += 64) {
>      > + mg_sha1_transform(context->state, &data[i]);
>      > + }
>      > + j = 0;
>      > + } else
>      > + i = 0;
>      > + memcpy(&context->buffer[j], &data[i], len - i);
>      > +}
>      > +
>      > +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
>     *context) {
>      > + unsigned i;
>      > + unsigned char finalcount[8], c;
>      > +
>      > + for (i = 0; i < 8; i++) {
>      > + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
>     1)] >>
>      > + ((3 - (i & 3)) * 8)) &
>      > + 255);
>      > + }
>      > + c = 0200;
>      > + mg_sha1_update(context, &c, 1);
>      > + while ((context->count[0] & 504) != 448) {
>      > + c = 0000;
>      > + mg_sha1_update(context, &c, 1);
>      > + }
>      > + mg_sha1_update(context, finalcount, 8);
>      > + for (i = 0; i < 20; i++) {
>      > + digest[i] =
>      > + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
>     8)) & 255);
>      > + }
>      > + memset(context, '\0', sizeof(*context));
>      > + memset(&finalcount, '\0', sizeof(finalcount));
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/sha256.c"
>      > +#endif
>      > +// https://github.com/B-Con/crypto-algorithms
>     <https://github.com/B-Con/crypto-algorithms>
>      > +// Author: Brad Conte (brad AT bradconte.com
>     <http://bradconte.com>)
>      > +// Disclaimer: This code is presented "as is" without any
>     guarantees.
>      > +// Details: Defines the API for the corresponding SHA1
>     implementation.
>      > +// Copyright: public domain
>      > +
>      > +
>      > +
>      > +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
>      > +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
>      > +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
>      > +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
>      > +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
>      > +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
>      > +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
>      > +
>      > +static const uint32_t mg_sha256_k[64] = {
>      > + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
>     0x59f111f1,
>      > + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
>     0x550c7dc3,
>      > + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1,
>     0xefbe4786,
>      > + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc,
>     0x76f988da,
>      > + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3,
>     0xd5a79147,
>      > + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
>     0x53380d13,
>      > + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1,
>     0xa81a664b,
>      > + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585,
>     0x106aa070,
>      > + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3,
>     0x4ed8aa4a,
>      > + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814,
>     0x8cc70208,
>      > + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
>      > +
>      > +void mg_sha256_init(mg_sha256_ctx *ctx) {
>      > + ctx->len = 0;
>      > + ctx->bits = 0;
>      > + ctx->state[0] = 0x6a09e667;
>      > + ctx->state[1] = 0xbb67ae85;
>      > + ctx->state[2] = 0x3c6ef372;
>      > + ctx->state[3] = 0xa54ff53a;
>      > + ctx->state[4] = 0x510e527f;
>      > + ctx->state[5] = 0x9b05688c;
>      > + ctx->state[6] = 0x1f83d9ab;
>      > + ctx->state[7] = 0x5be0cd19;
>      > +}
>      > +
>      > +static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
>      > + int i, j;
>      > + uint32_t a, b, c, d, e, f, g, h;
>      > + uint32_t m[64];
>      > + for (i = 0, j = 0; i < 16; ++i, j += 4)
>      > + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
>      > + ((uint32_t) ctx->buffer[j + 1] << 16) |
>      > + ((uint32_t) ctx->buffer[j + 2] << 8) |
>      > + ((uint32_t) ctx->buffer[j + 3]));
>      > + for (; i < 64; ++i)
>      > + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
>      > +
>      > + a = ctx->state[0];
>      > + b = ctx->state[1];
>      > + c = ctx->state[2];
>      > + d = ctx->state[3];
>      > + e = ctx->state[4];
>      > + f = ctx->state[5];
>      > + g = ctx->state[6];
>      > + h = ctx->state[7];
>      > +
>      > + for (i = 0; i < 64; ++i) {
>      > + uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
>      > + uint32_t t2 = ep0(a) + maj(a, b, c);
>      > + h = g;
>      > + g = f;
>      > + f = e;
>      > + e = d + t1;
>      > + d = c;
>      > + c = b;
>      > + b = a;
>      > + a = t1 + t2;
>      > + }
>      > +
>      > + ctx->state[0] += a;
>      > + ctx->state[1] += b;
>      > + ctx->state[2] += c;
>      > + ctx->state[3] += d;
>      > + ctx->state[4] += e;
>      > + ctx->state[5] += f;
>      > + ctx->state[6] += g;
>      > + ctx->state[7] += h;
>      > +}
>      > +
>      > +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char
>     *data,
>      > + size_t len) {
>      > + size_t i;
>      > + for (i = 0; i < len; i++) {
>      > + ctx->buffer[ctx->len] = data[i];
>      > + if ((++ctx->len) == 64) {
>      > + mg_sha256_chunk(ctx);
>      > + ctx->bits += 512;
>      > + ctx->len = 0;
>      > + }
>      > + }
>      > +}
>      > +
>      > +// TODO: make final reusable (remove side effects)
>      > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx
>     *ctx) {
>      > + uint32_t i = ctx->len;
>      > + if (i < 56) {
>      > + ctx->buffer[i++] = 0x80;
>      > + while (i < 56) {
>      > + ctx->buffer[i++] = 0x00;
>      > + }
>      > + } else {
>      > + ctx->buffer[i++] = 0x80;
>      > + while (i < 64) {
>      > + ctx->buffer[i++] = 0x00;
>      > + }
>      > + mg_sha256_chunk(ctx);
>      > + memset(ctx->buffer, 0, 56);
>      > + }
>      > +
>      > + ctx->bits += ctx->len * 8;
>      > + ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
>      > + ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
>      > + ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
>      > + ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
>      > + ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
>      > + ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
>      > + ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
>      > + ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
>      > + mg_sha256_chunk(ctx);
>      > +
>      > + for (i = 0; i < 4; ++i) {
>      > + digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
>      > + digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) &
>     0xff);
>      > + digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) &
>     0xff);
>      > + }
>      > +}
>      > +
>      > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
>     uint8_t *data,
>      > + size_t datasz) {
>      > + mg_sha256_ctx ctx;
>      > + uint8_t k[64] = {0};
>      > + uint8_t o_pad[64], i_pad[64];
>      > + unsigned int i;
>      > + memset(i_pad, 0x36, sizeof(i_pad));
>      > + memset(o_pad, 0x5c, sizeof(o_pad));
>      > + if (keysz < 64) {
>      > + if (keysz > 0) memmove(k, key, keysz);
>      > + } else {
>      > + mg_sha256_init(&ctx);
>      > + mg_sha256_update(&ctx, key, keysz);
>      > + mg_sha256_final(k, &ctx);
>      > + }
>      > + for (i = 0; i < sizeof(k); i++) {
>      > + i_pad[i] ^= k[i];
>      > + o_pad[i] ^= k[i];
>      > + }
>      > + mg_sha256_init(&ctx);
>      > + mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
>      > + mg_sha256_update(&ctx, data, datasz);
>      > + mg_sha256_final(dst, &ctx);
>      > + mg_sha256_init(&ctx);
>      > + mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
>      > + mg_sha256_update(&ctx, dst, 32);
>      > + mg_sha256_final(dst, &ctx);
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/sntp.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
>      > +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
>      > +
>      > +static int64_t gettimestamp(const uint32_t *data) {
>      > + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
>      > + if (sec) sec -= SNTP_TIME_OFFSET;
>      > + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
>     * 1000.0);
>      > +}
>      > +
>      > +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
>      > + int64_t res = -1;
>      > + int mode = len > 0 ? buf[0] & 7 : 0;
>      > + int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
>      > + if (len < 48) {
>      > + MG_ERROR(("%s", "corrupt packet"));
>      > + } else if (mode != 4 && mode != 5) {
>      > + MG_ERROR(("%s", "not a server reply"));
>      > + } else if (buf[1] == 0) {
>      > + MG_ERROR(("%s", "server sent a kiss of death"));
>      > + } else if (version == 4 || version == 3) {
>      > + // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
>      > + int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
>      > + int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
>      > + int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
>      > + int64_t t3 = (int64_t) mg_millis();
>      > + int64_t delta = (t3 - t0) - (t2 - t1);
>      > + MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
>     delta));
>      > + res = t2 + delta / 2;
>      > + } else {
>      > + MG_ERROR(("unexpected version: %d", version));
>      > + }
>      > + return res;
>      > +}
>      > +
>      > +static void sntp_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > + if (ev == MG_EV_READ) {
>      > + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
>      > + if (milliseconds > 0) {
>      > + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id,
>     milliseconds));
>      > + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
>      > + MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
>      > + (unsigned) (milliseconds % 1000)));
>      > + }
>      > + mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
>      > + } else if (ev == MG_EV_CONNECT) {
>      > + mg_sntp_request(c);
>      > + } else if (ev == MG_EV_CLOSE) {
>      > + }
>      > + (void) ev_data;
>      > +}
>      > +
>      > +void mg_sntp_request(struct mg_connection *c) {
>      > + if (c->is_resolving) {
>      > + MG_ERROR(("%lu wait until resolved", c->id));
>      > + } else {
>      > + int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
>      > + uint8_t buf[48] = {0};
>      > + uint32_t *t = (uint32_t *) &buf[40];
>      > + double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
>      > + buf[0] = (0 << 6) | (4 << 3) | 3;
>      > + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
>      > + t[1] = mg_htonl((uint32_t) frac);
>      > + mg_send(c, buf, sizeof(buf));
>      > + }
>      > +}
>      > +
>      > +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
>     char *url,
>      > + mg_event_handler_t fn, void *fnd) {
>      > + struct mg_connection *c = NULL;
>      > + if (url == NULL) url = "udp://time.google.com:123
>     <http://time.google.com:123>";
>      > + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
>      > + return c;
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/sock.c"
>      > +#endif
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +#if MG_ENABLE_SOCKET
>      > +
>      > +#ifndef closesocket
>      > +#define closesocket(x) close(x)
>      > +#endif
>      > +
>      > +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
>      > +#define S2PTR(s_) ((void *) (size_t) (s_))
>      > +
>      > +#ifndef MSG_NONBLOCKING
>      > +#define MSG_NONBLOCKING 0
>      > +#endif
>      > +
>      > +#ifndef AF_INET6
>      > +#define AF_INET6 10
>      > +#endif
>      > +
>      > +#ifndef MG_SOCK_ERR
>      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
>      > +#endif
>      > +
>      > +#ifndef MG_SOCK_INTR
>      > +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET &&
>     MG_SOCK_ERR(-1) == EINTR)
>      > +#endif
>      > +
>      > +#ifndef MG_SOCK_PENDING
>      > +#define MG_SOCK_PENDING(errcode) \
>      > + (((errcode) < 0) && (errno == EINPROGRESS || errno ==
>     EWOULDBLOCK))
>      > +#endif
>      > +
>      > +#ifndef MG_SOCK_RESET
>      > +#define MG_SOCK_RESET(errcode) \
>      > + (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
>      > +#endif
>      > +
>      > +union usa {
>      > + struct sockaddr sa;
>      > + struct sockaddr_in sin;
>      > +#if MG_ENABLE_IPV6
>      > + struct sockaddr_in6 sin6;
>      > +#endif
>      > +};
>      > +
>      > +static socklen_t tousa(struct mg_addr *a, union usa *usa) {
>      > + socklen_t len = sizeof(usa->sin);
>      > + memset(usa, 0, sizeof(*usa));
>      > + usa->sin.sin_family = AF_INET;
>      > + usa->sin.sin_port = a->port;
>      > + memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
>      > +#if MG_ENABLE_IPV6
>      > + if (a->is_ip6) {
>      > + usa->sin.sin_family = AF_INET6;
>      > + usa->sin6.sin6_port = a->port;
>      > + usa->sin6.sin6_scope_id = a->scope_id;
>      > + memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
>      > + len = sizeof(usa->sin6);
>      > + }
>      > +#endif
>      > + return len;
>      > +}
>      > +
>      > +static void tomgaddr(union usa *usa, struct mg_addr *a, bool
>     is_ip6) {
>      > + a->is_ip6 = is_ip6;
>      > + a->port = usa->sin.sin_port;
>      > + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
>      > +#if MG_ENABLE_IPV6
>      > + if (is_ip6) {
>      > + memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
>      > + a->port = usa->sin6.sin6_port;
>      > + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
>      > + }
>      > +#endif
>      > +}
>      > +
>      > +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
>      > + union usa usa;
>      > + socklen_t n = sizeof(usa);
>      > + if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
>      > + tomgaddr(&usa, addr, n != sizeof(usa.sin));
>      > + }
>      > +}
>      > +
>      > +static void iolog(struct mg_connection *c, char *buf, long n,
>     bool r) {
>      > + if (n == MG_IO_WAIT) {
>      > + // Do nothing
>      > + } else if (n <= 0) {
>      > + c->is_closing = 1; // Termination. Don't call mg_error(): #1529
>      > + } else if (n > 0) {
>      > + if (c->is_hexdumping) {
>      > + MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port,
>     &c->loc,
>      > + r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
>      > + mg_hexdump(buf, (size_t) n);
>      > + }
>      > + if (r) {
>      > + c->recv.len += (size_t) n;
>      > + mg_call(c, MG_EV_READ, &n);
>      > + } else {
>      > + mg_iobuf_del(&c->send, 0, (size_t) n);
>      > + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
>      > + if (c->send.len == 0) {
>      > + MG_EPOLL_MOD(c, 0);
>      > + }
>      > + mg_call(c, MG_EV_WRITE, &n);
>      > }
>      > }
>      > - return flags;
>      > }
>      >
>      > -static int uri_to_path(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - const struct mg_http_serve_opts *opts, char *path,
>      > - size_t path_size) {
>      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > - struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p =
>     {0, 0};
>      > - while (mg_commalist(&s, &k, &v)) {
>      > - if (v.len == 0) v = k, k = mg_str("/");
>      > - if (hm->uri.len < k.len) continue;
>      > - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
>      > - u = k, p = v;
>      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > + long n;
>      > + if (c->is_udp) {
>      > + union usa usa;
>      > + socklen_t slen = tousa(&c->rem, &usa);
>      > + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
>     <http://usa.sa>, slen);
>      > + if (n > 0) setlocaddr(FD(c), &c->loc);
>      > + } else {
>      > + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>      > }
>      > - return uri_to_path2(c, hm, fs, u, p, path, path_size);
>      > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
>      > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
>      > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > }
>      >
>      > -void mg_http_serve_dir(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - const struct mg_http_serve_opts *opts) {
>      > - char path[MG_PATH_MAX];
>      > - const char *sp = opts->ssi_pattern;
>      > - int flags = uri_to_path(c, hm, opts, path, sizeof(path));
>      > - if (flags < 0) {
>      > - // Do nothing: the response has already been sent by uri_to_path()
>      > - } else if (flags & MG_FS_DIR) {
>      > - listdir(c, hm, opts, path);
>      > - } else if (flags && sp != NULL &&
>      > - mg_globmatch(sp, strlen(sp), path, strlen(path))) {
>      > - mg_http_serve_ssi(c, opts->root_dir, path);
>      > +bool mg_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > + if (c->is_udp) {
>      > + long n = mg_io_send(c, buf, len);
>      > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
>     c->send.len,
>      > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
>      > + iolog(c, (char *) buf, n, false);
>      > + return n > 0;
>      > } else {
>      > - mg_http_serve_file(c, hm, path, opts);
>      > + return mg_iobuf_add(&c->send, c->send.len, buf, len);
>      > }
>      > }
>      >
>      > -static bool mg_is_url_safe(int c) {
>      > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
>      > - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
>     == '~';
>      > +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
>      > +#if defined(MG_CUSTOM_NONBLOCK)
>      > + MG_CUSTOM_NONBLOCK(fd);
>      > +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > + unsigned long on = 1;
>      > + ioctlsocket(fd, FIONBIO, &on);
>      > +#elif MG_ENABLE_RL
>      > + unsigned long on = 1;
>      > + ioctlsocket(fd, FIONBIO, &on);
>      > +#elif MG_ENABLE_FREERTOS_TCP
>      > + const BaseType_t off = 0;
>      > + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
>     != 0) (void) 0;
>      > + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
>     != 0) (void) 0;
>      > +#elif MG_ENABLE_LWIP
>      > + lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
>      > +#elif MG_ARCH == MG_ARCH_AZURERTOS
>      > + fcntl(fd, F_SETFL, O_NONBLOCK);
>      > +#elif MG_ARCH == MG_ARCH_TIRTOS
>      > + int val = 0;
>      > + setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
>      > + // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
>      > + int sz = sizeof(val);
>      > + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
>      > + val /= 2; // set send low-water mark at half send buffer size
>      > + setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
>      > +#else
>      > + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
>     Non-blocking mode
>      > + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
>      > +#endif
>      > }
>      >
>      > -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
>     len) {
>      > - size_t i, n = 0;
>      > - for (i = 0; i < sl; i++) {
>      > - int c = *(unsigned char *) &s[i];
>      > - if (n + 4 >= len) return 0;
>      > - if (mg_is_url_safe(c)) {
>      > - buf[n++] = s[i];
>      > +bool mg_open_listener(struct mg_connection *c, const char *url) {
>      > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>      > + bool success = false;
>      > + c->loc.port = mg_htons(mg_url_port(url));
>      > + if (!mg_aton(mg_url_host(url), &c->loc)) {
>      > + MG_ERROR(("invalid listening URL: %s", url));
>      > + } else {
>      > + union usa usa;
>      > + socklen_t slen = tousa(&c->loc, &usa);
>      > + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
>      > + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
>     SOCK_STREAM;
>      > + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
>      > + (void) on;
>      > +
>      > + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
>      > + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
>      > +#if defined(SO_EXCLUSIVEADDRUSE)
>      > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
>      > + (char *) &on, sizeof(on))) != 0) {
>      > + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
>      > + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on,
>     MG_SOCK_ERR(rc)));
>      > +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
>      > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char
>     *) &on,
>      > + sizeof(on))) != 0) {
>      > + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On
>      > + // Windows, SO_REUSEADDR allows to bind a socket to a port
>     without error
>      > + // even if the port is already open by another program. This is
>     not the
>      > + // behavior SO_REUSEADDR was designed for, and leads to
>     hard-to-track
>      > + // failure scenarios.
>      > + //
>      > + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by
>     defining
>      > + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will
>     compile but
>      > + // won't work! (setsockopt will return EINVAL)
>      > + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
>      > +#endif
>      > +#if MG_IPV6_V6ONLY
>      > + // Bind only to the V6 address, not V4 address on this port
>      > + } else if (c->loc.is_ip6 &&
>      > + (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
>      > + sizeof(on))) != 0) {
>      > + // See #2089. Allow to bind v4 and v6 sockets on the same port
>      > + MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
>      > +#endif
>      > + } else if ((rc = bind(fd, &usa.sa <http://usa.sa>, slen)) != 0) {
>      > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
>      > + } else if ((type == SOCK_STREAM &&
>      > + (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
>      > + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
>      > + // In case port was set to 0, get the real port number
>      > + MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
>      > } else {
>      > - buf[n++] = '%';
>      > - mg_hex(&s[i], 1, &buf[n]);
>      > - n += 2;
>      > + setlocaddr(fd, &c->loc);
>      > + mg_set_non_blocking_mode(fd);
>      > + c->fd = S2PTR(fd);
>      > + MG_EPOLL_ADD(c);
>      > + success = true;
>      > }
>      > }
>      > - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
>     the destination
>      > - if (len > 0) buf[len - 1] = '\0'; // Always.
>      > - return n;
>      > + if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
>      > + return success;
>      > }
>      >
>      > -void mg_http_creds(struct mg_http_message *hm, char *user,
>     size_t userlen,
>      > - char *pass, size_t passlen) {
>      > - struct mg_str *v = mg_http_get_header(hm, "Authorization");
>      > - user[0] = pass[0] = '\0';
>      > - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
>      > - char buf[256];
>      > - int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
>      > - const char *p = (const char *) memchr(buf, ':', n > 0 ?
>     (size_t) n : 0);
>      > - if (p != NULL) {
>      > - mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
>      > - mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p +
>     1);
>      > - }
>      > - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ",
>     7) == 0) {
>      > - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
>      > - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
>      > - struct mg_str t = mg_http_get_header_var(*v,
>     mg_str_n("access_token", 12));
>      > - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
>     t.ptr);
>      > +static long recv_raw(struct mg_connection *c, void *buf, size_t
>     len) {
>      > + long n = 0;
>      > + if (c->is_udp) {
>      > + union usa usa;
>      > + socklen_t slen = tousa(&c->rem, &usa);
>      > + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
>     <http://usa.sa>, &slen);
>      > + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
>      > } else {
>      > - mg_http_get_var(&hm->query, "access_token", pass, passlen);
>      > + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>      > }
>      > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
>      > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
>      > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > }
>      >
>      > -static struct mg_str stripquotes(struct mg_str s) {
>      > - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
>      > - ? mg_str_n(s.ptr + 1, s.len - 2)
>      > - : s;
>      > +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
>      > + bool res = false;
>      > + if (io->len >= MG_MAX_RECV_SIZE) {
>      > + mg_error(c, "MG_MAX_RECV_SIZE");
>      > + } else if (io->size <= io->len &&
>      > + !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
>      > + mg_error(c, "OOM");
>      > + } else {
>      > + res = true;
>      > + }
>      > + return res;
>      > }
>      >
>      > -struct mg_str mg_http_get_header_var(struct mg_str s, struct
>     mg_str v) {
>      > - size_t i;
>      > - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
>      > - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len)
>     == 0) {
>      > - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
>      > - int q = p < x && *p == '"' ? 1 : 0;
>      > - while (p < x &&
>      > - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
>      > - p++;
>      > - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int)
>     v.len,
>      > - // v.ptr, (int) (p - b), b));
>      > - return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
>      > +// NOTE(lsm): do only one iteration of reads, cause some systems
>      > +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
>     no data
>      > +static void read_conn(struct mg_connection *c) {
>      > + if (ioalloc(c, &c->recv)) {
>      > + char *buf = (char *) &c->recv.buf[c->recv.len];
>      > + size_t len = c->recv.size - c->recv.len;
>      > + long n = -1;
>      > + if (c->is_tls) {
>      > + if (!ioalloc(c, &c->rtls)) return;
>      > + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
>      > + c->rtls.size - c->rtls.len);
>      > + if (n == MG_IO_ERR && c->rtls.len == 0) {
>      > + // Close only if we have fully drained both raw (rtls) and TLS
>     buffers
>      > + c->is_closing = 1;
>      > + } else {
>      > + if (n > 0) c->rtls.len += (size_t) n;
>      > + if (c->is_tls_hs) mg_tls_handshake(c);
>      > + n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
>      > + }
>      > + } else {
>      > + n = recv_raw(c, buf, len);
>      > }
>      > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
>     c->send.len,
>      > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
>      > + iolog(c, buf, n, true);
>      > }
>      > - return mg_str_n(NULL, 0);
>      > }
>      >
>      > -bool mg_http_match_uri(const struct mg_http_message *hm, const
>     char *glob) {
>      > - return mg_match(hm->uri, mg_str(glob), NULL);
>      > +static void write_conn(struct mg_connection *c) {
>      > + char *buf = (char *) c->send.buf;
>      > + size_t len = c->send.len;
>      > + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
>     buf, len);
>      > + MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d",
>     c->id, c->fd,
>      > + (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>      > + (long) c->recv.size, n, MG_SOCK_ERR(n)));
>      > + iolog(c, buf, n, false);
>      > }
>      >
>      > -long mg_http_upload(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - struct mg_fs *fs, const char *path, size_t max_size) {
>      > - char buf[20] = "0";
>      > - long res = 0, offset;
>      > - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
>      > - offset = strtol(buf, NULL, 0);
>      > - if (hm->body.len == 0) {
>      > - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
>      > +static void close_conn(struct mg_connection *c) {
>      > + if (FD(c) != MG_INVALID_SOCKET) {
>      > +#if MG_ENABLE_EPOLL
>      > + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
>      > +#endif
>      > + closesocket(FD(c));
>      > +#if MG_ENABLE_FREERTOS_TCP
>      > + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
>      > +#endif
>      > + }
>      > + mg_close_conn(c);
>      > +}
>      > +
>      > +static void connect_conn(struct mg_connection *c) {
>      > + union usa usa;
>      > + socklen_t n = sizeof(usa);
>      > + // Use getpeername() to test whether we have connected
>      > + if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
>      > + c->is_connecting = 0;
>      > + setlocaddr(FD(c), &c->loc);
>      > + mg_call(c, MG_EV_CONNECT, NULL);
>      > + MG_EPOLL_MOD(c, 0);
>      > + if (c->is_tls_hs) mg_tls_handshake(c);
>      > } else {
>      > - struct mg_fd *fd;
>      > - size_t current_size = 0;
>      > - MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len,
>     offset));
>      > - if (offset == 0) fs->rm(path); // If offset if 0, truncate file
>      > - fs->st(path, &current_size, NULL);
>      > - if (offset < 0) {
>      > - mg_http_reply(c, 400, "", "offset required");
>      > - res = -1;
>      > - } else if (offset > 0 && current_size != (size_t) offset) {
>      > - mg_http_reply(c, 400, "", "%s: offset mismatch", path);
>      > - res = -2;
>      > - } else if ((size_t) offset + hm->body.len > max_size) {
>      > - mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
>      > - (unsigned long) max_size);
>      > - res = -3;
>      > - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
>      > - mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
>      > - res = -4;
>      > + mg_error(c, "socket error");
>      > + }
>      > +}
>      > +
>      > +static void setsockopts(struct mg_connection *c) {
>      > +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
>      > + MG_ARCH == MG_ARCH_TIRTOS
>      > + (void) c;
>      > +#else
>      > + int on = 1;
>      > +#if !defined(SOL_TCP)
>      > +#define SOL_TCP IPPROTO_TCP
>      > +#endif
>      > + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
>     sizeof(on)) != 0)
>      > + (void) 0;
>      > + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
>     sizeof(on)) !=
>      > + 0)
>      > + (void) 0;
>      > +#endif
>      > +}
>      > +
>      > +void mg_connect_resolved(struct mg_connection *c) {
>      > + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
>      > + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
>     resolved IP
>      > + c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
>      > + c->is_resolving = 0; // Clear resolving flag
>      > + if (FD(c) == MG_INVALID_SOCKET) {
>      > + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
>      > + } else if (c->is_udp) {
>      > + MG_EPOLL_ADD(c);
>      > +#if MG_ARCH == MG_ARCH_TIRTOS
>      > + union usa usa; // TI-RTOS NDK requires binding to receive on
>     UDP sockets
>      > + socklen_t slen = tousa(&c->loc, &usa);
>      > + if ((rc = bind(c->fd, &usa.sa <http://usa.sa>, slen)) != 0)
>      > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
>      > +#endif
>      > + setlocaddr(FD(c), &c->loc);
>      > + mg_call(c, MG_EV_RESOLVE, NULL);
>      > + mg_call(c, MG_EV_CONNECT, NULL);
>      > + } else {
>      > + union usa usa;
>      > + socklen_t slen = tousa(&c->rem, &usa);
>      > + mg_set_non_blocking_mode(FD(c));
>      > + setsockopts(c);
>      > + MG_EPOLL_ADD(c);
>      > + mg_call(c, MG_EV_RESOLVE, NULL);
>      > + rc = connect(FD(c), &usa.sa <http://usa.sa>, slen); // Attempt
>     to connect
>      > + if (rc == 0) { // Success
>      > + setlocaddr(FD(c), &c->loc);
>      > + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user
>      > + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP
>     handshake
>      > + MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port,
>     &c->rem));
>      > + c->is_connecting = 1;
>      > } else {
>      > - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
>      > - mg_fs_close(fd);
>      > - mg_http_reply(c, 200, "", "%ld", res);
>      > + mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
>      > }
>      > }
>      > - return res;
>      > -}
>      > -
>      > -int mg_http_status(const struct mg_http_message *hm) {
>      > - return atoi(hm->uri.ptr);
>      > -}
>      > -
>      > -// If a server sends data to the client using chunked encoding,
>     Mongoose strips
>      > -// off the chunking prefix (hex length and \r\n) and suffix
>     (\r\n), appends the
>      > -// stripped data to the body, and fires the MG_EV_HTTP_CHUNK
>     event. When zero
>      > -// chunk is received, we fire MG_EV_HTTP_MSG, and the body
>     already has all
>      > -// chunking prefixes/suffixes stripped.
>      > -//
>      > -// If a server sends data without chunked encoding, we also fire
>     a series of
>      > -// MG_EV_HTTP_CHUNK events for every received piece of data, and
>     then we fire
>      > -// MG_EV_HTTP_MSG event in the end.
>      > -//
>      > -// We track total processed length in the c->pfn_data, which is
>     a void *
>      > -// pointer: we store a size_t value there.
>      > -static bool getchunk(struct mg_str s, size_t *prefixlen, size_t
>     *datalen) {
>      > - size_t i = 0, n;
>      > - while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
>      > - n = mg_unhexn(s.ptr, i);
>      > - // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
>      > - if (s.len < i + n + 4) return false; // Chunk not yet fully
>     buffered
>      > - if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
>      > - if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n')
>     return false;
>      > - *prefixlen = i + 2;
>      > - *datalen = n;
>      > - return true;
>      > }
>      >
>      > -static bool mg_is_chunked(struct mg_http_message *hm) {
>      > - const char *needle = "chunked";
>      > - struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
>      > - return te != NULL && mg_vcasecmp(te, needle) == 0;
>      > -}
>      > -
>      > -void mg_http_delete_chunk(struct mg_connection *c, struct
>     mg_http_message *hm) {
>      > - size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
>      > - mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
>      > - c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
>      > -}
>      > -
>      > -static void deliver_chunked_chunks(struct mg_connection *c,
>     size_t hlen,
>      > - struct mg_http_message *hm, bool *next) {
>      > - // | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
>      > - // +------------------+--------------------------+----
>      > - // | hlen | chunk1 | ......
>      > - char *buf = (char *) &c->recv.buf[hlen], *p = buf;
>      > - size_t len = c->recv.len - hlen;
>      > - size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
>      > - size_t mark, pl, dl, del = 0, ofs = 0;
>      > - bool last = false;
>      > - if (processed <= len) len -= processed, buf += processed;
>      > - while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl,
>     &dl)) {
>      > - size_t saved = c->recv.len;
>      > - memmove(p + processed, buf + ofs + pl, dl);
>      > - // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
>      > - hm->chunk = mg_str_n(p + processed, dl);
>      > - mg_call(c, MG_EV_HTTP_CHUNK, hm);
>      > - ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
>      > - processed += dl;
>      > - if (c->recv.len != saved) processed -= dl, buf -= dl;
>      > - // mg_hexdump(c->recv.buf, hlen + processed);
>      > - last = (dl == 0);
>      > - }
>      > - mg_iobuf_del(&c->recv, hlen + processed, del);
>      > - mark = ((size_t) c->pfn_data) & MG_DMARK;
>      > - c->pfn_data = (void *) (processed | mark);
>      > - if (last) {
>      > - hm->body.len = processed;
>      > - hm->message.len = hlen + processed;
>      > - c->pfn_data = NULL;
>      > - if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
>      > - // MG_INFO(("LAST, mark: %lx", mark));
>      > - // mg_hexdump(c->recv.buf, c->recv.len);
>      > - }
>      > +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
>      > + socklen_t *len) {
>      > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>      > + do {
>      > + memset(usa, 0, sizeof(*usa));
>      > + fd = accept(sock, &usa->sa, len);
>      > + } while (MG_SOCK_INTR(fd));
>      > + return fd;
>      > }
>      >
>      > -static void deliver_normal_chunks(struct mg_connection *c,
>     size_t hlen,
>      > - struct mg_http_message *hm, bool *next) {
>      > - size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
>      > - size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
>      > - hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len -
>     hlen);
>      > - if (processed <= hm->chunk.len && !deleted) {
>      > - hm->chunk.len -= processed;
>      > - hm->chunk.ptr += processed;
>      > - }
>      > - left = hm->body.len < processed ? 0 : hm->body.len - processed;
>      > - if (hm->chunk.len > left) hm->chunk.len = left;
>      > - if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
>      > - processed += hm->chunk.len;
>      > - deleted = ((size_t) c->pfn_data) & MG_DMARK; // Re-evaluate
>     after user call
>      > - if (processed >= hm->body.len) { // Last, 0-len chunk
>      > - hm->chunk.len = 0; // Reset length
>      > - mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
>      > - c->pfn_data = NULL; // Reset processed counter
>      > - if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen),
>     *next = true;
>      > +static void accept_conn(struct mg_mgr *mgr, struct mg_connection
>     *lsn) {
>      > + struct mg_connection *c = NULL;
>      > + union usa usa;
>      > + socklen_t sa_len = sizeof(usa);
>      > + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
>      > + if (fd == MG_INVALID_SOCKET) {
>      > +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
>      > + // AzureRTOS, in non-block socket mode can mark listening
>     socket readable
>      > + // even it is not. See comment for 'select' func implementation in
>      > + // nx_bsd.c That's not an error, just should try later
>      > + if (errno != EAGAIN)
>      > +#endif
>      > + MG_ERROR(("%lu accept failed, errno %d", lsn->id,
>     MG_SOCK_ERR(-1)));
>      > +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
>      > + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
>      > + } else if ((long) fd >= FD_SETSIZE) {
>      > + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
>      > + closesocket(fd);
>      > +#endif
>      > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > + MG_ERROR(("%lu OOM", lsn->id));
>      > + closesocket(fd);
>      > } else {
>      > - c->pfn_data = (void *) (processed | deleted); // if it is set
>      > + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
>      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > + c->fd = S2PTR(fd);
>      > + MG_EPOLL_ADD(c);
>      > + mg_set_non_blocking_mode(FD(c));
>      > + setsockopts(c);
>      > + c->is_accepted = 1;
>      > + c->is_hexdumping = lsn->is_hexdumping;
>      > + c->loc = lsn->loc;
>      > + c->pfn = lsn->pfn;
>      > + c->pfn_data = lsn->pfn_data;
>      > + c->fn = lsn->fn;
>      > + c->fn_data = lsn->fn_data;
>      > + MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd,
>     mg_print_ip_port,
>      > + &c->rem, mg_print_ip_port, &c->loc));
>      > + mg_call(c, MG_EV_OPEN, NULL);
>      > + mg_call(c, MG_EV_ACCEPT, NULL);
>      > }
>      > }
>      >
>      > -static void http_cb(struct mg_connection *c, int ev, void *evd,
>     void *fnd) {
>      > - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
>      > - struct mg_http_message hm;
>      > - // mg_hexdump(c->recv.buf, c->recv.len);
>      > - while (c->recv.buf != NULL && c->recv.len > 0) {
>      > - bool next = false;
>      > - int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
>      > - if (hlen < 0) {
>      > - mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
>      > - break;
>      > - }
>      > - if (c->is_resp) break; // Response is still generated
>      > - if (hlen == 0) break; // Request is not buffered yet
>      > - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
>      > - hm.message.len = c->recv.len; // and closes now, deliver a MSG
>      > - hm.body.len = hm.message.len - (size_t) (hm.body.ptr -
>     hm.message.ptr);
>      > - }
>      > - if (mg_is_chunked(&hm)) {
>      > - deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
>      > - } else {
>      > - deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
>      > - }
>      > - if (next) continue; // Chunks & request were deleted
>      > - // Chunk events are delivered. If we have full body, deliver MSG
>      > - if (c->recv.len < hm.message.len) break;
>      > - if (c->is_accepted) c->is_resp = 1; // Start generating response
>      > - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
>      > - mg_iobuf_del(&c->recv, 0, hm.message.len);
>      > - }
>      > - }
>      > - (void) evd, (void) fnd;
>      > +static bool can_read(const struct mg_connection *c) {
>      > + return c->is_full == false;
>      > }
>      >
>      > -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
>     char *url,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>      > - if (c != NULL) c->pfn = http_cb;
>      > - return c;
>      > +static bool can_write(const struct mg_connection *c) {
>      > + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
>      > }
>      >
>      > -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
>     char *url,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>      > - if (c != NULL) c->pfn = http_cb;
>      > - return c;
>      > +static bool skip_iotest(const struct mg_connection *c) {
>      > + return (c->is_closing || c->is_resolving || FD(c) ==
>     MG_INVALID_SOCKET) ||
>      > + (can_read(c) == false && can_write(c) == false);
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/iobuf.c"
>      > -#endif
>      > -
>      > -
>      > -
>      > -
>      > -// Not using memset for zeroing memory, cause it can be dropped
>     by compiler
>      > -// See https://github.com/cesanta/mongoose/pull/1265
>     <https://github.com/cesanta/mongoose/pull/1265>
>      > -static void zeromem(volatile unsigned char *buf, size_t len) {
>      > - if (buf != NULL) {
>      > - while (len--) *buf++ = 0;
>      > +static void mg_iotest(struct mg_mgr *mgr, int ms) {
>      > +#if MG_ENABLE_FREERTOS_TCP
>      > + struct mg_connection *c;
>      > + for (c = mgr->conns; c != NULL; c = c->next) {
>      > + c->is_readable = c->is_writable = 0;
>      > + if (skip_iotest(c)) continue;
>      > + if (can_read(c))
>      > + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
>      > + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
>      > + if (c->is_closing) ms = 1;
>      > }
>      > -}
>      > -
>      > -static size_t roundup(size_t size, size_t align) {
>      > - return align == 0 ? size : (size + align - 1) / align * align;
>      > -}
>      > -
>      > -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
>      > - int ok = 1;
>      > - new_size = roundup(new_size, io->align);
>      > - if (new_size == 0) {
>      > - zeromem(io->buf, io->size);
>      > - free(io->buf);
>      > - io->buf = NULL;
>      > - io->len = io->size = 0;
>      > - } else if (new_size != io->size) {
>      > - // NOTE(lsm): do not use realloc here. Use calloc/free only, to
>     ease the
>      > - // porting to some obscure platforms like FreeRTOS
>      > - void *p = calloc(1, new_size);
>      > - if (p != NULL) {
>      > - size_t len = new_size < io->len ? new_size : io->len;
>      > - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
>      > - zeromem(io->buf, io->size);
>      > - free(io->buf);
>      > - io->buf = (unsigned char *) p;
>      > - io->size = new_size;
>      > + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
>      > + for (c = mgr->conns; c != NULL; c = c->next) {
>      > + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
>      > + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
>      > + c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
>      > + if (c->fd != MG_INVALID_SOCKET)
>      > + FreeRTOS_FD_CLR(c->fd, mgr->ss,
>      > + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
>      > + }
>      > +#elif MG_ENABLE_EPOLL
>      > + size_t max = 1;
>      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > + c->is_readable = c->is_writable = 0;
>      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1,
>     c->is_readable = 1;
>      > + if (can_write(c)) MG_EPOLL_MOD(c, 1);
>      > + if (c->is_closing) ms = 1;
>      > + max++;
>      > + }
>      > + struct epoll_event *evs = (struct epoll_event *) alloca(max *
>     sizeof(evs[0]));
>      > + int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
>      > + for (int i = 0; i < n; i++) {
>      > + struct mg_connection *c = (struct mg_connection *)
>     evs[i].data.ptr;
>      > + if (evs[i].events & EPOLLERR) {
>      > + mg_error(c, "socket error");
>      > + } else if (c->is_readable == 0) {
>      > + bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
>      > + bool wr = evs[i].events & EPOLLOUT;
>      > + c->is_readable = can_read(c) && rd ? 1U : 0;
>      > + c->is_writable = can_write(c) && wr ? 1U : 0;
>      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>      > + }
>      > + }
>      > + (void) skip_iotest;
>      > +#elif MG_ENABLE_POLL
>      > + nfds_t n = 0;
>      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) n++;
>      > + struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
>      > + memset(fds, 0, n * sizeof(fds[0]));
>      > + n = 0;
>      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > + c->is_readable = c->is_writable = 0;
>      > + if (skip_iotest(c)) {
>      > + // Socket not valid, ignore
>      > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
>      > + ms = 1; // Don't wait if TLS is ready
>      > } else {
>      > - ok = 0;
>      > - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
>     new_size));
>      > + fds[n].fd = FD(c);
>      > + if (can_read(c)) fds[n].events |= POLLIN;
>      > + if (can_write(c)) fds[n].events |= POLLOUT;
>      > + if (c->is_closing) ms = 1;
>      > + n++;
>      > }
>      > }
>      > - return ok;
>      > -}
>      > -
>      > -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
>      > - io->buf = NULL;
>      > - io->align = align;
>      > - io->size = io->len = 0;
>      > - return mg_iobuf_resize(io, size);
>      > -}
>      >
>      > -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
>     *buf,
>      > - size_t len) {
>      > - size_t new_size = roundup(io->len + len, io->align);
>      > - mg_iobuf_resize(io, new_size); // Attempt to resize
>      > - if (new_size != io->size) len = 0; // Resize failure, append
>     nothing
>      > - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
>     io->len - ofs);
>      > - if (buf != NULL) memmove(io->buf + ofs, buf, len);
>      > - if (ofs > io->len) io->len += ofs - io->len;
>      > - io->len += len;
>      > - return len;
>      > -}
>      > + // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
>      > + if (poll(fds, n, ms) < 0) {
>      > +#if MG_ARCH == MG_ARCH_WIN32
>      > + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
>      > +#endif
>      > + memset(fds, 0, n * sizeof(fds[0]));
>      > + }
>      > + n = 0;
>      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > + if (skip_iotest(c)) {
>      > + // Socket not valid, ignore
>      > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
>      > + c->is_readable = 1;
>      > + } else {
>      > + if (fds[n].revents & POLLERR) {
>      > + mg_error(c, "socket error");
>      > + } else {
>      > + c->is_readable =
>      > + (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
>      > + c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
>      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>      > + }
>      > + n++;
>      > + }
>      > + }
>      > +#else
>      > + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
>     {0, 0}, *tvp;
>      > + struct mg_connection *c;
>      > + fd_set rset, wset, eset;
>      > + MG_SOCKET_TYPE maxfd = 0;
>      > + int rc;
>      >
>      > -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
>      > - if (ofs > io->len) ofs = io->len;
>      > - if (ofs + len > io->len) len = io->len - ofs;
>      > - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
>     io->len - ofs - len);
>      > - if (io->buf) zeromem(io->buf + io->len - len, len);
>      > - io->len -= len;
>      > - return len;
>      > -}
>      > + FD_ZERO(&rset);
>      > + FD_ZERO(&wset);
>      > + FD_ZERO(&eset);
>      > + tvp = ms < 0 ? NULL : &tv;
>      > + for (c = mgr->conns; c != NULL; c = c->next) {
>      > + c->is_readable = c->is_writable = 0;
>      > + if (skip_iotest(c)) continue;
>      > + FD_SET(FD(c), &eset);
>      > + if (can_read(c)) FD_SET(FD(c), &rset);
>      > + if (can_write(c)) FD_SET(FD(c), &wset);
>      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
>      > + if (FD(c) > maxfd) maxfd = FD(c);
>      > + if (c->is_closing) ms = 1;
>      > + }
>      >
>      > -void mg_iobuf_free(struct mg_iobuf *io) {
>      > - mg_iobuf_resize(io, 0);
>      > -}
>      > + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) <
>     0) {
>      > +#if MG_ARCH == MG_ARCH_WIN32
>      > + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
>     sockets
>      > +#else
>      > + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
>      > +#endif
>      > + FD_ZERO(&rset);
>      > + FD_ZERO(&wset);
>      > + FD_ZERO(&eset);
>      > + }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/json.c"
>      > + for (c = mgr->conns; c != NULL; c = c->next) {
>      > + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
>      > + mg_error(c, "socket error");
>      > + } else {
>      > + c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>     &rset);
>      > + c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>     &wset);
>      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>      > + }
>      > + }
>      > #endif
>      > +}
>      >
>      > +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
>      > + socklen_t n = sizeof(usa[0].sin);
>      > + bool success = false;
>      >
>      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > + (void) memset(&usa[0], 0, sizeof(usa[0]));
>      > + usa[0].sin.sin_family = AF_INET;
>      > + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
>     127.0.0.1
>      > + usa[1] = usa[0];
>      >
>      > -
>      > -static const char *escapeseq(int esc) {
>      > - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
>      > + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
>     MG_INVALID_SOCKET &&
>      > + (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
>      > + bind(sp[0], &usa[0].sa, n) == 0 && //
>      > + bind(sp[1], &usa[1].sa, n) == 0 && //
>      > + getsockname(sp[0], &usa[0].sa, &n) == 0 && //
>      > + getsockname(sp[1], &usa[1].sa, &n) == 0 && //
>      > + connect(sp[0], &usa[1].sa, n) == 0 && //
>      > + connect(sp[1], &usa[0].sa, n) == 0) { //
>      > + success = true;
>      > + }
>      > + if (!success) {
>      > + if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
>      > + if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
>      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > + }
>      > + return success;
>      > }
>      >
>      > -static char json_esc(int c, int esc) {
>      > - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
>      > - for (p = esc1; *p != '\0'; p++) {
>      > - if (*p == c) return esc2[p - esc1];
>      > +// mg_wakeup() event handler
>      > +static void wufn(struct mg_connection *c, int ev, void *ev_data) {
>      > + if (ev == MG_EV_READ) {
>      > + unsigned long *id = (unsigned long *) c->recv.buf;
>      > + // MG_INFO(("Got data"));
>      > + // mg_hexdump(c->recv.buf, c->recv.len);
>      > + if (c->recv.len >= sizeof(*id)) {
>      > + struct mg_connection *t;
>      > + for (t = c->mgr->conns; t != NULL; t = t->next) {
>      > + if (t->id == *id) {
>      > + struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
>      > + c->recv.len - sizeof(*id));
>      > + mg_call(t, MG_EV_WAKEUP, &data);
>      > + }
>      > + }
>      > + }
>      > + c->recv.len = 0; // Consume received data
>      > + } else if (ev == MG_EV_CLOSE) {
>      > + closesocket(c->mgr->pipe); // When we're closing, close the other
>      > + c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too
>      > }
>      > - return 0;
>      > + (void) ev_data;
>      > }
>      >
>      > -static int mg_pass_string(const char *s, int len) {
>      > - int i;
>      > - for (i = 0; i < len; i++) {
>      > - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
>      > - i++;
>      > - } else if (s[i] == '\0') {
>      > - return MG_JSON_INVALID;
>      > - } else if (s[i] == '"') {
>      > - return i;
>      > +bool mg_wakeup_init(struct mg_mgr *mgr) {
>      > + bool ok = false;
>      > + if (mgr->pipe == MG_INVALID_SOCKET) {
>      > + union usa usa[2];
>      > + MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
>      > + struct mg_connection *c = NULL;
>      > + if (!mg_socketpair(sp, usa)) {
>      > + MG_ERROR(("Cannot create socket pair"));
>      > + } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) ==
>     NULL) {
>      > + closesocket(sp[0]);
>      > + closesocket(sp[1]);
>      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > + } else {
>      > + tomgaddr(&usa[0], &c->rem, false);
>      > + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
>     sp[0]));
>      > + mgr->pipe = sp[0];
>      > + ok = true;
>      > }
>      > }
>      > - return MG_JSON_INVALID;
>      > + return ok;
>      > }
>      >
>      > -static double mg_atod(const char *p, int len, int *numlen) {
>      > - double d = 0.0;
>      > - int i = 0, sign = 1;
>      > -
>      > - // Sign
>      > - if (i < len && *p == '-') {
>      > - sign = -1, i++;
>      > - } else if (i < len && *p == '+') {
>      > - i++;
>      > +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const
>     void *buf,
>      > + size_t len) {
>      > + if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
>      > + char *extended_buf = (char *) alloca(len + sizeof(conn_id));
>      > + memcpy(extended_buf, &conn_id, sizeof(conn_id));
>      > + memcpy(extended_buf + sizeof(conn_id), buf, len);
>      > + send(mgr->pipe, extended_buf, len + sizeof(conn_id),
>     MSG_NONBLOCKING);
>      > + return true;
>      > }
>      > + return false;
>      > +}
>      >
>      > - // Decimal
>      > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>      > - d *= 10.0;
>      > - d += p[i] - '0';
>      > - }
>      > - d *= sign;
>      > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>      > + struct mg_connection *c, *tmp;
>      > + uint64_t now;
>      >
>      > - // Fractional
>      > - if (i < len && p[i] == '.') {
>      > - double frac = 0.0, base = 0.1;
>      > - i++;
>      > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>      > - frac += base * (p[i] - '0');
>      > - base /= 10.0;
>      > + mg_iotest(mgr, ms);
>      > + now = mg_millis();
>      > + mg_timer_poll(&mgr->timers, now);
>      > +
>      > + for (c = mgr->conns; c != NULL; c = tmp) {
>      > + bool is_resp = c->is_resp;
>      > + tmp = c->next;
>      > + mg_call(c, MG_EV_POLL, &now);
>      > + if (is_resp && !c->is_resp) {
>      > + long n = 0;
>      > + mg_call(c, MG_EV_READ, &n);
>      > + }
>      > + MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
>      > + c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
>      > + c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
>      > + c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
>      > + c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
>      > + if (c->is_resolving || c->is_closing) {
>      > + // Do nothing
>      > + } else if (c->is_listening && c->is_udp == 0) {
>      > + if (c->is_readable) accept_conn(mgr, c);
>      > + } else if (c->is_connecting) {
>      > + if (c->is_readable || c->is_writable) connect_conn(c);
>      > + //} else if (c->is_tls_hs) {
>      > + // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
>      > + } else {
>      > + if (c->is_readable) read_conn(c);
>      > + if (c->is_writable) write_conn(c);
>      > }
>      > - d += frac * sign;
>      > - }
>      >
>      > - // Exponential
>      > - if (i < len && (p[i] == 'e' || p[i] == 'E')) {
>      > - int j, exp = 0, minus = 0;
>      > - i++;
>      > - if (i < len && p[i] == '-') minus = 1, i++;
>      > - if (i < len && p[i] == '+') i++;
>      > - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
>      > - exp = exp * 10 + (p[i++] - '0');
>      > - if (minus) exp = -exp;
>      > - for (j = 0; j < exp; j++) d *= 10.0;
>      > - for (j = 0; j < -exp; j++) d /= 10.0;
>      > + if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>      > + if (c->is_closing) close_conn(c);
>      > }
>      > -
>      > - if (numlen != NULL) *numlen = i;
>      > - return d;
>      > }
>      > +#endif
>      >
>      > -int mg_json_get(struct mg_str json, const char *path, int
>     *toklen) {
>      > - const char *s = json.ptr;
>      > - int len = (int) json.len;
>      > - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
>     S_VALUE;
>      > - unsigned char nesting[MG_JSON_MAX_DEPTH];
>      > - int i = 0; // Current offset in `s`
>      > - int j = 0; // Offset in `s` we're looking for (return value)
>      > - int depth = 0; // Current depth (nesting level)
>      > - int ed = 0; // Expected depth
>      > - int pos = 1; // Current position in `path`
>      > - int ci = -1, ei = -1; // Current and expected index in array
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/ssi.c"
>      > +#endif
>      >
>      > - if (toklen) *toklen = 0;
>      > - if (path[0] != '$') return MG_JSON_INVALID;
>      >
>      > -#define MG_CHECKRET(x) \
>      > - do { \
>      > - if (depth == ed && path[pos] == '\0' && ci == ei) { \
>      > - if (toklen) *toklen = i - j + 1; \
>      > - return j; \
>      > - } \
>      > - } while (0)
>      >
>      > -// In the ascii table, the distance between `[` and `]` is 2.
>      > -// Ditto for `{` and `}`. Hence +2 in the code below.
>      > -#define MG_EOO(x) \
>      > - do { \
>      > - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
>      > - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
>      > - depth--; \
>      > - MG_CHECKRET(x); \
>      > - } while (0)
>      >
>      > - for (i = 0; i < len; i++) {
>      > - unsigned char c = ((unsigned char *) s)[i];
>      > - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
>      > - switch (expecting) {
>      > - case S_VALUE:
>      > - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
>     ci, ei);
>      > - if (depth == ed) j = i;
>      > - if (c == '{') {
>      > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>      > - if (depth == ed && path[pos] == '.' && ci == ei) {
>      > - // If we start the object, reset array indices
>      > - ed++, pos++, ci = ei = -1;
>      > - }
>      > - nesting[depth++] = c;
>      > - expecting = S_KEY;
>      > - break;
>      > - } else if (c == '[') {
>      > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>      > - if (depth == ed && path[pos] == '[' && ei == ci) {
>      > - ed++, pos++, ci = 0;
>      > - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
>      > - ei *= 10;
>      > - ei += path[pos] - '0';
>      > - }
>      > - if (path[pos] != 0) pos++;
>      > - }
>      > - nesting[depth++] = c;
>      > - break;
>      > - } else if (c == ']' && depth > 0) { // Empty array
>      > - MG_EOO(']');
>      > - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
>     == 0) {
>      > - i += 3;
>      > - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
>     == 0) {
>      > - i += 3;
>      > - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
>     == 0) {
>      > - i += 4;
>      > - } else if (c == '-' || ((c >= '0' && c <= '9'))) {
>      > - int numlen = 0;
>      > - mg_atod(&s[i], len - i, &numlen);
>      > - i += numlen - 1;
>      > - } else if (c == '"') {
>      > - int n = mg_pass_string(&s[i + 1], len - i - 1);
>      > - if (n < 0) return n;
>      > - i += n + 1;
>      > - } else {
>      > - return MG_JSON_INVALID;
>      > - }
>      > - MG_CHECKRET('V');
>      > - if (depth == ed && ei >= 0) ci++;
>      > - expecting = S_COMMA_OR_EOO;
>      > - break;
>      > +#ifndef MG_MAX_SSI_DEPTH
>      > +#define MG_MAX_SSI_DEPTH 5
>      > +#endif
>      >
>      > - case S_KEY:
>      > - if (c == '"') {
>      > - int n = mg_pass_string(&s[i + 1], len - i - 1);
>      > - if (n < 0) return n;
>      > - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
>      > - if (depth < ed) return MG_JSON_NOT_FOUND;
>      > - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
>      > - // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
>      > - // &s[i + 1], n, depth, ed);
>      > - // NOTE(cpq): in the check sequence below is important.
>      > - // strncmp() must go first: it fails fast if the remaining
>     length of
>      > - // the path is smaller than `n`.
>      > - if (depth == ed && path[pos - 1] == '.' &&
>      > - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
>      > - (path[pos + n] == '\0' || path[pos + n] == '.' ||
>      > - path[pos + n] == '[')) {
>      > - pos += n;
>      > - }
>      > - i += n + 1;
>      > - expecting = S_COLON;
>      > - } else if (c == '}') { // Empty object
>      > - MG_EOO('}');
>      > - expecting = S_COMMA_OR_EOO;
>      > - } else {
>      > - return MG_JSON_INVALID;
>      > - }
>      > - break;
>      > +#ifndef MG_SSI_BUFSIZ
>      > +#define MG_SSI_BUFSIZ 1024
>      > +#endif
>      >
>      > - case S_COLON:
>      > - if (c == ':') {
>      > - expecting = S_VALUE;
>      > +#if MG_ENABLE_SSI
>      > +static char *mg_ssi(const char *path, const char *root, int
>     depth) {
>      > + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
>      > + FILE *fp = fopen(path, "rb");
>      > + if (fp != NULL) {
>      > + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
>      > + int ch, intag = 0;
>      > + size_t len = 0;
>      > + buf[0] = arg[0] = '\0';
>      > + while ((ch = fgetc(fp)) != EOF) {
>      > + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
>     == '-') {
>      > + buf[len++] = (char) (ch & 0xff);
>      > + buf[len] = '\0';
>      > + if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
>      > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
>      > + *p = (char *) path + strlen(path), *data;
>      > + while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
>      > + mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
>     arg);
>      > + if (depth < MG_MAX_SSI_DEPTH &&
>      > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>      > + mg_iobuf_add(&b, b.len, data, strlen(data));
>      > + free(data);
>      > + } else {
>      > + MG_ERROR(("%s: file=%s error or too deep", path, arg));
>      > + }
>      > + } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
>      > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
>      > + mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
>      > + if (depth < MG_MAX_SSI_DEPTH &&
>      > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>      > + mg_iobuf_add(&b, b.len, data, strlen(data));
>      > + free(data);
>      > + } else {
>      > + MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
>      > + }
>      > } else {
>      > - return MG_JSON_INVALID;
>      > + // Unknown SSI tag
>      > + MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
>      > + mg_iobuf_add(&b, b.len, buf, len);
>      > }
>      > - break;
>      > -
>      > - case S_COMMA_OR_EOO:
>      > - if (depth <= 0) {
>      > - return MG_JSON_INVALID;
>      > - } else if (c == ',') {
>      > - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
>      > - } else if (c == ']' || c == '}') {
>      > - MG_EOO('O');
>      > - if (depth == ed && ei >= 0) ci++;
>      > - } else {
>      > - return MG_JSON_INVALID;
>      > + intag = 0;
>      > + len = 0;
>      > + } else if (ch == '<') {
>      > + intag = 1;
>      > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>      > + len = 0;
>      > + buf[len++] = (char) (ch & 0xff);
>      > + } else if (intag) {
>      > + if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
>      > + intag = 0;
>      > + } else if (len >= sizeof(buf) - 2) {
>      > + MG_ERROR(("%s: SSI tag is too large", path));
>      > + len = 0;
>      > }
>      > - break;
>      > + buf[len++] = (char) (ch & 0xff);
>      > + } else {
>      > + buf[len++] = (char) (ch & 0xff);
>      > + if (len >= sizeof(buf)) {
>      > + mg_iobuf_add(&b, b.len, buf, len);
>      > + len = 0;
>      > + }
>      > + }
>      > }
>      > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>      > + if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
>      > + fclose(fp);
>      > }
>      > - return MG_JSON_NOT_FOUND;
>      > + (void) depth;
>      > + (void) root;
>      > + return (char *) b.buf;
>      > }
>      >
>      > -bool mg_json_get_num(struct mg_str json, const char *path,
>     double *v) {
>      > - int n, toklen, found = 0;
>      > - if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
>      > - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <=
>     '9'))) {
>      > - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
>      > - found = 1;
>      > - }
>      > - return found;
>      > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>      > + const char *fullpath) {
>      > + const char *headers = "Content-Type: text/html;
>     charset=utf-8\r\n";
>      > + char *data = mg_ssi(fullpath, root, 0);
>      > + mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
>      > + free(data);
>      > +}
>      > +#else
>      > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>      > + const char *fullpath) {
>      > + mg_http_reply(c, 501, NULL, "SSI not enabled");
>      > + (void) root, (void) fullpath;
>      > }
>      > +#endif
>      >
>      > -bool mg_json_get_bool(struct mg_str json, const char *path, bool
>     *v) {
>      > - int found = 0, off = mg_json_get(json, path, NULL);
>      > - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
>      > - if (v != NULL) *v = json.ptr[off] == 't';
>      > - found = 1;
>      > - }
>      > - return found;
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/str.c"
>      > +#endif
>      > +
>      > +
>      > +struct mg_str mg_str_s(const char *s) {
>      > + struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
>      > + return str;
>      > }
>      >
>      > -static bool json_unescape(const char *s, size_t len, char *to,
>     size_t n) {
>      > - size_t i, j;
>      > - for (i = 0, j = 0; i < len && j < n; i++, j++) {
>      > - if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
>      > - // \uXXXX escape. We could process a simple one-byte chars
>      > - // \u00xx from the ASCII range. More complex chars would require
>      > - // dragging in a UTF8 library, which is too much for us
>      > - if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up
>      > - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i +
>     4, 2);
>      > +struct mg_str mg_str_n(const char *s, size_t n) {
>      > + struct mg_str str = {(char *) s, n};
>      > + return str;
>      > +}
>      >
>      > - i += 5;
>      > - } else if (s[i] == '\\' && i + 1 < len) {
>      > - char c = json_esc(s[i + 1], 0);
>      > - if (c == 0) return false;
>      > - to[j] = c;
>      > - i++;
>      > - } else {
>      > - to[j] = s[i];
>      > - }
>      > +static int mg_tolc(char c) {
>      > + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
>      > +}
>      > +
>      > +int mg_casecmp(const char *s1, const char *s2) {
>      > + int diff = 0;
>      > + do {
>      > + int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
>      > + diff = c - d;
>      > + } while (diff == 0 && s1[-1] != '\0');
>      > + return diff;
>      > +}
>      > +
>      > +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
>      > + size_t i = 0;
>      > + while (i < str1.len && i < str2.len) {
>      > + int c1 = str1.buf[i];
>      > + int c2 = str2.buf[i];
>      > + if (c1 < c2) return -1;
>      > + if (c1 > c2) return 1;
>      > + i++;
>      > }
>      > - if (j >= n) return false;
>      > - if (n > 0) to[j] = '\0';
>      > - return true;
>      > + if (i < str1.len) return 1;
>      > + if (i < str2.len) return -1;
>      > + return 0;
>      > }
>      >
>      > -char *mg_json_get_str(struct mg_str json, const char *path) {
>      > - char *result = NULL;
>      > - int len = 0, off = mg_json_get(json, path, &len);
>      > - if (off >= 0 && len > 1 && json.ptr[off] == '"') {
>      > - if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
>      > - !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
>      > - (size_t) len)) {
>      > - free(result);
>      > - result = NULL;
>      > - }
>      > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
>     str2) {
>      > + size_t i = 0;
>      > + while (i < str1.len && i < str2.len) {
>      > + int c1 = mg_tolc(str1.buf[i]);
>      > + int c2 = mg_tolc(str2.buf[i]);
>      > + if (c1 < c2) return -1;
>      > + if (c1 > c2) return 1;
>      > + i++;
>      > }
>      > - return result;
>      > + if (i < str1.len) return 1;
>      > + if (i < str2.len) return -1;
>      > + return 0;
>      > }
>      >
>      > -char *mg_json_get_b64(struct mg_str json, const char *path, int
>     *slen) {
>      > - char *result = NULL;
>      > - int len = 0, off = mg_json_get(json, path, &len);
>      > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
>      > - (result = (char *) calloc(1, (size_t) len)) != NULL) {
>      > - int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
>      > - if (slen != NULL) *slen = k;
>      > +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
>     *caps) {
>      > + size_t i = 0, j = 0, ni = 0, nj = 0;
>      > + if (caps) caps->buf = NULL, caps->len = 0;
>      > + while (i < p.len || j < s.len) {
>      > + if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] ==
>     p.buf[i])) {
>      > + if (caps == NULL) {
>      > + } else if (p.buf[i] == '?') {
>      > + caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap
>      > + caps++, caps->buf = NULL, caps->len = 0; // Init next cap
>      > + } else if (caps->buf != NULL && caps->len == 0) {
>      > + caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize
>     current cap
>      > + caps++, caps->len = 0, caps->buf = NULL; // Init next cap
>      > + }
>      > + i++, j++;
>      > + } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
>      > + if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j];
>     // Init cap
>      > + ni = i++, nj = j + 1;
>      > + } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' ||
>     s.buf[j] != '/')) {
>      > + i = ni, j = nj;
>      > + if (caps && caps->buf == NULL && caps->len == 0) {
>      > + caps--, caps->len = 0; // Restart previous cap
>      > + }
>      > + } else {
>      > + return false;
>      > + }
>      > }
>      > - return result;
>      > + if (caps && caps->buf && caps->len == 0) {
>      > + caps->len = (size_t) (&s.buf[j] - caps->buf);
>      > + }
>      > + return true;
>      > }
>      >
>      > -char *mg_json_get_hex(struct mg_str json, const char *path, int
>     *slen) {
>      > - char *result = NULL;
>      > - int len = 0, off = mg_json_get(json, path, &len);
>      > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
>      > - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
>      > - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *)
>     result);
>      > - result[len / 2 - 1] = '\0';
>      > - if (slen != NULL) *slen = len / 2 - 1;
>      > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
>     *b, char sep) {
>      > + if (s.len == 0 || s.buf == NULL) {
>      > + return false; // Empty string, nothing to span - fail
>      > + } else {
>      > + size_t len = 0;
>      > + while (len < s.len && s.buf[len] != sep) len++; // Find separator
>      > + if (a) *a = mg_str_n(s.buf, len); // Init a
>      > + if (b) *b = mg_str_n(s.buf + len, s.len - len); // Init b
>      > + if (b && len < s.len) b->buf++, b->len--; // Skip separator
>      > + return true;
>      > }
>      > - return result;
>      > }
>      >
>      > -long mg_json_get_long(struct mg_str json, const char *path, long
>     dflt) {
>      > - double dv;
>      > - long result = dflt;
>      > - if (mg_json_get_num(json, path, &dv)) result = (long) dv;
>      > - return result;
>      > +bool mg_str_to_num(struct mg_str str, int base, void *val,
>     size_t val_len) {
>      > + size_t i = 0, ndigits = 0;
>      > + uint64_t max = val_len == sizeof(uint8_t) ? 0xFF
>      > + : val_len == sizeof(uint16_t) ? 0xFFFF
>      > + : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
>      > + : (uint64_t) ~0;
>      > + uint64_t result = 0;
>      > + if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return
>     false;
>      > + if (base == 0 && str.len >= 2) {
>      > + if (str.buf[i] == '0') {
>      > + i++;
>      > + base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
>      > + if (base != 10) ++i;
>      > + } else {
>      > + base = 10;
>      > + }
>      > + }
>      > + switch (base) {
>      > + case 2:
>      > + while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
>      > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
>      > + if (result > max/2) return false; // Overflow
>      > + result *= 2;
>      > + if (result > max - digit) return false; // Overflow
>      > + result += digit;
>      > + i++, ndigits++;
>      > + }
>      > + break;
>      > + case 10:
>      > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
>      > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
>      > + if (result > max/10) return false; // Overflow
>      > + result *= 10;
>      > + if (result > max - digit) return false; // Overflow
>      > + result += digit;
>      > + i++, ndigits++;
>      > + }
>      > + break;
>      > + case 16:
>      > + while (i < str.len) {
>      > + char c = str.buf[i];
>      > + uint64_t digit = (c >= '0' && c <= '9') ? (uint64_t) (c - '0')
>      > + : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
>      > + : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
>      > + : (uint64_t) ~0;
>      > + if (digit == (uint64_t) ~0) break;
>      > + if (result > max/16) return false; // Overflow
>      > + result *= 16;
>      > + if (result > max - digit) return false; // Overflow
>      > + result += digit;
>      > + i++, ndigits++;
>      > + }
>      > + break;
>      > + default:
>      > + return false;
>      > + }
>      > + if (ndigits == 0) return false;
>      > + if (i != str.len) return false;
>      > + if (val_len == 1) {
>      > + *((uint8_t *) val) = (uint8_t) result;
>      > + } else if (val_len == 2) {
>      > + *((uint16_t *) val) = (uint16_t) result;
>      > + } else if (val_len == 4) {
>      > + *((uint32_t *) val) = (uint32_t) result;
>      > + } else {
>      > + *((uint64_t *) val) = (uint64_t) result;
>      > + }
>      > + return true;
>      > }
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/log.c"
>      > +#line 1 "src/timer.c"
>      > #endif
>      >
>      >
>      >
>      > +#define MG_TIMER_CALLED 4
>      >
>      > -
>      > -static void default_logger(char c, void *param) {
>      > - putchar(c);
>      > - (void) c, (void) param;
>      > -}
>      > -
>      > -static int s_level = MG_LL_INFO;
>      > -static mg_pfn_t s_log_func = default_logger;
>      > -static void *s_log_func_param = NULL;
>      > -
>      > -void mg_log_set_fn(mg_pfn_t fn, void *param) {
>      > - s_log_func = fn;
>      > - s_log_func_param = param;
>      > -}
>      > -
>      > -static void logc(unsigned char c) {
>      > - s_log_func((char) c, s_log_func_param);
>      > -}
>      > -
>      > -static void logs(const char *buf, size_t len) {
>      > - size_t i;
>      > - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
>      > -}
>      > -
>      > -void mg_log_set(int log_level) {
>      > - MG_DEBUG(("Setting log level to %d", log_level));
>      > - s_level = log_level;
>      > -}
>      > -
>      > -bool mg_log_prefix(int level, const char *file, int line, const
>     char *fname) {
>      > - if (level <= s_level) {
>      > - const char *p = strrchr(file, '/');
>      > - char buf[41];
>      > - size_t n;
>      > - if (p == NULL) p = strrchr(file, '\\');
>      > - n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s",
>     mg_millis(), level,
>      > - p == NULL ? file : p + 1, line, fname);
>      > - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
>      > - while (n < sizeof(buf)) buf[n++] = ' ';
>      > - logs(buf, n - 1);
>      > - return true;
>      > - } else {
>      > - return false;
>      > - }
>      > +void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
>     uint64_t ms,
>      > + unsigned flags, void (*fn)(void *), void *arg) {
>      > + t->id = 0, t->period_ms = ms, t->expire = 0;
>      > + t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
>      > + *head = t;
>      > }
>      >
>      > -void mg_log(const char *fmt, ...) {
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
>      > - va_end(ap);
>      > - logc((unsigned char) '\n');
>      > +void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
>      > + while (*head && *head != t) head = &(*head)->next;
>      > + if (*head) *head = t->next;
>      > }
>      >
>      > -static unsigned char nibble(unsigned c) {
>      > - return (unsigned char) (c < 10 ? c + '0' : c + 'W');
>      > +// t: expiration time, prd: period, now: current time. Return
>     true if expired
>      > +bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
>      > + if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
>      > + if (*t == 0) *t = now + prd; // Firt poll? Set expiration
>      > + if (*t > now) return false; // Not expired yet, return
>      > + *t = (now - *t) > prd ? now + prd : *t + prd; // Next
>     expiration time
>      > + return true; // Expired, return true
>      > }
>      >
>      > -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
>      > -void mg_hexdump(const void *buf, size_t len) {
>      > - const unsigned char *p = (const unsigned char *) buf;
>      > - unsigned char ascii[16], alen = 0;
>      > - size_t i;
>      > - for (i = 0; i < len; i++) {
>      > - if ((i % 16) == 0) {
>      > - // Print buffered ascii chars
>      > - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
>     alen = 0;
>      > - // Print hex address, then \t
>      > - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
>      > - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
>      > - }
>      > - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
>     nibbles, e.g. c5
>      > - logc(' '); // Space after hex number
>      > - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
>     buf
>      > +void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
>      > + struct mg_timer *t, *tmp;
>      > + for (t = *head; t != NULL; t = tmp) {
>      > + bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
>      > + !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
>      > + bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
>      > + tmp = t->next;
>      > + if (!once && !expired) continue;
>      > + if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
>     MG_TIMER_CALLED)) {
>      > + t->fn(t->arg);
>      > + }
>      > + t->flags |= MG_TIMER_CALLED;
>      > }
>      > - while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
>      > - logs(" ", 2), logs((char *) ascii, 16), logc('\n');
>      > }
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/md5.c"
>      > +#line 1 "src/tls_aes128.c"
>      > #endif
>      >
>     +/******************************************************************************
>      > + *
>      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>     THE GOOD OF ALL
>      > + *
>      > + * This is a simple and straightforward implementation of the
>     AES Rijndael
>      > + * 128-bit block cipher designed by Vincent Rijmen and Joan
>     Daemen. The focus
>      > + * of this work was correctness & accuracy. It is written in 'C'
>     without any
>      > + * particular focus upon optimization or speed. It should be
>     endian (memory
>      > + * byte order) neutral since the few places that care are
>     handled explicitly.
>      > + *
>      > + * This implementation of Rijndael was created by Steven M.
>     Gibson of GRC.com.
>      > + *
>      > + * It is intended for general purpose use, but was written in
>     support of GRC's
>      > + * reference implementation of the SQRL (Secure Quick Reliable
>     Login) client.
>      > + *
>      > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
>     <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
>      > + *
>      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>     WARRANTY MADE
>      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>     YOUR OWN RISK.
>      > + *
>      > +
>     *******************************************************************************/
>      > +
>      >
>     +/******************************************************************************/
>      > +#define AES_DECRYPTION 1 // whether AES decryption is supported
>      >
>     +/******************************************************************************/
>      > +
>      > +#define MG_ENCRYPT 1 // specify whether we're encrypting
>      > +#define MG_DECRYPT 0 // or decrypting
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +#if MG_TLS == MG_TLS_BUILTIN
>      >
>     +/******************************************************************************
>      > + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
>      > +
>     ******************************************************************************/
>      > +static void aes_init_keygen_tables(void);
>      > +
>      >
>     +/******************************************************************************
>      > + * AES_SETKEY : called to expand the key for encryption or
>     decryption
>      > +
>     ******************************************************************************/
>      > +static int aes_setkey(aes_context *ctx, // pointer to context
>      > + int mode, // 1 or 0 for Encrypt/Decrypt
>      > + const uchar *key, // AES input key
>      > + uint keysize); // size in bytes (must be 16, 24, 32 for
>      > + // 128, 192 or 256-bit keys respectively)
>      > + // returns 0 for success
>      > +
>      >
>     +/******************************************************************************
>      > + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block
>     of data
>      > +
>     ******************************************************************************/
>      > +static int aes_cipher(aes_context *ctx, // pointer to context
>      > + const uchar input[16], // 128-bit block to en/decipher
>      > + uchar output[16]); // 128-bit output result block
>      > + // returns 0 for success
>      > +
>      >
>     +/******************************************************************************
>      > + * GCM_CONTEXT : GCM context / holds keytables, instance data,
>     and AES ctx
>      > +
>     ******************************************************************************/
>      > +typedef struct {
>      > + int mode; // cipher direction: encrypt/decrypt
>      > + uint64_t len; // cipher data length processed so far
>      > + uint64_t add_len; // total add data length
>      > + uint64_t HL[16]; // precalculated lo-half HTable
>      > + uint64_t HH[16]; // precalculated hi-half HTable
>      > + uchar base_ectr[16]; // first counter-mode cipher output for tag
>      > + uchar y[16]; // the current cipher-input IV|Counter value
>      > + uchar buf[16]; // buf working value
>      > + aes_context aes_ctx; // cipher context used
>      > +} gcm_context;
>      > +
>      >
>     +/******************************************************************************
>      > + * GCM_SETKEY : sets the GCM (and AES) keying material for use
>      > +
>     ******************************************************************************/
>      > +static int gcm_setkey(
>      > + gcm_context *ctx, // caller-provided context ptr
>      > + const uchar *key, // pointer to cipher key
>      > + const uint keysize // size in bytes (must be 16, 24, 32 for
>      > + // 128, 192 or 256-bit keys respectively)
>      > +); // returns 0 for success
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_CRYPT_AND_TAG
>      > + *
>      > + * This either encrypts or decrypts the user-provided data and,
>     either
>      > + * way, generates an authentication tag of the requested length.
>     It must be
>      > + * called with a GCM context whose key has already been set with
>     GCM_SETKEY.
>      > + *
>      > + * The user would typically call this explicitly to ENCRYPT a
>     buffer of data
>      > + * and optional associated data, and produce its an
>     authentication tag.
>      > + *
>      > + * To reverse the process the user would typically call the
>     companion
>      > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
>     user-provided
>      > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
>     function
>      > + * to perform its decryption and tag generation, which it then
>     compares.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int gcm_crypt_and_tag(
>      > + gcm_context *ctx, // gcm context with key already setup
>      > + int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
>      > + const uchar *iv, // pointer to the 12-byte initialization vector
>      > + size_t iv_len, // byte length if the IV. should always be 12
>      > + const uchar *add, // pointer to the non-ciphered additional data
>      > + size_t add_len, // byte length of the additional AEAD data
>      > + const uchar *input, // pointer to the cipher data source
>      > + uchar *output, // pointer to the cipher data destination
>      > + size_t length, // byte length of the cipher data
>      > + uchar *tag, // pointer to the tag to be generated
>      > + size_t tag_len); // byte length of the tag to be generated
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_START
>      > + *
>      > + * Given a user-provided GCM context, this initializes it, sets
>     the encryption
>      > + * mode, and preprocesses the initialization vector and
>     additional AEAD data.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int gcm_start(
>      > + gcm_context *ctx, // pointer to user-provided GCM context
>      > + int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0)
>      > + const uchar *iv, // pointer to initialization vector
>      > + size_t iv_len, // IV length in bytes (should == 12)
>      > + const uchar *add, // pointer to additional AEAD data (NULL if
>     none)
>      > + size_t add_len); // length of additional AEAD data (bytes)
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_UPDATE
>      > + *
>      > + * This is called once or more to process bulk plaintext or
>     ciphertext data.
>      > + * We give this some number of bytes of input and it returns the
>     same number
>      > + * of output bytes. If called multiple times (which is fine) all
>     but the final
>      > + * invocation MUST be called with length mod 16 == 0. (Only the
>     final call can
>      > + * have a partial block length of < 128 bits.)
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int gcm_update(gcm_context *ctx, // pointer to
>     user-provided GCM context
>      > + size_t length, // length, in bytes, of data to process
>      > + const uchar *input, // pointer to source data
>      > + uchar *output); // pointer to destination data
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_FINISH
>      > + *
>      > + * This is called once after all calls to GCM_UPDATE to finalize
>     the GCM.
>      > + * It performs the final GHASH to produce the resulting
>     authentication TAG.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int gcm_finish(
>      > + gcm_context *ctx, // pointer to user-provided GCM context
>      > + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0
>      > + size_t tag_len); // length, in bytes, of the tag-receiving buf
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_ZERO_CTX
>      > + *
>      > + * The GCM context contains both the GCM context and the AES
>     context.
>      > + * This includes keying and key-related material which is security-
>      > + * sensitive, so it MUST be zeroed after use. This function does
>     that.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static void gcm_zero_ctx(gcm_context *ctx);
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>     THE GOOD OF ALL
>      > + *
>      > + * This is a simple and straightforward implementation of the
>     AES Rijndael
>      > + * 128-bit block cipher designed by Vincent Rijmen and Joan
>     Daemen. The focus
>      > + * of this work was correctness & accuracy. It is written in 'C'
>     without any
>      > + * particular focus upon optimization or speed. It should be
>     endian (memory
>      > + * byte order) neutral since the few places that care are
>     handled explicitly.
>      > + *
>      > + * This implementation of Rijndael was created by Steven M.
>     Gibson of GRC.com.
>      > + *
>      > + * It is intended for general purpose use, but was written in
>     support of GRC's
>      > + * reference implementation of the SQRL (Secure Quick Reliable
>     Login) client.
>      > + *
>      > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
>     <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
>      > + *
>      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>     WARRANTY MADE
>      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>     YOUR OWN RISK.
>      > + *
>      > +
>     *******************************************************************************/
>      > +
>      > +
>      > +
>      > +
>      > +static int aes_tables_inited = 0; // run-once flag for
>     performing key
>      > + // expasion table generation (see below)
>      > +/*
>      > + * The following static local tables must be filled-in before
>     the first use of
>      > + * the GCM or AES ciphers. They are used for the AES key
>     expansion/scheduling
>      > + * and once built are read-only and thread safe. The
>     "gcm_initialize" function
>      > + * must be called once during system initialization to populate
>     these arrays
>      > + * for subsequent use by the AES key scheduler. If they have not
>     been built
>      > + * before attempted use, an error will be returned to the caller.
>      > + *
>      > + * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES
>     decryption. Since
>      > + * GCM uses AES in counter-mode, where the AES cipher output is
>     XORed with
>      > + * the GCM input, we ONLY NEED AES encryption. Thus, to save
>     space AES
>      > + * decryption is typically disabled by setting AES_DECRYPTION to
>     0 in aes.h.
>      > + */
>      > +// We always need our forward tables
>      > +static uchar FSb[256]; // Forward substitution box (FSb)
>      > +static uint32_t FT0[256]; // Forward key schedule assembly tables
>      > +static uint32_t FT1[256];
>      > +static uint32_t FT2[256];
>      > +static uint32_t FT3[256];
>      > +
>      > +#if AES_DECRYPTION // We ONLY need reverse for decryption
>      > +static uchar RSb[256]; // Reverse substitution box (RSb)
>      > +static uint32_t RT0[256]; // Reverse key schedule assembly tables
>      > +static uint32_t RT1[256];
>      > +static uint32_t RT2[256];
>      > +static uint32_t RT3[256];
>      > +#endif /* AES_DECRYPTION */
>      > +
>      > +static uint32_t RCON[10]; // AES round constants
>      >
>      > +/*
>      > + * Platform Endianness Neutralizing Load and Store Macro
>     definitions
>      > + * AES wants platform-neutral Little Endian (LE) byte ordering
>      > + */
>      > +#define GET_UINT32_LE(n, b, i) \
>      > + { \
>      > + (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) | \
>      > + ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] <<
>     24); \
>      > + }
>      >
>      > -
>      > -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
>      > -
>      > -static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
>      > - if (MG_BIG_ENDIAN) {
>      > - do {
>      > - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
>      > - ((unsigned) buf[1] << 8 | buf[0]);
>      > - *(uint32_t *) buf = t;
>      > - buf += 4;
>      > - } while (--longs);
>      > - } else {
>      > - (void) buf, (void) longs; // Little endian. Do nothing
>      > +#define PUT_UINT32_LE(n, b, i) \
>      > + { \
>      > + (b)[(i)] = (uchar) ((n)); \
>      > + (b)[(i) + 1] = (uchar) ((n) >> 8); \
>      > + (b)[(i) + 2] = (uchar) ((n) >> 16); \
>      > + (b)[(i) + 3] = (uchar) ((n) >> 24); \
>      > }
>      > -}
>      >
>      > -#define F1(x, y, z) (z ^ (x & (y ^ z)))
>      > -#define F2(x, y, z) F1(z, x, y)
>      > -#define F3(x, y, z) (x ^ y ^ z)
>      > -#define F4(x, y, z) (y ^ (x | ~z))
>      > +/*
>      > + * AES forward and reverse encryption round processing macros
>      > + */
>      > +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
>      > + { \
>      > + X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
>      > + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \
>      > + \
>      > + X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
>      > + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \
>      > + \
>      > + X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
>      > + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \
>      > + \
>      > + X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
>      > + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \
>      > + }
>      >
>      > -#define MD5STEP(f, w, x, y, z, data, s) \
>      > - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
>      > +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
>      > + { \
>      > + X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
>      > + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \
>      > + \
>      > + X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
>      > + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \
>      > + \
>      > + X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
>      > + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \
>      > + \
>      > + X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
>      > + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \
>      > + }
>      >
>      > /*
>      > - * Start MD5 accumulation. Set bit count to 0 and buffer to
>     mysterious
>      > - * initialization constants.
>      > + * These macros improve the readability of the key
>      > + * generation initialization code by collapsing
>      > + * repetitive common operations into logical pieces.
>      > */
>      > -void mg_md5_init(mg_md5_ctx *ctx) {
>      > - ctx->buf[0] = 0x67452301;
>      > - ctx->buf[1] = 0xefcdab89;
>      > - ctx->buf[2] = 0x98badcfe;
>      > - ctx->buf[3] = 0x10325476;
>      > +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
>      > +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
>      > +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
>      > +#define MIX(x, y) \
>      > + { \
>      > + y = ((y << 1) | (y >> 7)) & 0xFF; \
>      > + x ^= y; \
>      > + }
>      > +#define CPY128 \
>      > + { \
>      > + *RK++ = *SK++; \
>      > + *RK++ = *SK++; \
>      > + *RK++ = *SK++; \
>      > + *RK++ = *SK++; \
>      > + }
>      >
>      > - ctx->bits[0] = 0;
>      > - ctx->bits[1] = 0;
>      > -}
>      >
>     +/******************************************************************************
>      > + *
>      > + * AES_INIT_KEYGEN_TABLES
>      > + *
>      > + * Fills the AES key expansion tables allocated above with their
>     static
>      > + * data. This is not "per key" data, but static system-wide
>     read-only
>      > + * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be
>     called once
>      > + * at system initialization to setup the tables for all
>     subsequent use.
>      > + *
>      > +
>     ******************************************************************************/
>      > +void aes_init_keygen_tables(void) {
>      > + int i, x, y, z; // general purpose iteration and computation
>     locals
>      > + int pow[256];
>      > + int log[256];
>      > +
>      > + if (aes_tables_inited) return;
>      > +
>      > + // fill the 'pow' and 'log' tables over GF(2^8)
>      > + for (i = 0, x = 1; i < 256; i++) {
>      > + pow[i] = x;
>      > + log[x] = i;
>      > + x = (x ^ XTIME(x)) & 0xFF;
>      > + }
>      > + // compute the round constants
>      > + for (i = 0, x = 1; i < 10; i++) {
>      > + RCON[i] = (uint32_t) x;
>      > + x = XTIME(x) & 0xFF;
>      > + }
>      > + // fill the forward and reverse substitution boxes
>      > + FSb[0x00] = 0x63;
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > + RSb[0x63] = 0x00;
>      > +#endif /* AES_DECRYPTION */
>      > +
>      > + for (i = 1; i < 256; i++) {
>      > + x = y = pow[255 - log[i]];
>      > + MIX(x, y);
>      > + MIX(x, y);
>      > + MIX(x, y);
>      > + MIX(x, y);
>      > + FSb[i] = (uchar) (x ^= 0x63);
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > + RSb[x] = (uchar) i;
>      > +#endif /* AES_DECRYPTION */
>      > + }
>      > + // generate the forward and reverse key expansion tables
>      > + for (i = 0; i < 256; i++) {
>      > + x = FSb[i];
>      > + y = XTIME(x) & 0xFF;
>      > + z = (y ^ x) & 0xFF;
>      >
>      > -static void mg_md5_transform(uint32_t buf[4], uint32_t const
>     in[16]) {
>      > - uint32_t a, b, c, d;
>      > + FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x
>     << 16) ^
>      > + ((uint32_t) z << 24);
>      >
>      > - a = buf[0];
>      > - b = buf[1];
>      > - c = buf[2];
>      > - d = buf[3];
>      > + FT1[i] = ROTL8(FT0[i]);
>      > + FT2[i] = ROTL8(FT1[i]);
>      > + FT3[i] = ROTL8(FT2[i]);
>      >
>      > - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
>      > - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
>      > - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
>      > - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
>      > - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
>      > - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
>      > - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
>      > - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
>      > - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
>      > - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
>      > - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
>      > - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
>      > - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
>      > - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
>      > - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
>      > - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > + x = RSb[i];
>      >
>      > - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
>      > - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
>      > - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
>      > - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
>      > - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
>      > - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
>      > - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
>      > - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
>      > - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
>      > - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
>      > - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
>      > - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
>      > - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
>      > - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
>      > - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
>      > - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
>      > + RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x)
>     << 8) ^
>      > + ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
>      >
>      > - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
>      > - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
>      > - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
>      > - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
>      > - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
>      > - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
>      > - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
>      > - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
>      > - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
>      > - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
>      > - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
>      > - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
>      > - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
>      > - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
>      > - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
>      > - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
>      > + RT1[i] = ROTL8(RT0[i]);
>      > + RT2[i] = ROTL8(RT1[i]);
>      > + RT3[i] = ROTL8(RT2[i]);
>      > +#endif /* AES_DECRYPTION */
>      > + }
>      > + aes_tables_inited = 1; // flag that the tables have been generated
>      > +} // to permit subsequent use of the AES cipher
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * AES_SET_ENCRYPTION_KEY
>      > + *
>      > + * This is called by 'aes_setkey' when we're establishing a key for
>      > + * subsequent encryption. We give it a pointer to the encryption
>      > + * context, a pointer to the key, and the key's length in bytes.
>      > + * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int aes_set_encryption_key(aes_context *ctx, const uchar
>     *key,
>      > + uint keysize) {
>      > + uint i; // general purpose iteration local
>      > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
>      > +
>      > + for (i = 0; i < (keysize >> 2); i++) {
>      > + GET_UINT32_LE(RK[i], key, i << 2);
>      > + }
>      >
>      > - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
>      > - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
>      > - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
>      > - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
>      > - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
>      > - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
>      > - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
>      > - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
>      > - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
>      > - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
>      > - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
>      > - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
>      > - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
>      > - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
>      > - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
>      > - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
>      > + switch (ctx->rounds) {
>      > + case 10:
>      > + for (i = 0; i < 10; i++, RK += 4) {
>      > + RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
>      > + ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
>      > +
>      > + RK[5] = RK[1] ^ RK[4];
>      > + RK[6] = RK[2] ^ RK[5];
>      > + RK[7] = RK[3] ^ RK[6];
>      > + }
>      > + break;
>      >
>      > - buf[0] += a;
>      > - buf[1] += b;
>      > - buf[2] += c;
>      > - buf[3] += d;
>      > -}
>      > + case 12:
>      > + for (i = 0; i < 8; i++, RK += 6) {
>      > + RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
>      > + ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
>      > +
>      > + RK[7] = RK[1] ^ RK[6];
>      > + RK[8] = RK[2] ^ RK[7];
>      > + RK[9] = RK[3] ^ RK[8];
>      > + RK[10] = RK[4] ^ RK[9];
>      > + RK[11] = RK[5] ^ RK[10];
>      > + }
>      > + break;
>      >
>      > -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
>     size_t len) {
>      > - uint32_t t;
>      > + case 14:
>      > + for (i = 0; i < 7; i++, RK += 8) {
>      > + RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
>      > + ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
>      > +
>      > + RK[9] = RK[1] ^ RK[8];
>      > + RK[10] = RK[2] ^ RK[9];
>      > + RK[11] = RK[3] ^ RK[10];
>      > +
>      > + RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
>      > + ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
>      > +
>      > + RK[13] = RK[5] ^ RK[12];
>      > + RK[14] = RK[6] ^ RK[13];
>      > + RK[15] = RK[7] ^ RK[14];
>      > + }
>      > + break;
>      >
>      > - t = ctx->bits[0];
>      > - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
>     ctx->bits[1]++;
>      > - ctx->bits[1] += (uint32_t) len >> 29;
>      > + default:
>      > + return -1;
>      > + }
>      > + return (0);
>      > +}
>      > +
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * AES_SET_DECRYPTION_KEY
>      > + *
>      > + * This is called by 'aes_setkey' when we're establishing a
>      > + * key for subsequent decryption. We give it a pointer to
>      > + * the encryption context, a pointer to the key, and the key's
>      > + * length in bits. Valid lengths are: 128, 192, or 256 bits.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int aes_set_decryption_key(aes_context *ctx, const uchar
>     *key,
>      > + uint keysize) {
>      > + int i, j;
>      > + aes_context cty; // a calling aes context for set_encryption_key
>      > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
>      > + uint32_t *SK;
>      > + int ret;
>      >
>      > - t = (t >> 3) & 0x3f;
>      > + cty.rounds = ctx->rounds; // initialize our local aes context
>      > + cty.rk = cty.buf; // round count and key buf pointer
>      >
>      > - if (t) {
>      > - unsigned char *p = (unsigned char *) ctx->in + t;
>      > + if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0)
>     return (ret);
>      >
>      > - t = 64 - t;
>      > - if (len < t) {
>      > - memcpy(p, buf, len);
>      > - return;
>      > + SK = cty.rk + cty.rounds * 4;
>      > +
>      > + CPY128 // copy a 128-bit block from *SK to *RK
>      > +
>      > + for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
>      > + for (j = 0; j < 4; j++, SK++) {
>      > + *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
>      > + RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
>      > }
>      > - memcpy(p, buf, t);
>      > - mg_byte_reverse(ctx->in, 16);
>      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > - buf += t;
>      > - len -= t;
>      > + }
>      > + CPY128 // copy a 128-bit block from *SK to *RK
>      > + memset(&cty, 0, sizeof(aes_context)); // clear local aes context
>      > + return (0);
>      > +}
>      > +
>      > +#endif /* AES_DECRYPTION */
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * AES_SETKEY
>      > + *
>      > + * Invoked to establish the key schedule for subsequent
>     encryption/decryption
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int aes_setkey(aes_context *ctx, // AES context provided
>     by our caller
>      > + int mode, // ENCRYPT or DECRYPT flag
>      > + const uchar *key, // pointer to the key
>      > + uint keysize) // key length in bytes
>      > +{
>      > + // since table initialization is not thread safe, we could
>     either add
>      > + // system-specific mutexes and init the AES key generation
>     tables on
>      > + // demand, or ask the developer to simply call "gcm_initialize"
>     once during
>      > + // application startup before threading begins. That's what we
>     choose.
>      > + if (!aes_tables_inited) return (-1); // fail the call when not
>     inited.
>      > +
>      > + ctx->mode = mode; // capture the key type we're creating
>      > + ctx->rk = ctx->buf; // initialize our round key pointer
>      > +
>      > + switch (keysize) // set the rounds count based upon the keysize
>      > + {
>      > + case 16:
>      > + ctx->rounds = 10;
>      > + break; // 16-byte, 128-bit key
>      > + case 24:
>      > + ctx->rounds = 12;
>      > + break; // 24-byte, 192-bit key
>      > + case 32:
>      > + ctx->rounds = 14;
>      > + break; // 32-byte, 256-bit key
>      > + default:
>      > + return (-1);
>      > }
>      >
>      > - while (len >= 64) {
>      > - memcpy(ctx->in, buf, 64);
>      > - mg_byte_reverse(ctx->in, 16);
>      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > - buf += 64;
>      > - len -= 64;
>      > +#if AES_DECRYPTION
>      > + if (mode == MG_DECRYPT) // expand our key for encryption or
>     decryption
>      > + return (aes_set_decryption_key(ctx, key, keysize));
>      > + else /* MG_ENCRYPT */
>      > +#endif /* AES_DECRYPTION */
>      > + return (aes_set_encryption_key(ctx, key, keysize));
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * AES_CIPHER
>      > + *
>      > + * Perform AES encryption and decryption.
>      > + * The AES context will have been setup with the encryption mode
>      > + * and all keying information appropriate for the task.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int aes_cipher(aes_context *ctx, const uchar input[16],
>      > + uchar output[16]) {
>      > + int i;
>      > + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general
>     purpose locals
>      > +
>      > + RK = ctx->rk;
>      > +
>      > + GET_UINT32_LE(X0, input, 0);
>      > + X0 ^= *RK++; // load our 128-bit
>      > + GET_UINT32_LE(X1, input, 4);
>      > + X1 ^= *RK++; // input buffer in a storage
>      > + GET_UINT32_LE(X2, input, 8);
>      > + X2 ^= *RK++; // memory endian-neutral way
>      > + GET_UINT32_LE(X3, input, 12);
>      > + X3 ^= *RK++;
>      > +
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > +
>      > + if (ctx->mode == MG_DECRYPT) {
>      > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
>      > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>      > + AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
>      > + }
>      > +
>      > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>      > +
>      > + X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
>      > + ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
>      > +
>      > + X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
>      > + ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
>      > +
>      > + X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
>      > + ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
>      > +
>      > + X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
>      > + ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
>      > + } else /* MG_ENCRYPT */
>      > + {
>      > +#endif /* AES_DECRYPTION */
>      > +
>      > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
>      > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>      > + AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
>      > + }
>      > +
>      > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>      > +
>      > + X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
>      > + ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
>      > +
>      > + X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
>      > + ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
>      > +
>      > + X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
>      > + ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
>      > +
>      > + X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
>      > + ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
>      > + ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
>      > + ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
>      > +
>      > +#if AES_DECRYPTION // whether AES decryption is supported
>      > }
>      > +#endif /* AES_DECRYPTION */
>      > +
>      > + PUT_UINT32_LE(X0, output, 0);
>      > + PUT_UINT32_LE(X1, output, 4);
>      > + PUT_UINT32_LE(X2, output, 8);
>      > + PUT_UINT32_LE(X3, output, 12);
>      > +
>      > + return (0);
>      > +}
>      > +/* end of aes.c */
>      >
>     +/******************************************************************************
>      > + *
>      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>     THE GOOD OF ALL
>      > + *
>      > + * This is a simple and straightforward implementation of
>     AES-GCM authenticated
>      > + * encryption. The focus of this work was correctness &
>     accuracy. It is written
>      > + * in straight 'C' without any particular focus upon
>     optimization or speed. It
>      > + * should be endian (memory byte order) neutral since the few
>     places that care
>      > + * are handled explicitly.
>      > + *
>      > + * This implementation of AES-GCM was created by Steven M.
>     Gibson of GRC.com.
>      > + *
>      > + * It is intended for general purpose use, but was written in
>     support of GRC's
>      > + * reference implementation of the SQRL (Secure Quick Reliable
>     Login) client.
>      > + *
>      > + * See:
>     http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
>     <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
>      > + *
>     http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
>     <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/>
>      > + * gcm/gcm-revised-spec.pdf
>      > + *
>      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>     WARRANTY MADE
>      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>     YOUR OWN RISK.
>      > + *
>      > +
>     *******************************************************************************/
>      > +
>      >
>     +/******************************************************************************
>      > + * ==== IMPLEMENTATION WARNING ====
>      > + *
>      > + * This code was developed for use within SQRL's fixed
>     environmnent. Thus, it
>      > + * is somewhat less "general purpose" than it would be if it
>     were designed as
>      > + * a general purpose AES-GCM library. Specifically, it bothers
>     with almost NO
>      > + * error checking on parameter limits, buffer bounds, etc. It
>     assumes that it
>      > + * is being invoked by its author or by someone who understands
>     the values it
>      > + * expects to receive. Its behavior will be undefined otherwise.
>      > + *
>      > + * All functions that might fail are defined to return 'ints' to
>     indicate a
>      > + * problem. Most do not do so now. But this allows for error
>     propagation out
>      > + * of internal functions if robust error checking should ever be
>     desired.
>      > + *
>      > +
>     ******************************************************************************/
>      > +
>      > +/* Calculating the "GHASH"
>      > + *
>      > + * There are many ways of calculating the so-called GHASH in
>     software, each with
>      > + * a traditional size vs performance tradeoff. The GHASH (Galois
>     field hash) is
>      > + * an intriguing construction which takes two 128-bit strings
>     (also the cipher's
>      > + * block size and the fundamental operation size for the system)
>     and hashes them
>      > + * into a third 128-bit result.
>      > + *
>      > + * Many implementation solutions have been worked out that use
>     large precomputed
>      > + * table lookups in place of more time consuming bit fiddling,
>     and this approach
>      > + * can be scaled easily upward or downward as needed to change
>     the time/space
>      > + * tradeoff. It's been studied extensively and there's a solid
>     body of theory
>      > + * and practice. For example, without using any lookup tables an
>     implementation
>      > + * might obtain 119 cycles per byte throughput, whereas using a
>     simple, though
>      > + * large, key-specific 64 kbyte 8-bit lookup table the
>     performance jumps to 13
>      > + * cycles per byte.
>      > + *
>      > + * And Intel's processors have, since 2010, included an
>     instruction which does
>      > + * the entire 128x128->128 bit job in just several 64x64->128
>     bit pieces.
>      > + *
>      > + * Since SQRL is interactive, and only processing a few 128-bit
>     blocks, I've
>      > + * settled upon a relatively slower but appealing small-table
>     compromise which
>      > + * folds a bunch of not only time consuming but also bit
>     twiddling into a simple
>      > + * 16-entry table which is attributed to Victor Shoup's 1996
>     work while at
>      > + * Bellcore: "On Fast and Provably Secure MessageAuthentication
>     Based on
>      > + * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf
>     <http://www.shoup.net/papers/macs.pdf>
>      > + * See, also section 4.1 of the "gcm-revised-spec" cited above.
>      > + */
>      >
>      > - memcpy(ctx->in, buf, len);
>      > -}
>      > +/*
>      > + * This 16-entry table of pre-computed constants is used by the
>      > + * GHASH multiplier to improve over a strictly table-free but
>      > + * significantly slower 128x128 bit multiple within GF(2^128).
>      > + */
>      > +static const uint64_t last4[16] = {
>      > + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
>      > + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
>      >
>      > -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
>      > - unsigned count;
>      > - unsigned char *p;
>      > - uint32_t *a;
>      > +/*
>      > + * Platform Endianness Neutralizing Load and Store Macro
>     definitions
>      > + * GCM wants platform-neutral Big Endian (BE) byte ordering
>      > + */
>      > +#define GET_UINT32_BE(n, b, i) \
>      > + { \
>      > + (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] <<
>     16) | \
>      > + ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]); \
>      > + }
>      >
>      > - count = (ctx->bits[0] >> 3) & 0x3F;
>      > +#define PUT_UINT32_BE(n, b, i) \
>      > + { \
>      > + (b)[(i)] = (uchar) ((n) >> 24); \
>      > + (b)[(i) + 1] = (uchar) ((n) >> 16); \
>      > + (b)[(i) + 2] = (uchar) ((n) >> 8); \
>      > + (b)[(i) + 3] = (uchar) ((n)); \
>      > + }
>      >
>      > - p = ctx->in + count;
>      > - *p++ = 0x80;
>      > - count = 64 - 1 - count;
>      > - if (count < 8) {
>      > - memset(p, 0, count);
>      > - mg_byte_reverse(ctx->in, 16);
>      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > - memset(ctx->in, 0, 56);
>      > - } else {
>      > - memset(p, 0, count - 8);
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_INITIALIZE
>      > + *
>      > + * Must be called once to initialize the GCM library.
>      > + *
>      > + * At present, this only calls the AES keygen table generator,
>     which expands
>      > + * the AES keying tables for use. This is NOT A THREAD-SAFE
>     function, so it
>      > + * MUST be called during system initialization before a
>     multi-threading
>      > + * environment is running.
>      > + *
>      > +
>     ******************************************************************************/
>      > +int mg_gcm_initialize(void) {
>      > + aes_init_keygen_tables();
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_MULT
>      > + *
>      > + * Performs a GHASH operation on the 128-bit input vector 'x',
>     setting
>      > + * the 128-bit output vector to 'x' times H using our
>     precomputed tables.
>      > + * 'x' and 'output' are seen as elements of GCM's GF(2^128)
>     Galois field.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static void gcm_mult(gcm_context *ctx, // pointer to established
>     context
>      > + const uchar x[16], // pointer to 128-bit input vector
>      > + uchar output[16]) // pointer to 128-bit output vector
>      > +{
>      > + int i;
>      > + uchar lo, hi, rem;
>      > + uint64_t zh, zl;
>      > +
>      > + lo = (uchar) (x[15] & 0x0f);
>      > + hi = (uchar) (x[15] >> 4);
>      > + zh = ctx->HH[lo];
>      > + zl = ctx->HL[lo];
>      > +
>      > + for (i = 15; i >= 0; i--) {
>      > + lo = (uchar) (x[i] & 0x0f);
>      > + hi = (uchar) (x[i] >> 4);
>      > +
>      > + if (i != 15) {
>      > + rem = (uchar) (zl & 0x0f);
>      > + zl = (zh << 60) | (zl >> 4);
>      > + zh = (zh >> 4);
>      > + zh ^= (uint64_t) last4[rem] << 48;
>      > + zh ^= ctx->HH[lo];
>      > + zl ^= ctx->HL[lo];
>      > + }
>      > + rem = (uchar) (zl & 0x0f);
>      > + zl = (zh << 60) | (zl >> 4);
>      > + zh = (zh >> 4);
>      > + zh ^= (uint64_t) last4[rem] << 48;
>      > + zh ^= ctx->HH[hi];
>      > + zl ^= ctx->HL[hi];
>      > }
>      > - mg_byte_reverse(ctx->in, 14);
>      > + PUT_UINT32_BE(zh >> 32, output, 0);
>      > + PUT_UINT32_BE(zh, output, 4);
>      > + PUT_UINT32_BE(zl >> 32, output, 8);
>      > + PUT_UINT32_BE(zl, output, 12);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_SETKEY
>      > + *
>      > + * This is called to set the AES-GCM key. It initializes the AES
>     key
>      > + * and populates the gcm context's pre-calculated HTables.
>      > + *
>      > +
>     ******************************************************************************/
>      > +static int gcm_setkey(
>      > + gcm_context *ctx, // pointer to caller-provided gcm context
>      > + const uchar *key, // pointer to the AES encryption key
>      > + const uint keysize) // size in bytes (must be 16, 24, 32 for
>      > + // 128, 192 or 256-bit keys respectively)
>      > +{
>      > + int ret, i, j;
>      > + uint64_t hi, lo;
>      > + uint64_t vl, vh;
>      > + unsigned char h[16];
>      > +
>      > + memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided
>     GCM context
>      > + memset(h, 0, 16); // initialize the block to encrypt
>      > +
>      > + // encrypt the null 128-bit block to generate a key-based value
>      > + // which is then used to initialize our GHASH lookup tables
>      > + if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize))
>     != 0)
>      > + return (ret);
>      > + if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
>      > +
>      > + GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian
>      > + GET_UINT32_BE(lo, h, 4);
>      > + vh = (uint64_t) hi << 32 | lo;
>      > +
>      > + GET_UINT32_BE(hi, h, 8);
>      > + GET_UINT32_BE(lo, h, 12);
>      > + vl = (uint64_t) hi << 32 | lo;
>      > +
>      > + ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128)
>      > + ctx->HH[8] = vh;
>      > + ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128)
>      > + ctx->HL[0] = 0;
>      > +
>      > + for (i = 4; i > 0; i >>= 1) {
>      > + uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
>      > + vl = (vh << 63) | (vl >> 1);
>      > + vh = (vh >> 1) ^ ((uint64_t) T << 32);
>      > + ctx->HL[i] = vl;
>      > + ctx->HH[i] = vh;
>      > + }
>      > + for (i = 2; i < 16; i <<= 1) {
>      > + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
>      > + vh = *HiH;
>      > + vl = *HiL;
>      > + for (j = 1; j < i; j++) {
>      > + HiH[j] = vh ^ ctx->HH[j];
>      > + HiL[j] = vl ^ ctx->HL[j];
>      > + }
>      > + }
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM processing occurs four phases: SETKEY, START, UPDATE and
>     FINISH.
>      > + *
>      > + * SETKEY:
>      > + *
>      > + * START: Sets the Encryption/Decryption mode.
>      > + * Accepts the initialization vector and additional data.
>      > + *
>      > + * UPDATE: Encrypts or decrypts the plaintext or ciphertext.
>      > + *
>      > + * FINISH: Performs a final GHASH to generate the authentication
>     tag.
>      > + *
>      > +
>     ******************************************************************************
>      > + *
>      > + * GCM_START
>      > + *
>      > + * Given a user-provided GCM context, this initializes it, sets
>     the encryption
>      > + * mode, and preprocesses the initialization vector and
>     additional AEAD data.
>      > + *
>      > +
>     ******************************************************************************/
>      > +int gcm_start(gcm_context *ctx, // pointer to user-provided GCM
>     context
>      > + int mode, // GCM_ENCRYPT or GCM_DECRYPT
>      > + const uchar *iv, // pointer to initialization vector
>      > + size_t iv_len, // IV length in bytes (should == 12)
>      > + const uchar *add, // ptr to additional AEAD data (NULL if none)
>      > + size_t add_len) // length of additional AEAD data (bytes)
>      > +{
>      > + int ret; // our error return if the AES encrypt fails
>      > + uchar work_buf[16]; // XOR source built from provided IV if len
>     != 16
>      > + const uchar *p; // general purpose array pointer
>      > + size_t use_len; // byte count to process, up to 16 bytes
>      > + size_t i; // local loop iterator
>      > +
>      > + // since the context might be reused under the same key
>      > + // we zero the working buffers for this next new process
>      > + memset(ctx->y, 0x00, sizeof(ctx->y));
>      > + memset(ctx->buf, 0x00, sizeof(ctx->buf));
>      > + ctx->len = 0;
>      > + ctx->add_len = 0;
>      > +
>      > + ctx->mode = mode; // set the GCM encryption/decryption mode
>      > + ctx->aes_ctx.mode = MG_ENCRYPT; // GCM *always* runs AES in
>     ENCRYPTION mode
>      > +
>      > + if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV
>      > + memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the
>     'y' buff
>      > + ctx->y[15] = 1; // start "counting" from 1 (not 0)
>      > + } else // if we don't have a 12-byte IV, we GHASH whatever
>     we've been given
>      > + {
>      > + memset(work_buf, 0x00, 16); // clear the working buffer
>      > + PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into
>     buffer
>      > +
>      > + p = iv;
>      > + while (iv_len > 0) {
>      > + use_len = (iv_len < 16) ? iv_len : 16;
>      > + for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
>      > + gcm_mult(ctx, ctx->y, ctx->y);
>      > + iv_len -= use_len;
>      > + p += use_len;
>      > + }
>      > + for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
>      > + gcm_mult(ctx, ctx->y, ctx->y);
>      > + }
>      > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr))
>     != 0)
>      > + return (ret);
>      > +
>      > + ctx->add_len = add_len;
>      > + p = add;
>      > + while (add_len > 0) {
>      > + use_len = (add_len < 16) ? add_len : 16;
>      > + for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
>      > + gcm_mult(ctx, ctx->buf, ctx->buf);
>      > + add_len -= use_len;
>      > + p += use_len;
>      > + }
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_UPDATE
>      > + *
>      > + * This is called once or more to process bulk plaintext or
>     ciphertext data.
>      > + * We give this some number of bytes of input and it returns the
>     same number
>      > + * of output bytes. If called multiple times (which is fine) all
>     but the final
>      > + * invocation MUST be called with length mod 16 == 0. (Only the
>     final call can
>      > + * have a partial block length of < 128 bits.)
>      > + *
>      > +
>     ******************************************************************************/
>      > +int gcm_update(gcm_context *ctx, // pointer to user-provided GCM
>     context
>      > + size_t length, // length, in bytes, of data to process
>      > + const uchar *input, // pointer to source data
>      > + uchar *output) // pointer to destination data
>      > +{
>      > + int ret; // our error return if the AES encrypt fails
>      > + uchar ectr[16]; // counter-mode cipher output for XORing
>      > + size_t use_len; // byte count to process, up to 16 bytes
>      > + size_t i; // local loop iterator
>      > +
>      > + ctx->len += length; // bump the GCM context's running length count
>      > +
>      > + while (length > 0) {
>      > + // clamp the length to process at 16 bytes
>      > + use_len = (length < 16) ? length : 16;
>      > +
>      > + // increment the context's 128-bit IV||Counter 'y' vector
>      > + for (i = 16; i > 12; i--)
>      > + if (++ctx->y[i - 1] != 0) break;
>      > +
>      > + // encrypt the context's 'y' vector under the established key
>      > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0)
>     return (ret);
>      > +
>      > + // encrypt or decrypt the input to the output
>      > + if (ctx->mode == MG_ENCRYPT) {
>      > + for (i = 0; i < use_len; i++) {
>      > + // XOR the cipher's ouptut vector (ectr) with our input
>      > + output[i] = (uchar) (ectr[i] ^ input[i]);
>      > + // now we mix in our data into the authentication hash.
>      > + // if we're ENcrypting we XOR in the post-XOR (output)
>      > + // results, but if we're DEcrypting we XOR in the input
>      > + // data
>      > + ctx->buf[i] ^= output[i];
>      > + }
>      > + } else {
>      > + for (i = 0; i < use_len; i++) {
>      > + // but if we're DEcrypting we XOR in the input data first,
>      > + // i.e. before saving to ouput data, otherwise if the input
>      > + // and output buffer are the same (inplace decryption) we
>      > + // would not get the correct auth tag
>      >
>      > - a = (uint32_t *) ctx->in;
>      > - a[14] = ctx->bits[0];
>      > - a[15] = ctx->bits[1];
>      > + ctx->buf[i] ^= input[i];
>      >
>      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>      > - mg_byte_reverse((unsigned char *) ctx->buf, 4);
>      > - memcpy(digest, ctx->buf, 16);
>      > - memset((char *) ctx, 0, sizeof(*ctx));
>      > -}
>      > -#endif
>      > + // XOR the cipher's ouptut vector (ectr) with our input
>      > + output[i] = (uchar) (ectr[i] ^ input[i]);
>      > + }
>      > + }
>      > + gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/mqtt.c"
>      > -#endif
>      > + length -= use_len; // drop the remaining byte count to process
>      > + input += use_len; // bump our input pointer forward
>      > + output += use_len; // bump our output pointer forward
>      > + }
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_FINISH
>      > + *
>      > + * This is called once after all calls to GCM_UPDATE to finalize
>     the GCM.
>      > + * It performs the final GHASH to produce the resulting
>     authentication TAG.
>      > + *
>      > +
>     ******************************************************************************/
>      > +int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM
>     context
>      > + uchar *tag, // pointer to buffer which receives the tag
>      > + size_t tag_len) // length, in bytes, of the tag-receiving buf
>      > +{
>      > + uchar work_buf[16];
>      > + uint64_t orig_len = ctx->len * 8;
>      > + uint64_t orig_add_len = ctx->add_len * 8;
>      > + size_t i;
>      >
>      > + if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
>      >
>      > + if (orig_len || orig_add_len) {
>      > + memset(work_buf, 0x00, 16);
>      >
>      > + PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
>      > + PUT_UINT32_BE((orig_add_len), work_buf, 4);
>      > + PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
>      > + PUT_UINT32_BE((orig_len), work_buf, 12);
>      >
>      > + for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
>      > + gcm_mult(ctx, ctx->buf, ctx->buf);
>      > + for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
>      > + }
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_CRYPT_AND_TAG
>      > + *
>      > + * This either encrypts or decrypts the user-provided data and,
>     either
>      > + * way, generates an authentication tag of the requested length.
>     It must be
>      > + * called with a GCM context whose key has already been set with
>     GCM_SETKEY.
>      > + *
>      > + * The user would typically call this explicitly to ENCRYPT a
>     buffer of data
>      > + * and optional associated data, and produce its an
>     authentication tag.
>      > + *
>      > + * To reverse the process the user would typically call the
>     companion
>      > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
>     user-provided
>      > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
>     function
>      > + * to perform its decryption and tag generation, which it then
>     compares.
>      > + *
>      > +
>     ******************************************************************************/
>      > +int gcm_crypt_and_tag(
>      > + gcm_context *ctx, // gcm context with key already setup
>      > + int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
>      > + const uchar *iv, // pointer to the 12-byte initialization vector
>      > + size_t iv_len, // byte length if the IV. should always be 12
>      > + const uchar *add, // pointer to the non-ciphered additional data
>      > + size_t add_len, // byte length of the additional AEAD data
>      > + const uchar *input, // pointer to the cipher data source
>      > + uchar *output, // pointer to the cipher data destination
>      > + size_t length, // byte length of the cipher data
>      > + uchar *tag, // pointer to the tag to be generated
>      > + size_t tag_len) // byte length of the tag to be generated
>      > +{ /*
>      > + assuming that the caller has already invoked gcm_setkey to
>      > + prepare the gcm context with the keying material, we simply
>      > + invoke each of the three GCM sub-functions in turn...
>      > + */
>      > + gcm_start(ctx, mode, iv, iv_len, add, add_len);
>      > + gcm_update(ctx, length, input, output);
>      > + gcm_finish(ctx, tag, tag_len);
>      > + return (0);
>      > +}
>      > +
>      >
>     +/******************************************************************************
>      > + *
>      > + * GCM_ZERO_CTX
>      > + *
>      > + * The GCM context contains both the GCM context and the AES
>     context.
>      > + * This includes keying and key-related material which is security-
>      > + * sensitive, so it MUST be zeroed after use. This function does
>     that.
>      > + *
>      > +
>     ******************************************************************************/
>      > +void gcm_zero_ctx(gcm_context *ctx) {
>      > + // zero the context originally provided to us
>      > + memset(ctx, 0, sizeof(gcm_context));
>      > +}
>      > +//
>      > +// aes-gcm.c
>      > +// Pods
>      > +//
>      > +// Created by Markus Kosmal on 20/11/14.
>      > +//
>      > +//
>      >
>      > +int mg_aes_gcm_encrypt(unsigned char *output, //
>      > + const unsigned char *input, size_t input_length,
>      > + const unsigned char *key, const size_t key_len,
>      > + const unsigned char *iv, const size_t iv_len,
>      > + unsigned char *aead, size_t aead_len, unsigned char *tag,
>      > + const size_t tag_len) {
>      > + int ret = 0; // our return value
>      > + gcm_context ctx; // includes the AES context structure
>      >
>      > + gcm_setkey(&ctx, key, (uint) key_len);
>      >
>      > + ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead,
>     aead_len, input,
>      > + output, input_length, tag, tag_len);
>      >
>      > -#define MQTT_CLEAN_SESSION 0x02
>      > -#define MQTT_HAS_WILL 0x04
>      > -#define MQTT_WILL_RETAIN 0x20
>      > -#define MQTT_HAS_PASSWORD 0x40
>      > -#define MQTT_HAS_USER_NAME 0x80
>      > + gcm_zero_ctx(&ctx);
>      >
>      > -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
>     uint8_t flags,
>      > - uint32_t len) {
>      > - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
>      > - buf[0] = (uint8_t) ((cmd << 4) | flags);
>      > - do {
>      > - *vlen = len % 0x80;
>      > - len /= 0x80;
>      > - if (len > 0) *vlen |= 0x80;
>      > - vlen++;
>      > - } while (len > 0 && vlen < &buf[sizeof(buf)]);
>      > - mg_send(c, buf, (size_t) (vlen - buf));
>      > + return (ret);
>      > }
>      >
>      > -static void mg_send_u16(struct mg_connection *c, uint16_t value) {
>      > - mg_send(c, &value, sizeof(value));
>      > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
>     char *input,
>      > + size_t input_length, const unsigned char *key,
>      > + const size_t key_len, const unsigned char *iv,
>      > + const size_t iv_len) {
>      > + int ret = 0; // our return value
>      > + gcm_context ctx; // includes the AES context structure
>      > +
>      > + size_t tag_len = 0;
>      > + unsigned char *tag_buf = NULL;
>      > +
>      > + gcm_setkey(&ctx, key, (uint) key_len);
>      > +
>      > + ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0,
>     input, output,
>      > + input_length, tag_buf, tag_len);
>      > +
>      > + gcm_zero_ctx(&ctx);
>      > +
>      > + return (ret);
>      > }
>      > +#endif
>      > +// End of aes128 PD
>      >
>      > -void mg_mqtt_login(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts) {
>      > - char rnd[10], client_id[21], zero = 0;
>      > - struct mg_str cid = opts->client_id;
>      > - uint32_t total_len = 7 + 1 + 2 + 2;
>      > - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/tls_builtin.c"
>      > +#endif
>      >
>      > - if (cid.len == 0) {
>      > - mg_random(rnd, sizeof(rnd));
>      > - mg_hex(rnd, sizeof(rnd), client_id);
>      > - client_id[sizeof(client_id) - 1] = '\0';
>      > - cid = mg_str(client_id);
>      > - }
>      >
>      > - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
>     (3.1.1)
>      > - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
>      > - hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3); // Connection
>     flags
>      > - if (opts->user.len > 0) {
>      > - total_len += 2 + (uint32_t) opts->user.len;
>      > - hdr[7] |= MQTT_HAS_USER_NAME;
>      > +
>      > +
>      > +#if MG_TLS == MG_TLS_BUILTIN
>      > +
>      > +/* TLS 1.3 Record Content Type (RFC8446 B.1) */
>      > +#define MG_TLS_CHANGE_CIPHER 20
>      > +#define MG_TLS_ALERT 21
>      > +#define MG_TLS_HANDSHAKE 22
>      > +#define MG_TLS_APP_DATA 23
>      > +#define MG_TLS_HEARTBEAT 24
>      > +
>      > +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
>      > +#define MG_TLS_CLIENT_HELLO 1
>      > +#define MG_TLS_SERVER_HELLO 2
>      > +#define MG_TLS_ENCRYPTED_EXTENSIONS 8
>      > +#define MG_TLS_CERTIFICATE 11
>      > +#define MG_TLS_CERTIFICATE_VERIFY 15
>      > +#define MG_TLS_FINISHED 20
>      > +
>      > +// handshake is re-entrant, so we need to keep track of its
>     state state names
>      > +// refer to RFC8446#A.1
>      > +enum mg_tls_hs_state {
>      > + // Client state machine:
>      > + MG_TLS_STATE_CLIENT_START, // Send ClientHello
>      > + MG_TLS_STATE_CLIENT_WAIT_SH, // Wait for ServerHello
>      > + MG_TLS_STATE_CLIENT_WAIT_EE, // Wait for EncryptedExtensions
>      > + MG_TLS_STATE_CLIENT_WAIT_CERT, // Wait for Certificate
>      > + MG_TLS_STATE_CLIENT_WAIT_CV, // Wait for CertificateVerify
>      > + MG_TLS_STATE_CLIENT_WAIT_FINISHED, // Wait for Finished
>      > + MG_TLS_STATE_CLIENT_CONNECTED, // Done
>      > +
>      > + // Server state machine:
>      > + MG_TLS_STATE_SERVER_START, // Wait for ClientHello
>      > + MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finished
>      > + MG_TLS_STATE_SERVER_CONNECTED // Done
>      > +};
>      > +
>      > +// per-connection TLS data
>      > +struct tls_data {
>      > + enum mg_tls_hs_state state; // keep track of connection
>     handshake progress
>      > +
>      > + struct mg_iobuf send; // For the receive path, we're reusing
>     c->rtls
>      > + struct mg_iobuf recv; // While c->rtls contains full records,
>     recv reuses
>      > + // the same underlying buffer but points at individual
>      > + // decrypted messages
>      > + uint8_t content_type; // Last received record content type
>      > +
>      > + mg_sha256_ctx sha256; // incremental SHA-256 hash for TLS
>     handshake
>      > +
>      > + uint32_t sseq; // server sequence number, used in encryption
>      > + uint32_t cseq; // client sequence number, used in decryption
>      > +
>      > + uint8_t random[32]; // client random from ClientHello
>      > + uint8_t session_id[32]; // client session ID between the
>     handshake states
>      > + uint8_t x25519_cli[32]; // client X25519 key between the
>     handshake states
>      > + uint8_t x25519_sec[32]; // x25519 secret between the handshake
>     states
>      > +
>      > + int skip_verification; // perform checks on server certificate?
>      > + struct mg_str server_cert_der; // server certificate in DER format
>      > + uint8_t server_key[32]; // server EC private key
>      > + char hostname[254]; // server hostname (client extension)
>      > +
>      > + uint8_t certhash[32]; // certificate message hash
>      > + uint8_t pubkey[64]; // server EC public key to verify cert
>      > + uint8_t sighash[32]; // server EC public key to verify cert
>      > +
>      > + // keys for AES encryption
>      > + uint8_t handshake_secret[32];
>      > + uint8_t server_write_key[16];
>      > + uint8_t server_write_iv[12];
>      > + uint8_t server_finished_key[32];
>      > + uint8_t client_write_key[16];
>      > + uint8_t client_write_iv[12];
>      > + uint8_t client_finished_key[32];
>      > +};
>      > +
>      > +#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) |
>     MG_U8P(p)[1]))
>      > +#define MG_LOAD_BE24(p) \
>      > + ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) |
>     MG_U8P(p)[2]))
>      > +#define MG_STORE_BE16(p, n) \
>      > + do { \
>      > + MG_U8P(p)[0] = ((n) >> 8U) & 255; \
>      > + MG_U8P(p)[1] = (n) & 255; \
>      > + } while (0)
>      > +
>      > +#define TLS_RECHDR_SIZE 5 // 1 byte type, 2 bytes version, 2
>     bytes length
>      > +#define TLS_MSGHDR_SIZE 4 // 1 byte type, 3 bytes length
>      > +
>      > +#if 1
>      > +static void mg_ssl_key_log(const char *label, uint8_t
>     client_random[32],
>      > + uint8_t *secret, size_t secretsz) {
>      > + (void) label;
>      > + (void) client_random;
>      > + (void) secret;
>      > + (void) secretsz;
>      > +}
>      > +#else
>      > +#include <stdio.h>
>      > +static void mg_ssl_key_log(const char *label, uint8_t
>     client_random[32],
>      > + uint8_t *secret, size_t secretsz) {
>      > + char *keylogfile = getenv("SSLKEYLOGFILE");
>      > + if (keylogfile == NULL) {
>      > + return;
>      > }
>      > - if (opts->pass.len > 0) {
>      > - total_len += 2 + (uint32_t) opts->pass.len;
>      > - hdr[7] |= MQTT_HAS_PASSWORD;
>      > + FILE *f = fopen(keylogfile, "a");
>      > + fprintf(f, "%s ", label);
>      > + for (int i = 0; i < 32; i++) {
>      > + fprintf(f, "%02x", client_random[i]);
>      > }
>      > - if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
>      > - total_len +=
>      > - 4 + (uint32_t) opts->will_topic.len + (uint32_t)
>     opts->will_message.len;
>      > - hdr[7] |= MQTT_HAS_WILL;
>      > + fprintf(f, " ");
>      > + for (unsigned int i = 0; i < secretsz; i++) {
>      > + fprintf(f, "%02x", secret[i]);
>      > }
>      > - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
>      > - if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
>      > - total_len += (uint32_t) cid.len;
>      > - if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U
>     : 0);
>      > + fprintf(f, "\n");
>      > + fclose(f);
>      > +}
>      > +#endif
>      >
>      > - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
>      > - mg_send(c, hdr, sizeof(hdr));
>      > - // keepalive == 0 means "do not disconnect us!"
>      > - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
>      > +// for derived tls keys we need SHA256([0]*32)
>      > +static uint8_t zeros[32] = {0};
>      > +static uint8_t zeros_sha256_digest[32] = {
>      > + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
>      > + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
>      > + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
>      >
>      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // V5 properties
>      > - mg_send_u16(c, mg_htons((uint16_t) cid.len));
>      > - mg_send(c, cid.ptr, cid.len);
>      > +// helper to hexdump buffers inline
>      > +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t
>     bufsz) {
>      > + MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
>      > +}
>      >
>      > - if (hdr[7] & MQTT_HAS_WILL) {
>      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // will props
>      > - mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
>      > - mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
>      > - mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
>      > - mg_send(c, opts->will_message.ptr, opts->will_message.len);
>      > +// helper utilities to parse ASN.1 DER
>      > +struct mg_der_tlv {
>      > + uint8_t type;
>      > + uint32_t len;
>      > + uint8_t *value;
>      > +};
>      > +
>      > +// parse DER into a TLV record
>      > +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct
>     mg_der_tlv *tlv) {
>      > + if (dersz < 2) {
>      > + return -1;
>      > }
>      > - if (opts->user.len > 0) {
>      > - mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
>      > - mg_send(c, opts->user.ptr, opts->user.len);
>      > + tlv->type = der[0];
>      > + tlv->len = der[1];
>      > + tlv->value = der + 2;
>      > + if (tlv->len > 0x7f) {
>      > + uint32_t i, n = tlv->len - 0x80;
>      > + tlv->len = 0;
>      > + for (i = 0; i < n; i++) {
>      > + tlv->len = (tlv->len << 8) | (der[2 + i]);
>      > + }
>      > + tlv->value = der + 2 + n;
>      > }
>      > - if (opts->pass.len > 0) {
>      > - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
>      > - mg_send(c, opts->pass.ptr, opts->pass.len);
>      > + if (der + dersz < tlv->value + tlv->len) {
>      > + return -1;
>      > }
>      > + return 0;
>      > }
>      >
>      > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
>      > - struct mg_str data, int qos, bool retain) {
>      > - uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 :
>     0)), zero = 0;
>      > - uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
>      > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char
>     *) topic.ptr,
>      > - (int) data.len, (char *) data.ptr));
>      > - if (qos > 0) len += 2;
>      > - if (c->is_mqtt5) len++;
>      > - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
>      > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
>      > - mg_send(c, topic.ptr, topic.len);
>      > - if (qos > 0) {
>      > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>      > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>      > +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid,
>     size_t oidsz,
>      > + struct mg_der_tlv *tlv) {
>      > + uint8_t *p, *end;
>      > + struct mg_der_tlv child = {0, 0, NULL};
>      > + if (mg_der_to_tlv(der, dersz, tlv) < 0) {
>      > + return -1; // invalid DER
>      > + } else if (tlv->type == 6) { // found OID, check value
>      > + return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
>      > + } else if ((tlv->type & 0x20) == 0) {
>      > + return 0; // Primitive, but not OID: not found
>      > + }
>      > + // Constructed object: scan children
>      > + p = tlv->value;
>      > + end = tlv->value + tlv->len;
>      > + while (end > p) {
>      > + int r;
>      > + mg_der_to_tlv(p, (size_t) (end - p), &child);
>      > + r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
>      > + if (r < 0) return -1; // error
>      > + if (r > 0) return 1; // found OID!
>      > + p = child.value + child.len;
>      > }
>      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
>      > - mg_send(c, data.ptr, data.len);
>      > + return 0; // not found
>      > }
>      >
>      > -void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic,
>     int qos) {
>      > - uint8_t qos_ = qos & 3, zero = 0;
>      > - uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5
>     ? 1 : 0);
>      > - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
>      > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>      > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
>      > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
>      > - mg_send(c, topic.ptr, topic.len);
>      > - mg_send(c, &qos_, sizeof(qos_));
>      > +// Did we receive a full TLS record in the c->rtls buffer?
>      > +static bool mg_tls_got_record(struct mg_connection *c) {
>      > + return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
>      > + c->rtls.len >=
>      > + (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
>      > }
>      >
>      > -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
>      > - struct mg_mqtt_message *m) {
>      > - uint8_t lc = 0, *p, *end;
>      > - uint32_t n = 0, len_len = 0;
>      > +// Remove a single TLS record from the recv buffer
>      > +static void mg_tls_drop_record(struct mg_connection *c) {
>      > + struct mg_iobuf *rio = &c->rtls;
>      > + uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
>      > + mg_iobuf_del(rio, 0, n);
>      > +}
>      >
>      > - memset(m, 0, sizeof(*m));
>      > - m->dgram.ptr = (char *) buf;
>      > - if (len < 2) return MQTT_INCOMPLETE;
>      > - m->cmd = (uint8_t) (buf[0] >> 4);
>      > - m->qos = (buf[0] >> 1) & 3;
>      > +// Remove a single TLS message from decrypted buffer, remove the
>     wrapping
>      > +// record if it was the last message within a record
>      > +static void mg_tls_drop_message(struct mg_connection *c) {
>      > + uint32_t len;
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (tls->recv.len == 0) {
>      > + return;
>      > + }
>      > + len = MG_LOAD_BE24(tls->recv.buf + 1);
>      > + mg_sha256_update(&tls->sha256, tls->recv.buf, len +
>     TLS_MSGHDR_SIZE);
>      > + tls->recv.buf += len + TLS_MSGHDR_SIZE;
>      > + tls->recv.len -= len + TLS_MSGHDR_SIZE;
>      > + if (tls->recv.len == 0) {
>      > + mg_tls_drop_record(c);
>      > + }
>      > +}
>      >
>      > - n = len_len = 0;
>      > - p = (uint8_t *) buf + 1;
>      > - while ((size_t) (p - buf) < len) {
>      > - lc = *((uint8_t *) p++);
>      > - n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
>      > - len_len++;
>      > - if (!(lc & 0x80)) break;
>      > - if (len_len >= 4) return MQTT_MALFORMED;
>      > +// TLS1.3 secret derivation based on the key label
>      > +static void mg_tls_derive_secret(const char *label, uint8_t
>     *key, size_t keysz,
>      > + uint8_t *data, size_t datasz, uint8_t *hash,
>      > + size_t hashsz) {
>      > + size_t labelsz = strlen(label);
>      > + uint8_t secret[32];
>      > + uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
>      > + // TODO: assert lengths of label, key, data and hash
>      > + if (labelsz > 0) memmove(packed + 3, label, labelsz);
>      > + packed[3 + labelsz] = (uint8_t) datasz;
>      > + if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
>      > + packed[4 + labelsz + datasz] = 1;
>      > +
>      > + mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
>      > + memmove(hash, secret, hashsz);
>      > +}
>      > +
>      > +// at this point we have x25519 shared secret, we can generate a
>     set of derived
>      > +// handshake encryption keys
>      > +static void mg_tls_generate_handshake_keys(struct mg_connection
>     *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > +
>      > + mg_sha256_ctx sha256;
>      > + uint8_t early_secret[32];
>      > + uint8_t pre_extract_secret[32];
>      > + uint8_t hello_hash[32];
>      > + uint8_t server_hs_secret[32];
>      > + uint8_t client_hs_secret[32];
>      > +
>      > + mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
>      > + mg_tls_derive_secret("tls13 derived", early_secret, 32,
>     zeros_sha256_digest,
>      > + 32, pre_extract_secret, 32);
>      > + mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
>      > + sizeof(pre_extract_secret), tls->x25519_sec,
>      > + sizeof(tls->x25519_sec));
>      > + mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
>      > +
>      > + // mg_sha256_final is not idempotent, need to copy sha256
>     context to calculate
>      > + // the digest
>      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>      > + mg_sha256_final(hello_hash, &sha256);
>      > +
>      > + mg_tls_hexdump("hello hash", hello_hash, 32);
>      > + // derive keys needed for the rest of the handshake
>      > + mg_tls_derive_secret("tls13 s hs traffic",
>     tls->handshake_secret, 32,
>      > + hello_hash, 32, server_hs_secret, 32);
>      > + mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
>      > + tls->server_write_key, 16);
>      > + mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
>      > + tls->server_write_iv, 12);
>      > + mg_tls_derive_secret("tls13 finished", server_hs_secret, 32,
>     NULL, 0,
>      > + tls->server_finished_key, 32);
>      > +
>      > + mg_tls_derive_secret("tls13 c hs traffic",
>     tls->handshake_secret, 32,
>      > + hello_hash, 32, client_hs_secret, 32);
>      > + mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
>      > + tls->client_write_key, 16);
>      > + mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
>      > + tls->client_write_iv, 12);
>      > + mg_tls_derive_secret("tls13 finished", client_hs_secret, 32,
>     NULL, 0,
>      > + tls->client_finished_key, 32);
>      > +
>      > + mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
>      > + mg_tls_hexdump("s key", tls->server_write_key, 16);
>      > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
>      > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
>      > + mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
>      > + mg_tls_hexdump("c key", tls->client_write_key, 16);
>      > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
>      > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
>      > +
>      > + mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
>      > + server_hs_secret, 32);
>      > + mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
>      > + client_hs_secret, 32);
>      > +}
>      > +
>      > +static void mg_tls_generate_application_keys(struct
>     mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + uint8_t hash[32];
>      > + uint8_t premaster_secret[32];
>      > + uint8_t master_secret[32];
>      > + uint8_t server_secret[32];
>      > + uint8_t client_secret[32];
>      > +
>      > + mg_sha256_ctx sha256;
>      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>      > + mg_sha256_final(hash, &sha256);
>      > +
>      > + mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
>      > + zeros_sha256_digest, 32, premaster_secret, 32);
>      > + mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
>      > +
>      > + mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32,
>     hash, 32,
>      > + server_secret, 32);
>      > + mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
>      > + tls->server_write_key, 16);
>      > + mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
>      > + tls->server_write_iv, 12);
>      > + mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32,
>     hash, 32,
>      > + client_secret, 32);
>      > + mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
>      > + tls->client_write_key, 16);
>      > + mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
>      > + tls->client_write_iv, 12);
>      > +
>      > + mg_tls_hexdump("s ap traffic", server_secret, 32);
>      > + mg_tls_hexdump("s key", tls->server_write_key, 16);
>      > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
>      > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
>      > + mg_tls_hexdump("c ap traffic", client_secret, 32);
>      > + mg_tls_hexdump("c key", tls->client_write_key, 16);
>      > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
>      > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
>      > + tls->sseq = tls->cseq = 0;
>      > +
>      > + mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random,
>     server_secret, 32);
>      > + mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random,
>     client_secret, 32);
>      > +}
>      > +
>      > +// AES GCM encryption of the message + put encoded data into the
>     write buffer
>      > +static void mg_tls_encrypt(struct mg_connection *c, const
>     uint8_t *msg,
>      > + size_t msgsz, uint8_t msgtype) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *wio = &tls->send;
>      > + uint8_t *outmsg;
>      > + uint8_t *tag;
>      > + size_t encsz = msgsz + 16 + 1;
>      > + uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
>      > + (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
>      > + uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
>      > + (uint8_t) ((encsz >> 8) & 0xff),
>      > + (uint8_t) (encsz & 0xff)};
>      > + uint8_t nonce[12];
>      > +
>      > + mg_gcm_initialize();
>      > +
>      > + if (c->is_client) {
>      > + memmove(nonce, tls->client_write_iv,
>     sizeof(tls->client_write_iv));
>      > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
>      > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
>      > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
>      > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
>      > + } else {
>      > + memmove(nonce, tls->server_write_iv,
>     sizeof(tls->server_write_iv));
>      > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
>      > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
>      > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
>      > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
>      > }
>      > - end = p + n;
>      > - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
>      > - m->dgram.len = (size_t) (end - buf);
>      >
>      > - switch (m->cmd) {
>      > - case MQTT_CMD_CONNACK:
>      > - if (end - p < 2) return MQTT_MALFORMED;
>      > - m->ack = p[1];
>      > - break;
>      > - case MQTT_CMD_PUBACK:
>      > - case MQTT_CMD_PUBREC:
>      > - case MQTT_CMD_PUBREL:
>      > - case MQTT_CMD_PUBCOMP:
>      > - case MQTT_CMD_SUBSCRIBE:
>      > - case MQTT_CMD_SUBACK:
>      > - case MQTT_CMD_UNSUBSCRIBE:
>      > - case MQTT_CMD_UNSUBACK:
>      > - if (p + 2 > end) return MQTT_MALFORMED;
>      > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > - p += 2;
>      > - break;
>      > - case MQTT_CMD_PUBLISH: {
>      > - if (p + 2 > end) return MQTT_MALFORMED;
>      > - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > - m->topic.ptr = (char *) p + 2;
>      > - p += 2 + m->topic.len;
>      > - if (p > end) return MQTT_MALFORMED;
>      > - if (m->qos > 0) {
>      > - if (p + 2 > end) return MQTT_MALFORMED;
>      > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>      > - p += 2;
>      > - }
>      > - if (p > end) return MQTT_MALFORMED;
>      > - if (version == 5 && p + 2 < end) p += 1 + p[0]; // Skip options
>      > - if (p > end) return MQTT_MALFORMED;
>      > - m->data.ptr = (char *) p;
>      > - m->data.len = (size_t) (end - p);
>      > - break;
>      > + mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
>      > + mg_iobuf_resize(wio, wio->len + encsz);
>      > + outmsg = wio->buf + wio->len;
>      > + tag = wio->buf + wio->len + msgsz + 1;
>      > + memmove(outmsg, msg, msgsz);
>      > + outmsg[msgsz] = msgtype;
>      > + if (c->is_client) {
>      > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
>     tls->client_write_key,
>      > + sizeof(tls->client_write_key), nonce, sizeof(nonce),
>      > + associated_data, sizeof(associated_data), tag, 16);
>      > + tls->cseq++;
>      > + } else {
>      > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
>     tls->server_write_key,
>      > + sizeof(tls->server_write_key), nonce, sizeof(nonce),
>      > + associated_data, sizeof(associated_data), tag, 16);
>      > + tls->sseq++;
>      > + }
>      > + wio->len += encsz;
>      > +}
>      > +
>      > +// read an encrypted record, decrypt it in place
>      > +static int mg_tls_recv_record(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *rio = &c->rtls;
>      > + uint16_t msgsz;
>      > + uint8_t *msg;
>      > + uint8_t nonce[12];
>      > + int r;
>      > + if (tls->recv.len > 0) {
>      > + return 0; /* some data from previous record is still present */
>      > + }
>      > + for (;;) {
>      > + if (!mg_tls_got_record(c)) {
>      > + return MG_IO_WAIT;
>      > }
>      > - default:
>      > + if (rio->buf[0] == MG_TLS_APP_DATA) {
>      > break;
>      > + } else if (rio->buf[0] ==
>      > + MG_TLS_CHANGE_CIPHER) { // Skip ChangeCipher messages
>      > + mg_tls_drop_record(c);
>      > + } else if (rio->buf[0] == MG_TLS_ALERT) { // Skip Alerts
>      > + MG_INFO(("TLS ALERT packet received"));
>      > + mg_tls_drop_record(c);
>      > + } else {
>      > + mg_error(c, "unexpected packet");
>      > + return -1;
>      > + }
>      > }
>      > - return MQTT_OK;
>      > +
>      > + mg_gcm_initialize();
>      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>      > + msg = rio->buf + 5;
>      > + if (c->is_client) {
>      > + memmove(nonce, tls->server_write_iv,
>     sizeof(tls->server_write_iv));
>      > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
>      > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
>      > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
>      > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
>      > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
>      > + sizeof(tls->server_write_key), nonce, sizeof(nonce));
>      > + tls->sseq++;
>      > + } else {
>      > + memmove(nonce, tls->client_write_iv,
>     sizeof(tls->client_write_iv));
>      > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
>      > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
>      > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
>      > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
>      > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
>      > + sizeof(tls->client_write_key), nonce, sizeof(nonce));
>      > + tls->cseq++;
>      > + }
>      > + r = msgsz - 16 - 1;
>      > + tls->content_type = msg[msgsz - 16 - 1];
>      > + tls->recv.buf = msg;
>      > + tls->recv.size = tls->recv.len = msgsz - 16 - 1;
>      > + return r;
>      > }
>      >
>      > -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
>      > - void *fn_data) {
>      > - if (ev == MG_EV_READ) {
>      > - for (;;) {
>      > - uint8_t version = c->is_mqtt5 ? 5 : 4;
>      > - struct mg_mqtt_message mm;
>      > - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
>      > - if (rc == MQTT_MALFORMED) {
>      > - MG_ERROR(("%lu MQTT malformed message", c->id));
>      > - c->is_closing = 1;
>      > - break;
>      > - } else if (rc == MQTT_OK) {
>      > - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
>      > - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
>      > - switch (mm.cmd) {
>      > - case MQTT_CMD_CONNACK:
>      > - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
>      > - if (mm.ack == 0) {
>      > - MG_DEBUG(("%lu Connected", c->id));
>      > - } else {
>      > - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
>      > - c->is_closing = 1;
>      > - }
>      > - break;
>      > - case MQTT_CMD_PUBLISH: {
>      > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
>      > - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
>      > - if (mm.qos > 0) {
>      > - uint16_t id = mg_htons(mm.id <http://mm.id>);
>      > - mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
>      > - mg_send(c, &id, sizeof(id));
>      > - }
>      > - mg_call(c, MG_EV_MQTT_MSG, &mm);
>      > - break;
>      > - }
>      > - }
>      > - mg_call(c, MG_EV_MQTT_CMD, &mm);
>      > - mg_iobuf_del(&c->recv, 0, mm.dgram.len);
>      > - } else {
>      > - break;
>      > +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
>      > + uint8_t hash[32]) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + uint8_t sig_content[130] = {
>      > + " "
>      > + " "
>      > + "TLS 1.3, server CertificateVerify\0"};
>      > + mg_sha256_ctx sha256;
>      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>      > + mg_sha256_final(sig_content + 98, &sha256);
>      > +
>      > + mg_sha256_init(&sha256);
>      > + mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
>      > + mg_sha256_final(hash, &sha256);
>      > +}
>      > +
>      > +// read and parse ClientHello record
>      > +static int mg_tls_server_recv_hello(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *rio = &c->rtls;
>      > + uint8_t session_id_len;
>      > + uint16_t j;
>      > + uint16_t cipher_suites_len;
>      > + uint16_t ext_len;
>      > + uint8_t *ext;
>      > + uint16_t msgsz;
>      > +
>      > + if (!mg_tls_got_record(c)) {
>      > + return MG_IO_WAIT;
>      > + }
>      > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
>     MG_TLS_CLIENT_HELLO) {
>      > + mg_error(c, "not a client hello packet");
>      > + return -1;
>      > + }
>      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>      > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
>      > + // store client random
>      > + memmove(tls->random, rio->buf + 11, sizeof(tls->random));
>      > + // store session_id
>      > + session_id_len = rio->buf[43];
>      > + if (session_id_len == sizeof(tls->session_id)) {
>      > + memmove(tls->session_id, rio->buf + 44, session_id_len);
>      > + } else if (session_id_len != 0) {
>      > + MG_INFO(("bad session id len"));
>      > + }
>      > + cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
>      > + ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len +
>     cipher_suites_len);
>      > + ext = rio->buf + 50 + session_id_len + cipher_suites_len;
>      > + for (j = 0; j < ext_len;) {
>      > + uint16_t k;
>      > + uint16_t key_exchange_len;
>      > + uint8_t *key_exchange;
>      > + uint16_t n = MG_LOAD_BE16(ext + j + 2);
>      > + if (ext[j] != 0x00 ||
>      > + ext[j + 1] != 0x33) { // not a key share extension, ignore
>      > + j += (uint16_t) (n + 4);
>      > + continue;
>      > + }
>      > + key_exchange_len = MG_LOAD_BE16(ext + j + 5);
>      > + key_exchange = ext + j + 6;
>      > + for (k = 0; k < key_exchange_len;) {
>      > + uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
>      > + if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1]
>     == 0x1d) {
>      > + memmove(tls->x25519_cli, key_exchange + k + 4, m);
>      > + mg_tls_drop_record(c);
>      > + return 0;
>      > }
>      > + k += (uint16_t) (m + 4);
>      > }
>      > + j += (uint16_t) (n + 4);
>      > }
>      > - (void) ev_data;
>      > - (void) fn_data;
>      > -}
>      > -
>      > -void mg_mqtt_ping(struct mg_connection *nc) {
>      > - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
>      > -}
>      > -
>      > -void mg_mqtt_pong(struct mg_connection *nc) {
>      > - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
>      > + mg_error(c, "bad client hello");
>      > + return -1;
>      > }
>      >
>      > -void mg_mqtt_disconnect(struct mg_connection *nc) {
>      > - mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
>      > -}
>      > +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
>      > +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
>      > +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
>      > +
>      > +// put ServerHello record into wio buffer
>      > +static void mg_tls_server_send_hello(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *wio = &tls->send;
>      > +
>      > + uint8_t msg_server_hello[122] = {
>      > + // server hello, tls 1.2
>      > + 0x02,
>      > + 0x00,
>      > + 0x00,
>      > + 0x76,
>      > + 0x03,
>      > + 0x03,
>      > + // random (32 bytes)
>      > + PLACEHOLDER_32B,
>      > + // session ID length + session ID (32 bytes)
>      > + 0x20,
>      > + PLACEHOLDER_32B,
>      > +#if defined(CHACHA20) && CHACHA20
>      > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
>      > + 0x13,
>      > + 0x03,
>      > + 0x00,
>      > +#else
>      > + // TLS_AES_128_GCM_SHA256 + no compression
>      > + 0x13,
>      > + 0x01,
>      > + 0x00,
>      > +#endif
>      > + // extensions + keyshare
>      > + 0x00,
>      > + 0x2e,
>      > + 0x00,
>      > + 0x33,
>      > + 0x00,
>      > + 0x24,
>      > + 0x00,
>      > + 0x1d,
>      > + 0x00,
>      > + 0x20,
>      > + // x25519 keyshare
>      > + PLACEHOLDER_32B,
>      > + // supported versions (tls1.3 == 0x304)
>      > + 0x00,
>      > + 0x2b,
>      > + 0x00,
>      > + 0x02,
>      > + 0x03,
>      > + 0x04
>      > + };
>      >
>      > -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
>     char *url,
>      > - const struct mg_mqtt_opts *opts,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>      > - if (c != NULL) {
>      > - struct mg_mqtt_opts empty;
>      > - memset(&empty, 0, sizeof(empty));
>      > - mg_mqtt_login(c, opts == NULL ? &empty : opts);
>      > - c->pfn = mqtt_cb;
>      > + // calculate keyshare
>      > + uint8_t x25519_pub[X25519_BYTES];
>      > + uint8_t x25519_prv[X25519_BYTES];
>      > + mg_random(x25519_prv, sizeof(x25519_prv));
>      > + mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
>      > + mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
>      > + mg_tls_hexdump("s x25519 sec", tls->x25519_sec,
>     sizeof(tls->x25519_sec));
>      > +
>      > + // fill in the gaps: random + session ID + keyshare
>      > + memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
>      > + memmove(msg_server_hello + 39, tls->session_id,
>     sizeof(tls->session_id));
>      > + memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
>      > +
>      > + // server hello message
>      > + mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
>      > + mg_iobuf_add(wio, wio->len, msg_server_hello,
>     sizeof(msg_server_hello));
>      > + mg_sha256_update(&tls->sha256, msg_server_hello,
>     sizeof(msg_server_hello));
>      > +
>      > + // change cipher message
>      > + mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
>      > +}
>      > +
>      > +static void mg_tls_server_send_ext(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + // server extensions
>      > + uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
>      > + mg_sha256_update(&tls->sha256, ext, sizeof(ext));
>      > + mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
>      > +}
>      > +
>      > +static void mg_tls_server_send_cert(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + // server DER certificate (empty)
>      > + size_t n = tls->server_cert_der.len;
>      > + uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
>      > + if (cert == NULL) {
>      > + mg_error(c, "tls cert oom");
>      > + return;
>      > }
>      > - return c;
>      > -}
>      > + cert[0] = 0x0b; // handshake header
>      > + cert[1] = (uint8_t) (((n + 9) >> 16) & 255U); // 3 bytes:
>     payload length
>      > + cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
>      > + cert[3] = (uint8_t) ((n + 9) & 255U);
>      > + cert[4] = 0; // request context
>      > + cert[5] = (uint8_t) (((n + 5) >> 16) & 255U); // 3 bytes: cert
>     (s) length
>      > + cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
>      > + cert[7] = (uint8_t) ((n + 5) & 255U);
>      > + cert[8] =
>      > + (uint8_t) (((n) >> 16) & 255U); // 3 bytes: first (and only)
>     cert len
>      > + cert[9] = (uint8_t) (((n) >> 8) & 255U);
>      > + cert[10] = (uint8_t) (n & 255U);
>      > + // bytes 11+ are certificate in DER format
>      > + memmove(cert + 11, tls->server_cert_der.buf, n);
>      > + cert[11 + n] = cert[12 + n] = 0; // certificate extensions (none)
>      > + mg_sha256_update(&tls->sha256, cert, 13 + n);
>      > + mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
>      > + free(cert);
>      > +}
>      > +
>      > +// type adapter between uECC hash context and our sha256
>     implementation
>      > +typedef struct SHA256_HashContext {
>      > + MG_UECC_HashContext uECC;
>      > + mg_sha256_ctx ctx;
>      > +} SHA256_HashContext;
>      > +
>      > +static void init_SHA256(const MG_UECC_HashContext *base) {
>      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>      > + mg_sha256_init(&c->ctx);
>      > +}
>      > +
>      > +static void update_SHA256(const MG_UECC_HashContext *base,
>      > + const uint8_t *message, unsigned message_size) {
>      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>      > + mg_sha256_update(&c->ctx, message, message_size);
>      > +}
>      > +static void finish_SHA256(const MG_UECC_HashContext *base,
>      > + uint8_t *hash_result) {
>      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>      > + mg_sha256_final(hash_result, &c->ctx);
>      > +}
>      > +
>      > +static void mg_tls_server_send_cert_verify(struct mg_connection
>     *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + // server certificate verify packet
>      > + uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00,
>     0x00};
>      > + size_t sigsz, verifysz = 0;
>      > + uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
>      > + struct SHA256_HashContext ctx = {
>      > + {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
>      > + {{0}, 0, 0, {0}}};
>      > + int neg1, neg2;
>      > + uint8_t sig[64] = {0};
>      > +
>      > + mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
>      > +
>      > + mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash),
>     &ctx.uECC,
>      > + sig, mg_uecc_secp256r1());
>      > +
>      > + neg1 = !!(sig[0] & 0x80);
>      > + neg2 = !!(sig[32] & 0x80);
>      > + verify[8] = 0x30; // ASN.1 SEQUENCE
>      > + verify[9] = (uint8_t) (68 + neg1 + neg2);
>      > + verify[10] = 0x02; // ASN.1 INTEGER
>      > + verify[11] = (uint8_t) (32 + neg1);
>      > + memmove(verify + 12 + neg1, sig, 32);
>      > + verify[12 + 32 + neg1] = 0x02; // ASN.1 INTEGER
>      > + verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
>      > + memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
>      > +
>      > + sigsz = (size_t) (70 + neg1 + neg2);
>      > + verifysz = 8U + sigsz;
>      > + verify[3] = (uint8_t) (sigsz + 4);
>      > + verify[7] = (uint8_t) sigsz;
>      > +
>      > + mg_sha256_update(&tls->sha256, verify, verifysz);
>      > + mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
>      > +}
>      > +
>      > +static void mg_tls_server_send_finish(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *wio = &tls->send;
>      > + mg_sha256_ctx sha256;
>      > + uint8_t hash[32];
>      > + uint8_t finish[36] = {0x14, 0, 0, 32};
>      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>      > + mg_sha256_final(hash, &sha256);
>      > + mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash,
>     32);
>      > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
>      > + mg_io_send(c, wio->buf, wio->len);
>      > + wio->len = 0;
>      > +
>      > + mg_sha256_update(&tls->sha256, finish, sizeof(finish));
>      > +}
>      > +
>      > +static int mg_tls_server_recv_finish(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + // we have to backup sha256 value to restore it later, since
>     Finished record
>      > + // is exceptional and is not supposed to be added to the
>     rolling hash
>      > + // calculation.
>      > + mg_sha256_ctx sha256 = tls->sha256;
>      > + if (mg_tls_recv_record(c) < 0) {
>      > + return -1;
>      > + }
>      > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
>      > + mg_error(c, "expected Finish but got msg 0x%02x",
>     tls->recv.buf[0]);
>      > + return -1;
>      > + }
>      > + mg_tls_drop_message(c);
>      >
>      > -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>     char *url,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>      > - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
>      > - return c;
>      > + // restore hash
>      > + tls->sha256 = sha256;
>      > + return 0;
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/net.c"
>      > +static void mg_tls_client_send_hello(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *wio = &tls->send;
>      > +
>      > + const char *hostname = tls->hostname;
>      > + size_t hostnamesz = strlen(tls->hostname);
>      > + uint8_t x25519_pub[X25519_BYTES];
>      > +
>      > + uint8_t msg_client_hello[162 + 32] = {
>      > + // TLS Client Hello header reported as TLS1.2 (5)
>      > + 0x16,
>      > + 0x03,
>      > + 0x01,
>      > + 0x00,
>      > + 0xfe,
>      > + // server hello, tls 1.2 (6)
>      > + 0x01,
>      > + 0x00,
>      > + 0x00,
>      > + 0x8c,
>      > + 0x03,
>      > + 0x03,
>      > + // random (32 bytes)
>      > + PLACEHOLDER_32B,
>      > + // session ID length + session ID (32 bytes)
>      > + 0x20,
>      > + PLACEHOLDER_32B,
>      > +#if defined(CHACHA20) && CHACHA20
>      > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
>      > + 0x13,
>      > + 0x03,
>      > + 0x00,
>      > +#else
>      > + 0x00,
>      > + 0x02, // size = 2 bytes
>      > + 0x13,
>      > + 0x01, // TLS_AES_128_GCM_SHA256
>      > + 0x01,
>      > + 0x00, // no compression
>      > #endif
>      >
>      > + // extensions + keyshare
>      > + 0x00,
>      > + 0xfe,
>      > + // x25519 keyshare
>      > + 0x00,
>      > + 0x33,
>      > + 0x00,
>      > + 0x26,
>      > + 0x00,
>      > + 0x24,
>      > + 0x00,
>      > + 0x1d,
>      > + 0x00,
>      > + 0x20,
>      > + PLACEHOLDER_32B,
>      > + // supported groups (x25519)
>      > + 0x00,
>      > + 0x0a,
>      > + 0x00,
>      > + 0x04,
>      > + 0x00,
>      > + 0x02,
>      > + 0x00,
>      > + 0x1d,
>      > + // supported versions (tls1.3 == 0x304)
>      > + 0x00,
>      > + 0x2b,
>      > + 0x00,
>      > + 0x03,
>      > + 0x02,
>      > + 0x03,
>      > + 0x04,
>      > + // session ticket (none)
>      > + 0x00,
>      > + 0x23,
>      > + 0x00,
>      > + 0x00,
>      > + // signature algorithms (we don't care, so list all the common
>     ones)
>      > + 0x00,
>      > + 0x0d,
>      > + 0x00,
>      > + 0x24,
>      > + 0x00,
>      > + 0x22,
>      > + 0x04,
>      > + 0x03,
>      > + 0x05,
>      > + 0x03,
>      > + 0x06,
>      > + 0x03,
>      > + 0x08,
>      > + 0x07,
>      > + 0x08,
>      > + 0x08,
>      > + 0x08,
>      > + 0x1a,
>      > + 0x08,
>      > + 0x1b,
>      > + 0x08,
>      > + 0x1c,
>      > + 0x08,
>      > + 0x09,
>      > + 0x08,
>      > + 0x0a,
>      > + 0x08,
>      > + 0x0b,
>      > + 0x08,
>      > + 0x04,
>      > + 0x08,
>      > + 0x05,
>      > + 0x08,
>      > + 0x06,
>      > + 0x04,
>      > + 0x01,
>      > + 0x05,
>      > + 0x01,
>      > + 0x06,
>      > + 0x01,
>      > + // server name
>      > + 0x00,
>      > + 0x00,
>      > + 0x00,
>      > + 0xfe,
>      > + 0x00,
>      > + 0xfe,
>      > + 0x00,
>      > + 0x00,
>      > + 0xfe
>      > + };
>      >
>      > + // patch ClientHello with correct hostname length + offset:
>      > + MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
>      > + MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
>      > + MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
>      > + MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
>      > + MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
>      > + MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
>      > +
>      > + // calculate keyshare
>      > + mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
>      > + mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
>      > +
>      > + // fill in the gaps: random + session ID + keyshare
>      > + mg_random(tls->session_id, sizeof(tls->session_id));
>      > + mg_random(tls->random, sizeof(tls->random));
>      > + memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
>      > + memmove(msg_client_hello + 44, tls->session_id,
>     sizeof(tls->session_id));
>      > + memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
>      > +
>      > + // server hello message
>      > + mg_iobuf_add(wio, wio->len, msg_client_hello,
>     sizeof(msg_client_hello));
>      > + mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
>      > + mg_sha256_update(&tls->sha256, msg_client_hello + 5,
>      > + sizeof(msg_client_hello) - 5);
>      > + mg_sha256_update(&tls->sha256, (uint8_t *) hostname,
>     strlen(hostname));
>      > +
>      > + // change cipher message
>      > + mg_iobuf_add(wio, wio->len, (const char *)
>     "\x14\x03\x03\x00\x01\x01", 6);
>      > + mg_io_send(c, wio->buf, wio->len);
>      > + wio->len = 0;
>      > +}
>      > +
>      > +static int mg_tls_client_recv_hello(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *rio = &c->rtls;
>      > + uint16_t msgsz;
>      > + uint8_t *ext;
>      > + uint16_t ext_len;
>      > + int j;
>      > +
>      > + if (!mg_tls_got_record(c)) {
>      > + return MG_IO_WAIT;
>      > + }
>      > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
>     MG_TLS_SERVER_HELLO) {
>      > + if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
>      > + mg_error(c, "tls alert %d", rio->buf[6]);
>      > + return -1;
>      > + }
>      > + MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0],
>     rio->buf[5]));
>      > + mg_error(c, "not a server hello packet");
>      > + return -1;
>      > + }
>      >
>      > -
>      > -
>      > -
>      > -
>      > -size_t mg_vprintf(struct mg_connection *c, const char *fmt,
>     va_list *ap) {
>      > - size_t old = c->send.len;
>      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>      > - return c->send.len - old;
>      > -}
>      > -
>      > -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
>      > - size_t len = 0;
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - len = mg_vprintf(c, fmt, &ap);
>      > - va_end(ap);
>      > - return len;
>      > -}
>      > -
>      > -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
>      > - if (mg_vcasecmp(&str, "localhost") != 0) return false;
>      > - addr->ip = mg_htonl(0x7f000001);
>      > - addr->is_ip6 = false;
>      > - return true;
>      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>      > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
>      > +
>      > + ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
>      > + ext = rio->buf + 5 + 39 + 32 + 3 + 2;
>      > +
>      > + for (j = 0; j < ext_len;) {
>      > + uint16_t ext_type = MG_LOAD_BE16(ext + j);
>      > + uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
>      > + uint16_t group;
>      > + uint8_t *key_exchange;
>      > + uint16_t key_exchange_len;
>      > + if (ext_type != 0x0033) { // not a key share extension, ignore
>      > + j += (uint16_t) (ext_len2 + 4);
>      > + continue;
>      > + }
>      > + group = MG_LOAD_BE16(ext + j + 4);
>      > + if (group != 0x001d) {
>      > + mg_error(c, "bad key exchange group");
>      > + return -1;
>      > + }
>      > + key_exchange_len = MG_LOAD_BE16(ext + j + 6);
>      > + key_exchange = ext + j + 8;
>      > + if (key_exchange_len != 32) {
>      > + mg_error(c, "bad key exchange length");
>      > + return -1;
>      > + }
>      > + mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
>      > + mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
>      > + mg_tls_drop_record(c);
>      > + /* generate handshake keys */
>      > + mg_tls_generate_handshake_keys(c);
>      > + return 0;
>      > + }
>      > + mg_error(c, "bad client hello");
>      > + return -1;
>      > }
>      >
>      > -static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
>      > - if (str.len > 0) return false;
>      > - addr->ip = 0;
>      > - addr->is_ip6 = false;
>      > - return true;
>      > +static int mg_tls_client_recv_ext(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (mg_tls_recv_record(c) < 0) {
>      > + return -1;
>      > + }
>      > + if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
>      > + mg_error(c, "expected server extensions but got msg 0x%02x",
>      > + tls->recv.buf[0]);
>      > + return -1;
>      > + }
>      > + mg_tls_drop_message(c);
>      > + return 0;
>      > }
>      >
>      > -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
>      > - uint8_t data[4] = {0, 0, 0, 0};
>      > - size_t i, num_dots = 0;
>      > - for (i = 0; i < str.len; i++) {
>      > - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>      > - int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
>      > - if (octet > 255) return false;
>      > - data[num_dots] = (uint8_t) octet;
>      > - } else if (str.ptr[i] == '.') {
>      > - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return
>     false;
>      > - num_dots++;
>      > - } else {
>      > - return false;
>      > - }
>      > +static int mg_tls_client_recv_cert(struct mg_connection *c) {
>      > + uint8_t *cert;
>      > + uint32_t certsz;
>      > + struct mg_der_tlv oid, pubkey, seq, subj;
>      > + int subj_match = 0;
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (mg_tls_recv_record(c) < 0) {
>      > + return -1;
>      > + }
>      > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
>      > + mg_error(c, "expected server certificate but got msg 0x%02x",
>      > + tls->recv.buf[0]);
>      > + return -1;
>      > + }
>      > + if (tls->skip_verification) {
>      > + mg_tls_drop_message(c);
>      > + return 0;
>      > }
>      > - if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
>      > - memcpy(&addr->ip, data, sizeof(data));
>      > - addr->is_ip6 = false;
>      > - return true;
>      > -}
>      >
>      > -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
>      > - int i;
>      > - if (str.len < 14) return false;
>      > - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] !=
>     ':') return false;
>      > - for (i = 2; i < 6; i++) {
>      > - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
>      > + if (tls->recv.len < 11) {
>      > + mg_error(c, "certificate list too short");
>      > + return -1;
>      > }
>      > - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return
>     false;
>      > - memset(addr->ip6, 0, sizeof(addr->ip6));
>      > - addr->ip6[10] = addr->ip6[11] = 255;
>      > - memcpy(&addr->ip6[12], &addr->ip, 4);
>      > - addr->is_ip6 = true;
>      > - return true;
>      > -}
>      >
>      > -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
>      > - size_t i, j = 0, n = 0, dc = 42;
>      > - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
>      > - if (mg_v4mapped(str, addr)) return true;
>      > - for (i = 0; i < str.len; i++) {
>      > - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
>      > - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
>      > - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
>      > - unsigned long val;
>      > - if (i > j + 3) return false;
>      > - // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1),
>     &str.ptr[j]));
>      > - val = mg_unhexn(&str.ptr[j], i - j + 1);
>      > - addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
>      > - addr->ip6[n + 1] = (uint8_t) (val & 255);
>      > - } else if (str.ptr[i] == ':') {
>      > - j = i + 1;
>      > - if (i > 0 && str.ptr[i - 1] == ':') {
>      > - dc = n; // Double colon
>      > - if (i > 1 && str.ptr[i - 2] == ':') return false;
>      > - } else if (i > 0) {
>      > - n += 2;
>      > + cert = tls->recv.buf + 11;
>      > + certsz = MG_LOAD_BE24(tls->recv.buf + 8);
>      > + if (certsz > tls->recv.len - 11) {
>      > + mg_error(c, "certificate too long: %d vs %d", certsz,
>     tls->recv.len - 11);
>      > + return -1;
>      > + }
>      > +
>      > + do {
>      > + // secp256r1 public key
>      > + if (mg_der_find(cert, certsz,
>      > + (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
>      > + &oid) < 0) {
>      > + mg_error(c, "certificate secp256r1 public key OID not found");
>      > + return -1;
>      > + }
>      > + if (mg_der_to_tlv(oid.value + oid.len,
>      > + (size_t) (cert + certsz - (oid.value + oid.len)),
>      > + &pubkey) < 0) {
>      > + mg_error(c, "certificate secp256r1 public key not found");
>      > + return -1;
>      > + }
>      > +
>      > + // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32
>     content bytes
>      > + if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] !=
>     0 ||
>      > + pubkey.value[1] != 4) {
>      > + mg_error(c, "unsupported public key bitstring encoding");
>      > + return -1;
>      > + }
>      > + memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
>      > + } while (0);
>      > +
>      > + // Subject Alternative Names
>      > + do {
>      > + if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3,
>     &oid) < 0) {
>      > + mg_error(c, "certificate does not contain subject alternative
>     names");
>      > + return -1;
>      > + }
>      > + if (mg_der_to_tlv(oid.value + oid.len,
>      > + (size_t) (cert + certsz - (oid.value + oid.len)),
>      > + &seq) < 0) {
>      > + mg_error(c, "certificate subject alternative names not found");
>      > + return -1;
>      > + }
>      > + if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
>      > + mg_error(
>      > + c,
>      > + "certificate subject alternative names is not a constructed
>     object");
>      > + return -1;
>      > + }
>      > + MG_VERBOSE(("verify hostname %s", tls->hostname));
>      > + while (seq.len > 0) {
>      > + if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
>      > + mg_error(c, "bad subject alternative name");
>      > + return -1;
>      > }
>      > - if (n > 14) return false;
>      > - addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing ::
>      > - } else {
>      > - return false;
>      > + MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
>      > + if (mg_match(mg_str((const char *) tls->hostname),
>      > + mg_str_n((const char *) subj.value, subj.len), NULL)) {
>      > + subj_match = 1;
>      > + break;
>      > + }
>      > + seq.len = (uint32_t) (seq.value + seq.len - (subj.value +
>     subj.len));
>      > + seq.value = subj.value + subj.len;
>      > + }
>      > + if (!subj_match) {
>      > + mg_error(c, "certificate did not match the hostname");
>      > + return -1;
>      > }
>      > + } while (0);
>      > +
>      > + mg_tls_drop_message(c);
>      > + mg_tls_calc_cert_verify_hash(c, tls->sighash);
>      > + return 0;
>      > +}
>      > +
>      > +static int mg_tls_client_recv_cert_verify(struct mg_connection
>     *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (mg_tls_recv_record(c) < 0) {
>      > + return -1;
>      > }
>      > - if (n < 14 && dc == 42) return false;
>      > - if (n < 14) {
>      > - memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
>      > - memset(&addr->ip6[dc], 0, 14 - n);
>      > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
>      > + mg_error(c, "expected server certificate verify but got msg
>     0x%02x",
>      > + tls->recv.buf[0]);
>      > + return -1;
>      > + }
>      > + // Ignore CertificateVerify is strict checks are not required
>      > + if (tls->skip_verification) {
>      > + mg_tls_drop_message(c);
>      > + return 0;
>      > }
>      > - addr->is_ip6 = true;
>      > - return true;
>      > -}
>      >
>      > -bool mg_aton(struct mg_str str, struct mg_addr *addr) {
>      > - // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
>      > - return mg_atone(str, addr) || mg_atonl(str, addr) ||
>     mg_aton4(str, addr) ||
>      > - mg_aton6(str, addr);
>      > + // Extract certificate signature and verify it using pubkey and
>     sighash
>      > + do {
>      > + uint8_t sig[64];
>      > + struct mg_der_tlv seq, a, b;
>      > + if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) <
>     0) {
>      > + mg_error(c, "verification message is not an ASN.1 DER sequence");
>      > + return -1;
>      > + }
>      > + if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
>      > + mg_error(c, "missing first part of the signature");
>      > + return -1;
>      > + }
>      > + if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
>      > + mg_error(c, "missing second part of the signature");
>      > + return -1;
>      > + }
>      > + // Integers may be padded with zeroes
>      > + if (a.len > 32) {
>      > + a.value = a.value + (a.len - 32);
>      > + a.len = 32;
>      > + }
>      > + if (b.len > 32) {
>      > + b.value = b.value + (b.len - 32);
>      > + b.len = 32;
>      > + }
>      > +
>      > + memmove(sig, a.value, a.len);
>      > + memmove(sig + 32, b.value, b.len);
>      > +
>      > + if (mg_uecc_verify(tls->pubkey, tls->sighash,
>     sizeof(tls->sighash), sig,
>      > + mg_uecc_secp256r1()) != 1) {
>      > + mg_error(c, "failed to verify certificate");
>      > + return -1;
>      > + }
>      > + } while (0);
>      > +
>      > + mg_tls_drop_message(c);
>      > + return 0;
>      > }
>      >
>      > -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
>      > - struct mg_connection *c =
>      > - (struct mg_connection *) calloc(1, sizeof(*c) +
>     mgr->extraconnsize);
>      > - if (c != NULL) {
>      > - c->mgr = mgr;
>      > - c->send.align = c->recv.align = MG_IO_SIZE;
>      > - c->id = ++mgr->nextid;
>      > +static int mg_tls_client_recv_finish(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (mg_tls_recv_record(c) < 0) {
>      > + return -1;
>      > }
>      > - return c;
>      > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
>      > + mg_error(c, "expected server finished but got msg 0x%02x",
>      > + tls->recv.buf[0]);
>      > + return -1;
>      > + }
>      > + mg_tls_drop_message(c);
>      > + return 0;
>      > }
>      >
>      > -void mg_close_conn(struct mg_connection *c) {
>      > - mg_resolve_cancel(c); // Close any pending DNS query
>      > - LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
>      > - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
>      > - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
>      > - // Order of operations is important. `MG_EV_CLOSE` event must
>     be fired
>      > - // before we deallocate received data, see #1331
>      > - mg_call(c, MG_EV_CLOSE, NULL);
>      > - MG_DEBUG(("%lu %p closed", c->id, c->fd));
>      > -
>      > - mg_tls_free(c);
>      > - mg_iobuf_free(&c->recv);
>      > - mg_iobuf_free(&c->send);
>      > - memset(c, 0, sizeof(*c));
>      > - free(c);
>      > +static void mg_tls_client_send_finish(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + struct mg_iobuf *wio = &tls->send;
>      > + mg_sha256_ctx sha256;
>      > + uint8_t hash[32];
>      > + uint8_t finish[36] = {0x14, 0, 0, 32};
>      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>      > + mg_sha256_final(hash, &sha256);
>      > + mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash,
>     32);
>      > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
>      > + mg_io_send(c, wio->buf, wio->len);
>      > + wio->len = 0;
>      > +}
>      > +
>      > +static void mg_tls_client_handshake(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + switch (tls->state) {
>      > + case MG_TLS_STATE_CLIENT_START:
>      > + mg_tls_client_send_hello(c);
>      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
>      > + // Fallthrough
>      > + case MG_TLS_STATE_CLIENT_WAIT_SH:
>      > + if (mg_tls_client_recv_hello(c) < 0) {
>      > + break;
>      > + }
>      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
>      > + // Fallthrough
>      > + case MG_TLS_STATE_CLIENT_WAIT_EE:
>      > + if (mg_tls_client_recv_ext(c) < 0) {
>      > + break;
>      > + }
>      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
>      > + // Fallthrough
>      > + case MG_TLS_STATE_CLIENT_WAIT_CERT:
>      > + if (mg_tls_client_recv_cert(c) < 0) {
>      > + break;
>      > + }
>      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
>      > + // Fallthrough
>      > + case MG_TLS_STATE_CLIENT_WAIT_CV:
>      > + if (mg_tls_client_recv_cert_verify(c) < 0) {
>      > + break;
>      > + }
>      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
>      > + // Fallthrough
>      > + case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
>      > + if (mg_tls_client_recv_finish(c) < 0) {
>      > + break;
>      > + }
>      > + mg_tls_client_send_finish(c);
>      > + mg_tls_generate_application_keys(c);
>      > + tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
>      > + c->is_tls_hs = 0;
>      > + break;
>      > + default: mg_error(c, "unexpected client state: %d",
>     tls->state); break;
>      > + }
>      > }
>      >
>      > -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
>     *url,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = NULL;
>      > - if (url == NULL || url[0] == '\0') {
>      > - MG_ERROR(("null url"));
>      > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > - MG_ERROR(("OOM"));
>      > - } else {
>      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > - c->is_udp = (strncmp(url, "udp:", 4) == 0);
>      > - c->fd = (void *) (size_t) MG_INVALID_SOCKET;
>      > - c->fn = fn;
>      > - c->is_client = true;
>      > - c->fn_data = fn_data;
>      > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
>      > - mg_call(c, MG_EV_OPEN, NULL);
>      > - mg_resolve(c, url);
>      > +static void mg_tls_server_handshake(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + switch (tls->state) {
>      > + case MG_TLS_STATE_SERVER_START:
>      > + if (mg_tls_server_recv_hello(c) < 0) {
>      > + return;
>      > + }
>      > + mg_tls_server_send_hello(c);
>      > + mg_tls_generate_handshake_keys(c);
>      > + mg_tls_server_send_ext(c);
>      > + mg_tls_server_send_cert(c);
>      > + mg_tls_server_send_cert_verify(c);
>      > + mg_tls_server_send_finish(c);
>      > + tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
>      > + // fallthrough
>      > + case MG_TLS_STATE_SERVER_NEGOTIATED:
>      > + if (mg_tls_server_recv_finish(c) < 0) {
>      > + return;
>      > + }
>      > + mg_tls_generate_application_keys(c);
>      > + tls->state = MG_TLS_STATE_SERVER_CONNECTED;
>      > + c->is_tls_hs = 0;
>      > + return;
>      > + default: mg_error(c, "unexpected server state: %d",
>     tls->state); break;
>      > }
>      > - return c;
>      > }
>      >
>      > -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
>     *url,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = NULL;
>      > - if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > - MG_ERROR(("OOM %s", url));
>      > - } else if (!mg_open_listener(c, url)) {
>      > - MG_ERROR(("Failed: %s, errno %d", url, errno));
>      > - free(c);
>      > - c = NULL;
>      > +void mg_tls_handshake(struct mg_connection *c) {
>      > + if (c->is_client) {
>      > + mg_tls_client_handshake(c);
>      > } else {
>      > - c->is_listening = 1;
>      > - c->is_udp = strncmp(url, "udp:", 4) == 0;
>      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > - c->fn = fn;
>      > - c->fn_data = fn_data;
>      > - mg_call(c, MG_EV_OPEN, NULL);
>      > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
>      > + mg_tls_server_handshake(c);
>      > }
>      > - return c;
>      > }
>      >
>      > -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
>      > - mg_event_handler_t fn, void *fn_data) {
>      > - struct mg_connection *c = mg_alloc_conn(mgr);
>      > - if (c != NULL) {
>      > - c->fd = (void *) (size_t) fd;
>      > - c->fn = fn;
>      > - c->fn_data = fn_data;
>      > - MG_EPOLL_ADD(c);
>      > - mg_call(c, MG_EV_OPEN, NULL);
>      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > +static int mg_parse_pem(const struct mg_str pem, const struct
>     mg_str label,
>      > + struct mg_str *der) {
>      > + size_t n = 0, m = 0;
>      > + char *s;
>      > + const char *c;
>      > + struct mg_str caps[5];
>      > + if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END
>     #-----#"), caps)) {
>      > + der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
>      > + der->len = pem.len;
>      > + return 0;
>      > + }
>      > + if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label)
>     != 0) {
>      > + return -1; // bad label
>      > + }
>      > + if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
>      > + return -1;
>      > }
>      > - return c;
>      > -}
>      >
>      > -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>     milliseconds,
>      > - unsigned flags, void (*fn)(void *), void *arg) {
>      > - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
>      > - if (t != NULL) {
>      > - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
>      > - t->id = mgr->timerid++;
>      > + for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
>      > + if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
>      > + continue;
>      > + }
>      > + s[n++] = *c;
>      > }
>      > - return t;
>      > + m = mg_base64_decode(s, n, s, n);
>      > + if (m == 0) {
>      > + free(s);
>      > + return -1;
>      > + }
>      > + der->buf = s;
>      > + der->len = m;
>      > + return 0;
>      > }
>      >
>      > -void mg_mgr_free(struct mg_mgr *mgr) {
>      > - struct mg_connection *c;
>      > - struct mg_timer *tmp, *t = mgr->timers;
>      > - while (t != NULL) tmp = t->next, free(t), t = tmp;
>      > - mgr->timers = NULL; // Important. Next call to poll won't touch
>     timers
>      > - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
>      > - mg_mgr_poll(mgr, 0);
>      > -#if MG_ENABLE_FREERTOS_TCP
>      > - FreeRTOS_DeleteSocketSet(mgr->ss);
>      > -#endif
>      > - MG_DEBUG(("All connections closed"));
>      > -#if MG_ENABLE_EPOLL
>      > - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
>      > -#endif
>      > -}
>      > +void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > + struct mg_str key;
>      > + struct tls_data *tls = (struct tls_data *) calloc(1,
>     sizeof(struct tls_data));
>      > + if (tls == NULL) {
>      > + mg_error(c, "tls oom");
>      > + return;
>      > + }
>      >
>      > -void mg_mgr_init(struct mg_mgr *mgr) {
>      > - memset(mgr, 0, sizeof(*mgr));
>      > -#if MG_ENABLE_EPOLL
>      > - if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll:
>     %d", errno));
>      > -#else
>      > - mgr->epoll_fd = -1;
>      > -#endif
>      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > - // clang-format off
>      > - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
>      > - // clang-format on
>      > -#elif MG_ENABLE_FREERTOS_TCP
>      > - mgr->ss = FreeRTOS_CreateSocketSet();
>      > -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
>      > - // Ignore SIGPIPE signal, so if client cancels the request, it
>      > - // won't kill the whole process.
>      > - signal(SIGPIPE, SIG_IGN);
>      > -#endif
>      > - mgr->dnstimeout = 3000;
>      > - mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
>      > - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
>      > -}
>      > + tls->state =
>      > + c->is_client ? MG_TLS_STATE_CLIENT_START :
>     MG_TLS_STATE_SERVER_START;
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/rpc.c"
>      > -#endif
>      > + tls->skip_verification = opts->skip_verification;
>      > + tls->send.align = MG_IO_SIZE;
>      >
>      > + c->tls = tls;
>      > + c->is_tls = c->is_tls_hs = 1;
>      > + mg_sha256_init(&tls->sha256);
>      >
>      > -void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
>      > - void (*fn)(struct mg_rpc_req *), void *fn_data) {
>      > - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
>      > - if (rpc != NULL) {
>      > - rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data =
>     fn_data;
>      > - rpc->next = *head, *head = rpc;
>      > + // save hostname (client extension)
>      > + if (opts->name.len > 0) {
>      > + if (opts->name.len >= sizeof(tls->hostname) - 1) {
>      > + mg_error(c, "hostname too long");
>      > + }
>      > + strncpy((char *) tls->hostname, opts->name.buf,
>     sizeof(tls->hostname) - 1);
>      > + tls->hostname[opts->name.len] = 0;
>      > }
>      > -}
>      >
>      > -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
>     mg_rpc_req *)) {
>      > - struct mg_rpc *r;
>      > - while ((r = *head) != NULL) {
>      > - if (r->fn == fn || fn == NULL) {
>      > - *head = r->next;
>      > - free((void *) r->method.ptr);
>      > - free(r);
>      > - } else {
>      > - head = &(*head)->next;
>      > - }
>      > + if (c->is_client) {
>      > + tls->server_cert_der.buf = NULL;
>      > + return;
>      > }
>      > -}
>      >
>      > -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
>     method) {
>      > - struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
>      > - while (h != NULL && !mg_match(method, h->method, NULL)) h =
>     h->next;
>      > - if (h != NULL) {
>      > - r->rpc = h;
>      > - h->fn(r);
>      > + // parse PEM or DER certificate
>      > + if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"),
>     &tls->server_cert_der) <
>      > + 0) {
>      > + MG_ERROR(("Failed to load certificate"));
>      > + return;
>      > + }
>      > +
>      > + // parse PEM or DER EC key
>      > + if (opts->key.buf == NULL) {
>      > + mg_error(c, "certificate provided without a private key");
>      > + return;
>      > + }
>      > +
>      > + if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key)
>     == 0) {
>      > + if (key.len < 39) {
>      > + MG_ERROR(("EC private key too short"));
>      > + return;
>      > + }
>      > + // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
>      > + // 30 nn 02 01 01 04 20 [key] ...
>      > + if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
>      > + MG_ERROR(("EC private key: ASN.1 bad sequence"));
>      > + return;
>      > + }
>      > + if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
>      > + MG_ERROR(("EC private key: ASN.1 bad data"));
>      > + }
>      > + memmove(tls->server_key, key.buf + 7, 32);
>      > + free((void *) key.buf);
>      > + } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"),
>     &key) == 0) {
>      > + mg_error(c, "PKCS8 private key format is not supported");
>      > } else {
>      > - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
>     method.ptr);
>      > + mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
>      > }
>      > }
>      >
>      > -void mg_rpc_process(struct mg_rpc_req *r) {
>      > - int len, off = mg_json_get(r->frame, "$.method", &len);
>      > - if (off > 0 && r->frame.ptr[off] == '"') {
>      > - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1],
>     (size_t) len - 2);
>      > - mg_rpc_call(r, method);
>      > - } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
>      > - (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
>      > - mg_rpc_call(r, mg_str("")); // JSON response! call "" method
>     handler
>      > - } else {
>      > - mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len,
>     r->frame.ptr); // Invalid
>      > +void mg_tls_free(struct mg_connection *c) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + if (tls != NULL) {
>      > + mg_iobuf_free(&tls->send);
>      > + free((void *) tls->server_cert_der.buf);
>      > }
>      > + free(c->tls);
>      > + c->tls = NULL;
>      > }
>      >
>      > -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
>     *ap) {
>      > - int len, off = mg_json_get(r->frame, "$.id", &len);
>      > - if (off > 0) {
>      > - mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
>      > - &r->frame.ptr[off], "result");
>      > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>      > - mg_xprintf(r->pfn, r->pfn_data, "}");
>      > +long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + long n = MG_IO_WAIT;
>      > + if (len > MG_IO_SIZE) len = MG_IO_SIZE;
>      > + mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
>      > + while (tls->send.len > 0 &&
>      > + (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
>      > + mg_iobuf_del(&tls->send, 0, (size_t) n);
>      > }
>      > + if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
>      > + return (long) len;
>      > }
>      >
>      > -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - mg_rpc_vok(r, fmt, &ap);
>      > - va_end(ap);
>      > -}
>      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > + int r = 0;
>      > + struct tls_data *tls = (struct tls_data *) c->tls;
>      > + size_t minlen;
>      >
>      > -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
>     *fmt, va_list *ap) {
>      > - int len, off = mg_json_get(r->frame, "$.id", &len);
>      > - mg_xprintf(r->pfn, r->pfn_data, "{");
>      > - if (off > 0) {
>      > - mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len,
>     &r->frame.ptr[off]);
>      > + r = mg_tls_recv_record(c);
>      > + if (r < 0) {
>      > + return r;
>      > }
>      > - mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error",
>     "code", code,
>      > - "message");
>      > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>      > - mg_xprintf(r->pfn, r->pfn_data, "}}");
>      > + if (tls->content_type != MG_TLS_APP_DATA) {
>      > + tls->recv.len = 0;
>      > + mg_tls_drop_record(c);
>      > + return MG_IO_WAIT;
>      > + }
>      > + minlen = len < tls->recv.len ? len : tls->recv.len;
>      > + memmove(buf, tls->recv.buf, minlen);
>      > + tls->recv.buf += minlen;
>      > + tls->recv.len -= minlen;
>      > + if (tls->recv.len == 0) {
>      > + mg_tls_drop_record(c);
>      > + }
>      > + return (long) minlen;
>      > }
>      >
>      > -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
>     ...) {
>      > - va_list ap;
>      > - va_start(ap, fmt);
>      > - mg_rpc_verr(r, code, fmt, &ap);
>      > - va_end(ap);
>      > +size_t mg_tls_pending(struct mg_connection *c) {
>      > + return mg_tls_got_record(c) ? 1 : 0;
>      > }
>      >
>      > -static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
>     va_list *ap) {
>      > - struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
>     **);
>      > - size_t len = 0;
>      > - for (h = *head; h != NULL; h = h->next) {
>      > - if (h->method.len == 0) continue; // Ignore response handler
>      > - len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
>      > - (int) h->method.len, h->method.ptr);
>      > - }
>      > - return len;
>      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > }
>      >
>      > -void mg_rpc_list(struct mg_rpc_req *r) {
>      > - mg_rpc_ok(r, "[%M]", print_methods, r->head);
>      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > }
>      > +#endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/sha1.c"
>      > +#line 1 "src/tls_dummy.c"
>      > +#endif
>      > +
>      > +
>      > +#if MG_TLS == MG_TLS_NONE
>      > +void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > + (void) opts;
>      > + mg_error(c, "TLS is not enabled");
>      > +}
>      > +void mg_tls_handshake(struct mg_connection *c) {
>      > + (void) c;
>      > +}
>      > +void mg_tls_free(struct mg_connection *c) {
>      > + (void) c;
>      > +}
>      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>      > +}
>      > +long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>      > +}
>      > +size_t mg_tls_pending(struct mg_connection *c) {
>      > + (void) c;
>      > + return 0;
>      > +}
>      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > +}
>      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > +}
>      > #endif
>      > -/* Copyright(c) By Steve Reid <st...@edmweb.com> */
>      > -/* 100% Public Domain */
>      >
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/tls_mbed.c"
>      > +#endif
>      >
>      >
>      > -union char64long16 {
>      > - unsigned char c[64];
>      > - uint32_t l[16];
>      > -};
>      >
>      > -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
>     - (bits))))
>      > +#if MG_TLS == MG_TLS_MBED
>      >
>      > -static uint32_t blk0(union char64long16 *block, int i) {
>      > - if (MG_BIG_ENDIAN) {
>      > - } else {
>      > - block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
>      > - (rol(block->l[i], 8) & 0x00FF00FF);
>      > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>     0x03000000
>      > +#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
>      > +#else
>      > +#define MG_MBEDTLS_RNG_GET
>      > +#endif
>      > +
>      > +static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
>      > + mg_random(buf, len);
>      > + (void) ctx;
>      > + return 0;
>      > +}
>      > +
>      > +static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
>      > + int rc;
>      > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
>     return true;
>      > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
>      > + if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf,
>     str.len)) != 0) {
>      > + MG_ERROR(("cert err %#x", -rc));
>      > + return false;
>      > }
>      > - return block->l[i];
>      > + return true;
>      > }
>      >
>      > -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
>     define R0~R4) */
>      > -#undef blk
>      > -#undef R0
>      > -#undef R1
>      > -#undef R2
>      > -#undef R3
>      > -#undef R4
>      > +static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
>      > + int rc;
>      > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
>     return true;
>      > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
>      > + if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len,
>     NULL,
>      > + 0 MG_MBEDTLS_RNG_GET)) != 0) {
>      > + MG_ERROR(("key err %#x", -rc));
>      > + return false;
>      > + }
>      > + return true;
>      > +}
>      >
>      > -#define blk(i) \
>      > - (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
>     8) & 15] ^ \
>      > - block->l[(i + 2) & 15] ^ block->l[i & 15], \
>      > - 1))
>      > -#define R0(v, w, x, y, z, i) \
>      > - z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
>     5); \
>      > - w = rol(w, 30);
>      > -#define R1(v, w, x, y, z, i) \
>      > - z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
>      > - w = rol(w, 30);
>      > -#define R2(v, w, x, y, z, i) \
>      > - z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
>      > - w = rol(w, 30);
>      > -#define R3(v, w, x, y, z, i) \
>      > - z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
>     5); \
>      > - w = rol(w, 30);
>      > -#define R4(v, w, x, y, z, i) \
>      > - z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
>      > - w = rol(w, 30);
>      > +void mg_tls_free(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + if (tls != NULL) {
>      > + mbedtls_ssl_free(&tls->ssl);
>      > + mbedtls_pk_free(&tls->pk);
>      > + mbedtls_x509_crt_free(&tls->ca);
>      > + mbedtls_x509_crt_free(&tls->cert);
>      > + mbedtls_ssl_config_free(&tls->conf);
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + mbedtls_ssl_ticket_free(&tls->ticket);
>      > +#endif
>      > + free(tls);
>      > + c->tls = NULL;
>      > + }
>      > +}
>      >
>      > -static void mg_sha1_transform(uint32_t state[5],
>      > - const unsigned char buffer[64]) {
>      > - uint32_t a, b, c, d, e;
>      > - union char64long16 block[1];
>      > +static int mg_net_send(void *ctx, const unsigned char *buf,
>     size_t len) {
>      > + long n = mg_io_send((struct mg_connection *) ctx, buf, len);
>      > + MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *)
>     ctx)->id, n, errno));
>      > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>      > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>      > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
>      > + return (int) n;
>      > +}
>      >
>      > - memcpy(block, buffer, 64);
>      > - a = state[0];
>      > - b = state[1];
>      > - c = state[2];
>      > - d = state[3];
>      > - e = state[4];
>      > - R0(a, b, c, d, e, 0);
>      > - R0(e, a, b, c, d, 1);
>      > - R0(d, e, a, b, c, 2);
>      > - R0(c, d, e, a, b, 3);
>      > - R0(b, c, d, e, a, 4);
>      > - R0(a, b, c, d, e, 5);
>      > - R0(e, a, b, c, d, 6);
>      > - R0(d, e, a, b, c, 7);
>      > - R0(c, d, e, a, b, 8);
>      > - R0(b, c, d, e, a, 9);
>      > - R0(a, b, c, d, e, 10);
>      > - R0(e, a, b, c, d, 11);
>      > - R0(d, e, a, b, c, 12);
>      > - R0(c, d, e, a, b, 13);
>      > - R0(b, c, d, e, a, 14);
>      > - R0(a, b, c, d, e, 15);
>      > - R1(e, a, b, c, d, 16);
>      > - R1(d, e, a, b, c, 17);
>      > - R1(c, d, e, a, b, 18);
>      > - R1(b, c, d, e, a, 19);
>      > - R2(a, b, c, d, e, 20);
>      > - R2(e, a, b, c, d, 21);
>      > - R2(d, e, a, b, c, 22);
>      > - R2(c, d, e, a, b, 23);
>      > - R2(b, c, d, e, a, 24);
>      > - R2(a, b, c, d, e, 25);
>      > - R2(e, a, b, c, d, 26);
>      > - R2(d, e, a, b, c, 27);
>      > - R2(c, d, e, a, b, 28);
>      > - R2(b, c, d, e, a, 29);
>      > - R2(a, b, c, d, e, 30);
>      > - R2(e, a, b, c, d, 31);
>      > - R2(d, e, a, b, c, 32);
>      > - R2(c, d, e, a, b, 33);
>      > - R2(b, c, d, e, a, 34);
>      > - R2(a, b, c, d, e, 35);
>      > - R2(e, a, b, c, d, 36);
>      > - R2(d, e, a, b, c, 37);
>      > - R2(c, d, e, a, b, 38);
>      > - R2(b, c, d, e, a, 39);
>      > - R3(a, b, c, d, e, 40);
>      > - R3(e, a, b, c, d, 41);
>      > - R3(d, e, a, b, c, 42);
>      > - R3(c, d, e, a, b, 43);
>      > - R3(b, c, d, e, a, 44);
>      > - R3(a, b, c, d, e, 45);
>      > - R3(e, a, b, c, d, 46);
>      > - R3(d, e, a, b, c, 47);
>      > - R3(c, d, e, a, b, 48);
>      > - R3(b, c, d, e, a, 49);
>      > - R3(a, b, c, d, e, 50);
>      > - R3(e, a, b, c, d, 51);
>      > - R3(d, e, a, b, c, 52);
>      > - R3(c, d, e, a, b, 53);
>      > - R3(b, c, d, e, a, 54);
>      > - R3(a, b, c, d, e, 55);
>      > - R3(e, a, b, c, d, 56);
>      > - R3(d, e, a, b, c, 57);
>      > - R3(c, d, e, a, b, 58);
>      > - R3(b, c, d, e, a, 59);
>      > - R4(a, b, c, d, e, 60);
>      > - R4(e, a, b, c, d, 61);
>      > - R4(d, e, a, b, c, 62);
>      > - R4(c, d, e, a, b, 63);
>      > - R4(b, c, d, e, a, 64);
>      > - R4(a, b, c, d, e, 65);
>      > - R4(e, a, b, c, d, 66);
>      > - R4(d, e, a, b, c, 67);
>      > - R4(c, d, e, a, b, 68);
>      > - R4(b, c, d, e, a, 69);
>      > - R4(a, b, c, d, e, 70);
>      > - R4(e, a, b, c, d, 71);
>      > - R4(d, e, a, b, c, 72);
>      > - R4(c, d, e, a, b, 73);
>      > - R4(b, c, d, e, a, 74);
>      > - R4(a, b, c, d, e, 75);
>      > - R4(e, a, b, c, d, 76);
>      > - R4(d, e, a, b, c, 77);
>      > - R4(c, d, e, a, b, 78);
>      > - R4(b, c, d, e, a, 79);
>      > - state[0] += a;
>      > - state[1] += b;
>      > - state[2] += c;
>      > - state[3] += d;
>      > - state[4] += e;
>      > - /* Erase working structures. The order of operations is important,
>      > - * used to ensure that compiler doesn't optimize those out. */
>      > - memset(block, 0, sizeof(block));
>      > - a = b = c = d = e = 0;
>      > - (void) a;
>      > - (void) b;
>      > - (void) c;
>      > - (void) d;
>      > - (void) e;
>      > +static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
>      > + long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
>      > + MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>      > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>      > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>      > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
>      > + return (int) n;
>      > +}
>      > +
>      > +void mg_tls_handshake(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + int rc = mbedtls_ssl_handshake(&tls->ssl);
>      > + if (rc == 0) { // Success
>      > + MG_DEBUG(("%lu success", c->id));
>      > + c->is_tls_hs = 0;
>      > + mg_call(c, MG_EV_TLS_HS, NULL);
>      > + } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
>      > + rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
>      > + MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
>     c->is_connecting,
>      > + c->is_tls_hs, rc, -rc));
>      > + } else {
>      > + mg_error(c, "TLS handshake: -%#x", -rc); // Error
>      > + }
>      > }
>      >
>      > -void mg_sha1_init(mg_sha1_ctx *context) {
>      > - context->state[0] = 0x67452301;
>      > - context->state[1] = 0xEFCDAB89;
>      > - context->state[2] = 0x98BADCFE;
>      > - context->state[3] = 0x10325476;
>      > - context->state[4] = 0xC3D2E1F0;
>      > - context->count[0] = context->count[1] = 0;
>      > +static void debug_cb(void *c, int lev, const char *s, int n,
>     const char *s2) {
>      > + n = (int) strlen(s2) - 1;
>      > + MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev,
>     n, s2));
>      > + (void) s;
>      > }
>      >
>      > -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
>     *data,
>      > - size_t len) {
>      > - size_t i, j;
>      > +void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>      > + int rc = 0;
>      > + c->tls = tls;
>      > + if (c->tls == NULL) {
>      > + mg_error(c, "TLS OOM");
>      > + goto fail;
>      > + }
>      > + if (c->is_listening) goto fail;
>      > + MG_DEBUG(("%lu Setting TLS", c->id));
>      > + MG_PROF_ADD(c, "mbedtls_init_start");
>      > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>     0x03000000 && \
>      > + defined(MBEDTLS_PSA_CRYPTO_C)
>      > + psa_crypto_init(); //
>     https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711 <https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711>
>      > +#endif
>      > + mbedtls_ssl_init(&tls->ssl);
>      > + mbedtls_ssl_config_init(&tls->conf);
>      > + mbedtls_x509_crt_init(&tls->ca);
>      > + mbedtls_x509_crt_init(&tls->cert);
>      > + mbedtls_pk_init(&tls->pk);
>      > + mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
>      > +#if defined(MG_MBEDTLS_DEBUG_LEVEL)
>      > + mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
>      > +#endif
>      > + if ((rc = mbedtls_ssl_config_defaults(
>      > + &tls->conf,
>      > + c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
>      > + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
>      > + mg_error(c, "tls defaults %#x", -rc);
>      > + goto fail;
>      > + }
>      > + mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
>      >
>      > - j = context->count[0];
>      > - if ((context->count[0] += (uint32_t) len << 3) < j)
>     context->count[1]++;
>      > - context->count[1] += (uint32_t) (len >> 29);
>      > - j = (j >> 3) & 63;
>      > - if ((j + len) > 63) {
>      > - memcpy(&context->buffer[j], data, (i = 64 - j));
>      > - mg_sha1_transform(context->state, context->buffer);
>      > - for (; i + 63 < len; i += 64) {
>      > - mg_sha1_transform(context->state, &data[i]);
>      > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
>      > + // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on
>     client side
>      > + // See https://github.com/Mbed-TLS/mbedtls/issues/7075
>     <https://github.com/Mbed-TLS/mbedtls/issues/7075>
>      > + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
>      > + } else {
>      > + if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
>      > + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
>      > + if (c->is_client && opts->name.buf != NULL && opts->name.buf[0]
>     != '\0') {
>      > + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
>      > + mbedtls_ssl_set_hostname(&tls->ssl, host);
>      > + MG_DEBUG(("%lu hostname verification: %s", c->id, host));
>      > + free(host);
>      > }
>      > - j = 0;
>      > - } else
>      > - i = 0;
>      > - memcpy(&context->buffer[j], &data[i], len - i);
>      > + mbedtls_ssl_conf_authmode(&tls->conf,
>     MBEDTLS_SSL_VERIFY_REQUIRED);
>      > + }
>      > + if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
>      > + if (!mg_load_key(opts->key, &tls->pk)) goto fail;
>      > + if (tls->cert.version &&
>      > + (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert,
>     &tls->pk)) != 0) {
>      > + mg_error(c, "own cert %#x", -rc);
>      > + goto fail;
>      > + }
>      > +
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + mbedtls_ssl_conf_session_tickets_cb(
>      > + &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
>      > + &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
>      > +#endif
>      > +
>      > + if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
>      > + mg_error(c, "setup err %#x", -rc);
>      > + goto fail;
>      > + }
>      > + c->is_tls = 1;
>      > + c->is_tls_hs = 1;
>      > + mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
>      > + MG_PROF_ADD(c, "mbedtls_init_end");
>      > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>     0) {
>      > + mg_tls_handshake(c);
>      > + }
>      > + return;
>      > +fail:
>      > + mg_tls_free(c);
>      > }
>      >
>      > -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
>     *context) {
>      > - unsigned i;
>      > - unsigned char finalcount[8], c;
>      > +size_t mg_tls_pending(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
>      > +}
>      >
>      > - for (i = 0; i < 8; i++) {
>      > - finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
>     1)] >>
>      > - ((3 - (i & 3)) * 8)) &
>      > - 255);
>      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
>      > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>     MBEDTLS_ERR_SSL_WANT_WRITE)
>      > + return MG_IO_WAIT;
>      > +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
>      > + if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
>      > + return MG_IO_WAIT;
>      > }
>      > - c = 0200;
>      > - mg_sha1_update(context, &c, 1);
>      > - while ((context->count[0] & 504) != 448) {
>      > - c = 0000;
>      > - mg_sha1_update(context, &c, 1);
>      > +#endif
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > +}
>      > +
>      > +long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
>      > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>     MBEDTLS_ERR_SSL_WANT_WRITE)
>      > + return MG_IO_WAIT;
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > +}
>      > +
>      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>      > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1,
>     sizeof(*ctx));
>      > + if (ctx == NULL) {
>      > + MG_ERROR(("TLS context init OOM"));
>      > + } else {
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + int rc;
>      > + mbedtls_ssl_ticket_init(&ctx->tickets);
>      > + if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng,
>     NULL,
>      > + MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
>      > + 0) {
>      > + MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
>      > + }
>      > +#endif
>      > + mgr->tls_ctx = ctx;
>      > }
>      > - mg_sha1_update(context, finalcount, 8);
>      > - for (i = 0; i < 20; i++) {
>      > - digest[i] =
>      > - (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
>     8)) & 255);
>      > +}
>      > +
>      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>      > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
>      > + if (ctx != NULL) {
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + mbedtls_ssl_ticket_free(&ctx->tickets);
>      > +#endif
>      > + free(ctx);
>      > + mgr->tls_ctx = NULL;
>      > }
>      > - memset(context, '\0', sizeof(*context));
>      > - memset(&finalcount, '\0', sizeof(finalcount));
>      > }
>      > +#endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/sntp.c"
>      > +#line 1 "src/tls_openssl.c"
>      > #endif
>      >
>      >
>      >
>      > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>      >
>      > +static int tls_err_cb(const char *s, size_t len, void *c) {
>      > + int n = (int) len - 1;
>      > + MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
>      > + return 0; // undocumented
>      > +}
>      >
>      > +static int mg_tls_err(struct mg_connection *c, struct mg_tls
>     *tls, int res) {
>      > + int err = SSL_get_error(tls->ssl, res);
>      > + // We've just fetched the last error from the queue.
>      > + // Now we need to clear the error queue. If we do not, then the
>     following
>      > + // can happen (actually reported):
>      > + // - A new connection is accept()-ed with cert error (e.g.
>     self-signed cert)
>      > + // - Since all accept()-ed connections share listener's context,
>      > + // - *ALL* SSL accepted connection report read error on the
>     next poll cycle.
>      > + // Thus a single errored connection can close all the rest,
>     unrelated ones.
>      > + // Clearing the error keeps the shared SSL_CTX in an OK state.
>      >
>      > -#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
>      > -#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
>      > + if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
>      > + ERR_clear_error();
>      > + if (err == SSL_ERROR_WANT_READ) return 0;
>      > + if (err == SSL_ERROR_WANT_WRITE) return 0;
>      > + return err;
>      > +}
>      >
>      > -static int64_t gettimestamp(const uint32_t *data) {
>      > - uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
>      > - if (sec) sec -= SNTP_TIME_OFFSET;
>      > - return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
>     * 1000.0);
>      > +static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
>      > + BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
>      > + STACK_OF(X509_INFO) *certs =
>      > + bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
>      > + if (bio) BIO_free(bio);
>      > + return certs;
>      > }
>      >
>      > -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
>      > - int64_t res = -1;
>      > - int mode = len > 0 ? buf[0] & 7 : 0;
>      > - int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
>      > - if (len < 48) {
>      > - MG_ERROR(("%s", "corrupt packet"));
>      > - } else if (mode != 4 && mode != 5) {
>      > - MG_ERROR(("%s", "not a server reply"));
>      > - } else if (buf[1] == 0) {
>      > - MG_ERROR(("%s", "server sent a kiss of death"));
>      > - } else if (version == 4 || version == 3) {
>      > - // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
>      > - int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
>      > - int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
>      > - int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
>      > - int64_t t3 = (int64_t) mg_millis();
>      > - int64_t delta = (t3 - t0) - (t2 - t1);
>      > - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
>     delta));
>      > - res = t2 + delta / 2;
>      > - } else {
>      > - MG_ERROR(("unexpected version: %d", version));
>      > +static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) *
>     certs) {
>      > + X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
>      > + for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
>      > + X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
>      > + if (cert_info->x509 && !X509_STORE_add_cert(cert_store,
>     cert_info->x509))
>      > + return false;
>      > + }
>      > + return true;
>      > +}
>      > +
>      > +static EVP_PKEY *load_key(struct mg_str s) {
>      > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
>      > + EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0,
>     NULL) : NULL;
>      > + if (bio) BIO_free(bio);
>      > + return key;
>      > +}
>      > +
>      > +static X509 *load_cert(struct mg_str s) {
>      > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
>      > + X509 *cert = bio == NULL ? NULL
>      > + : s.buf[0] == '-'
>      > + ? PEM_read_bio_X509(bio, NULL, NULL, NULL) // PEM
>      > + : d2i_X509_bio(bio, NULL); // DER
>      > + if (bio) BIO_free(bio);
>      > + return cert;
>      > +}
>      > +
>      > +static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
>      > + long ret = 0;
>      > + if (cmd == BIO_CTRL_PUSH) ret = 1;
>      > + if (cmd == BIO_CTRL_POP) ret = 1;
>      > + if (cmd == BIO_CTRL_FLUSH) ret = 1;
>      > +#if MG_TLS == MG_TLS_OPENSSL
>      > + if (cmd == BIO_C_SET_NBIO) ret = 1;
>      > +#endif
>      > + // MG_DEBUG(("%d -> %ld", cmd, ret));
>      > + (void) b, (void) cmd, (void) larg, (void) pargs;
>      > + return ret;
>      > +}
>      > +
>      > +static int mg_bio_read(BIO *bio, char *buf, int len) {
>      > + struct mg_connection *c = (struct mg_connection *)
>     BIO_get_data(bio);
>      > + long res = mg_io_recv(c, buf, (size_t) len);
>      > + // MG_DEBUG(("%p %d %ld", buf, len, res));
>      > + len = res > 0 ? (int) res : -1;
>      > + if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
>      > + return len;
>      > +}
>      > +
>      > +static int mg_bio_write(BIO *bio, const char *buf, int len) {
>      > + struct mg_connection *c = (struct mg_connection *)
>     BIO_get_data(bio);
>      > + long res = mg_io_send(c, buf, (size_t) len);
>      > + // MG_DEBUG(("%p %d %ld", buf, len, res));
>      > + len = res > 0 ? (int) res : -1;
>      > + if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
>      > + return len;
>      > +}
>      > +
>      > +void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>      > + const char *id = "mongoose";
>      > + static unsigned char s_initialised = 0;
>      > + BIO *bio = NULL;
>      > + int rc;
>      > +
>      > + if (tls == NULL) {
>      > + mg_error(c, "TLS OOM");
>      > + goto fail;
>      > + }
>      > +
>      > + if (!s_initialised) {
>      > + SSL_library_init();
>      > + s_initialised++;
>      > + }
>      > + MG_DEBUG(("%lu Setting TLS", c->id));
>      > + tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
>      > + : SSL_CTX_new(SSLv23_server_method());
>      > + if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
>      > + mg_error(c, "SSL_new");
>      > + goto fail;
>      > + }
>      > + SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
>      > + (unsigned) strlen(id));
>      > + // Disable deprecated protocols
>      > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
>      > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
>      > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
>      > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
>      > +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
>      > + SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
>      > +#endif
>      > +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
>      > + SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
>      > +#endif
>      > +
>      > +#if MG_TLS == MG_TLS_WOLFSSL &&
>     !defined(OPENSSL_COMPATIBLE_DEFAULTS)
>      > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
>      > + // Older versions require that either the CA is loaded or
>     SSL_VERIFY_NONE
>      > + // explicitly set
>      > + SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
>      > + }
>      > +#endif
>      > + if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
>      > + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
>     SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
>      > + NULL);
>      > + STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
>      > + rc = add_ca_certs(tls->ctx, certs);
>      > + sk_X509_INFO_pop_free(certs, X509_INFO_free);
>      > + if (!rc) {
>      > + mg_error(c, "CA err");
>      > + goto fail;
>      > + }
>      > + }
>      > + if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
>      > + X509 *cert = load_cert(opts->cert);
>      > + rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
>      > + X509_free(cert);
>      > + if (cert == NULL || rc != 1) {
>      > + mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
>      > + goto fail;
>      > + }
>      > + }
>      > + if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
>      > + EVP_PKEY *key = load_key(opts->key);
>      > + rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
>      > + EVP_PKEY_free(key);
>      > + if (key == NULL || rc != 1) {
>      > + mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
>      > + goto fail;
>      > + }
>      > + }
>      > +
>      > + SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
>      > +#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER >
>     0x10002000L
>      > + (void) SSL_set_ecdh_auto(tls->ssl, 1);
>      > +#endif
>      > +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
>      > + if (opts->name.len > 0) {
>      > + char *s = mg_mprintf("%.*s", (int) opts->name.len,
>     opts->name.buf);
>      > +#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >=
>     0x05005002
>      > + SSL_set1_host(tls->ssl, s);
>      > +#else
>      > + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
>      > +#endif
>      > + SSL_set_tlsext_host_name(tls->ssl, s);
>      > + free(s);
>      > }
>      > - return res;
>      > -}
>      > +#endif
>      > +#if MG_TLS == MG_TLS_WOLFSSL
>      > + tls->bm = BIO_meth_new(0, "bio_mg");
>      > +#else
>      > + tls->bm = BIO_meth_new(BIO_get_new_index() |
>     BIO_TYPE_SOURCE_SINK, "bio_mg");
>      > +#endif
>      > + BIO_meth_set_write(tls->bm, mg_bio_write);
>      > + BIO_meth_set_read(tls->bm, mg_bio_read);
>      > + BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
>      >
>      > -static void sntp_cb(struct mg_connection *c, int ev, void *evd,
>     void *fnd) {
>      > - if (ev == MG_EV_READ) {
>      > - int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
>      > - if (milliseconds > 0) {
>      > - MG_INFO(("%lu got time: %lld ms from epoch", c->id,
>     milliseconds));
>      > - mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
>      > - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
>      > - (unsigned) (milliseconds % 1000)));
>      > - }
>      > - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
>      > - } else if (ev == MG_EV_CONNECT) {
>      > - mg_sntp_request(c);
>      > - } else if (ev == MG_EV_CLOSE) {
>      > + bio = BIO_new(tls->bm);
>      > + BIO_set_data(bio, c);
>      > + SSL_set_bio(tls->ssl, bio, bio);
>      > +
>      > + c->tls = tls;
>      > + c->is_tls = 1;
>      > + c->is_tls_hs = 1;
>      > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>     0) {
>      > + mg_tls_handshake(c);
>      > }
>      > - (void) fnd;
>      > - (void) evd;
>      > + MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
>     "client"));
>      > + return;
>      > +fail:
>      > + free(tls);
>      > }
>      >
>      > -void mg_sntp_request(struct mg_connection *c) {
>      > - if (c->is_resolving) {
>      > - MG_ERROR(("%lu wait until resolved", c->id));
>      > +void mg_tls_handshake(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + int rc = c->is_client ? SSL_connect(tls->ssl) :
>     SSL_accept(tls->ssl);
>      > + if (rc == 1) {
>      > + MG_DEBUG(("%lu success", c->id));
>      > + c->is_tls_hs = 0;
>      > + mg_call(c, MG_EV_TLS_HS, NULL);
>      > } else {
>      > - int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
>      > - uint8_t buf[48] = {0};
>      > - uint32_t *t = (uint32_t *) &buf[40];
>      > - double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
>      > - buf[0] = (0 << 6) | (4 << 3) | 3;
>      > - t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
>      > - t[1] = mg_htonl((uint32_t) frac);
>      > - mg_send(c, buf, sizeof(buf));
>      > + int code = mg_tls_err(c, tls, rc);
>      > + if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
>      > }
>      > }
>      >
>      > -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
>     char *url,
>      > - mg_event_handler_t fn, void *fnd) {
>      > - struct mg_connection *c = NULL;
>      > - if (url == NULL) url = "udp://time.google.com:123
>     <http://time.google.com:123>";
>      > - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
>      > - return c;
>      > +void mg_tls_free(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + if (tls == NULL) return;
>      > + SSL_free(tls->ssl);
>      > + SSL_CTX_free(tls->ctx);
>      > + BIO_meth_free(tls->bm);
>      > + free(tls);
>      > + c->tls = NULL;
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/sock.c"
>      > -#endif
>      > -
>      > +size_t mg_tls_pending(struct mg_connection *c) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
>      > +}
>      >
>      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + int n = SSL_read(tls->ssl, buf, (int) len);
>      > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > +}
>      >
>      > +long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > + int n = SSL_write(tls->ssl, buf, (int) len);
>      > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
>      > + if (n <= 0) return MG_IO_ERR;
>      > + return n;
>      > +}
>      >
>      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > +}
>      >
>      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>      > + (void) mgr;
>      > +}
>      > +#endif
>      >
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/tls_uecc.c"
>      > +#endif
>      > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
>     2-clause license. */
>      >
>      >
>      >
>      >
>      > -#if MG_ENABLE_SOCKET
>      > +#if MG_TLS == MG_TLS_BUILTIN
>      >
>      > -#ifndef closesocket
>      > -#define closesocket(x) close(x)
>      > +#ifndef MG_UECC_RNG_MAX_TRIES
>      > +#define MG_UECC_RNG_MAX_TRIES 64
>      > #endif
>      >
>      > -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
>      > -#define S2PTR(s_) ((void *) (size_t) (s_))
>      > -
>      > -#ifndef MSG_NONBLOCKING
>      > -#define MSG_NONBLOCKING 0
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +#define MG_UECC_VLI_API
>      > +#else
>      > +#define MG_UECC_VLI_API static
>      > #endif
>      >
>      > -#ifndef AF_INET6
>      > -#define AF_INET6 10
>      > +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM ==
>     mg_uecc_arm) || \
>      > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb) || \
>      > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
>      > +#define CONCATX(a, ...) a##__VA_ARGS__
>      > +#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
>      > +
>      > +#define STRX(a) #a
>      > +#define STR(a) STRX(a)
>      > +
>      > +#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
>      > +#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
>      > +#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
>      > +#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
>      > +#define EVAL4(...) __VA_ARGS__
>      > +
>      > +#define DEC_1 0
>      > +#define DEC_2 1
>      > +#define DEC_3 2
>      > +#define DEC_4 3
>      > +#define DEC_5 4
>      > +#define DEC_6 5
>      > +#define DEC_7 6
>      > +#define DEC_8 7
>      > +#define DEC_9 8
>      > +#define DEC_10 9
>      > +#define DEC_11 10
>      > +#define DEC_12 11
>      > +#define DEC_13 12
>      > +#define DEC_14 13
>      > +#define DEC_15 14
>      > +#define DEC_16 15
>      > +#define DEC_17 16
>      > +#define DEC_18 17
>      > +#define DEC_19 18
>      > +#define DEC_20 19
>      > +#define DEC_21 20
>      > +#define DEC_22 21
>      > +#define DEC_23 22
>      > +#define DEC_24 23
>      > +#define DEC_25 24
>      > +#define DEC_26 25
>      > +#define DEC_27 26
>      > +#define DEC_28 27
>      > +#define DEC_29 28
>      > +#define DEC_30 29
>      > +#define DEC_31 30
>      > +#define DEC_32 31
>      > +
>      > +#define DEC(N) CONCAT(DEC_, N)
>      > +
>      > +#define SECOND_ARG(_, val, ...) val
>      > +#define SOME_CHECK_0 ~, 0
>      > +#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
>      > +#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
>      > +
>      > +#define EMPTY(...)
>      > +#define DEFER(...) __VA_ARGS__ EMPTY()
>      > +
>      > +#define REPEAT_NAME_0() REPEAT_0
>      > +#define REPEAT_NAME_SOME() REPEAT_SOME
>      > +#define REPEAT_0(...)
>      > +#define REPEAT_SOME(N, stuff) \
>      > + DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff)
>     stuff
>      > +#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
>      > +
>      > +#define REPEATM_NAME_0() REPEATM_0
>      > +#define REPEATM_NAME_SOME() REPEATM_SOME
>      > +#define REPEATM_0(...)
>      > +#define REPEATM_SOME(N, macro) \
>      > + macro(N) DEFER(CONCAT(REPEATM_NAME_,
>     SOME_OR_0(DEC(N))))()(DEC(N), macro)
>      > +#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
>      > #endif
>      >
>      > -union usa {
>      > - struct sockaddr sa;
>      > - struct sockaddr_in sin;
>      > -#if MG_ENABLE_IPV6
>      > - struct sockaddr_in6 sin6;
>      > -#endif
>      > -};
>      > +//
>      >
>      > -static socklen_t tousa(struct mg_addr *a, union usa *usa) {
>      > - socklen_t len = sizeof(usa->sin);
>      > - memset(usa, 0, sizeof(*usa));
>      > - usa->sin.sin_family = AF_INET;
>      > - usa->sin.sin_port = a->port;
>      > - *(uint32_t *) &usa->sin.sin_addr = a->ip;
>      > -#if MG_ENABLE_IPV6
>      > - if (a->is_ip6) {
>      > - usa->sin.sin_family = AF_INET6;
>      > - usa->sin6.sin6_port = a->port;
>      > - memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
>      > - len = sizeof(usa->sin6);
>      > - }
>      > +#if (MG_UECC_WORD_SIZE == 1)
>      > +#if MG_UECC_SUPPORTS_secp160r1
>      > +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
>      > #endif
>      > - return len;
>      > -}
>      > -
>      > -static void tomgaddr(union usa *usa, struct mg_addr *a, bool
>     is_ip6) {
>      > - a->is_ip6 = is_ip6;
>      > - a->port = usa->sin.sin_port;
>      > - memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
>      > -#if MG_ENABLE_IPV6
>      > - if (is_ip6) {
>      > - memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
>      > - a->port = usa->sin6.sin6_port;
>      > - }
>      > +#if MG_UECC_SUPPORTS_secp192r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 24
>      > #endif
>      > -}
>      > -
>      > -static bool mg_sock_would_block(void) {
>      > - int err = MG_SOCKET_ERRNO;
>      > - return err == EINPROGRESS || err == EWOULDBLOCK
>      > -#ifndef WINCE
>      > - || err == EAGAIN || err == EINTR
>      > +#if MG_UECC_SUPPORTS_secp224r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 28
>      > #endif
>      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > - || err == WSAEINTR || err == WSAEWOULDBLOCK
>      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 32
>      > #endif
>      > - ;
>      > -}
>      > -
>      > -static bool mg_sock_conn_reset(void) {
>      > - int err = MG_SOCKET_ERRNO;
>      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > - return err == WSAECONNRESET;
>      > -#else
>      > - return err == EPIPE || err == ECONNRESET;
>      > +#elif (MG_UECC_WORD_SIZE == 4)
>      > +#if MG_UECC_SUPPORTS_secp160r1
>      > +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
>      > #endif
>      > -}
>      > +#if MG_UECC_SUPPORTS_secp192r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 6
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp224r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 7
>      > +#endif
>      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 8
>      > +#endif
>      > +#elif (MG_UECC_WORD_SIZE == 8)
>      > +#if MG_UECC_SUPPORTS_secp160r1
>      > +#define MG_UECC_MAX_WORDS 3
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp192r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 3
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp224r1
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 4
>      > +#endif
>      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>      > +#undef MG_UECC_MAX_WORDS
>      > +#define MG_UECC_MAX_WORDS 4
>      > +#endif
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +
>      > +#define BITS_TO_WORDS(num_bits) \
>      > + ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
>      > + (MG_UECC_WORD_SIZE * 8)))
>      > +#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
>      > +
>      > +struct MG_UECC_Curve_t {
>      > + wordcount_t num_words;
>      > + wordcount_t num_bytes;
>      > + bitcount_t num_n_bits;
>      > + mg_uecc_word_t p[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t n[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
>      > + mg_uecc_word_t b[MG_UECC_MAX_WORDS];
>      > + void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
>      > +#endif
>      > + void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
>      > + MG_UECC_Curve curve);
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t
>     *product);
>      > +#endif
>      > +};
>      >
>      > -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
>      > - union usa usa;
>      > - socklen_t n = sizeof(usa);
>      > - if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
>      > - tomgaddr(&usa, addr, n != sizeof(usa.sin));
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > +static void bcopy(uint8_t *dst, const uint8_t *src, unsigned
>     num_bytes) {
>      > + while (0 != num_bytes) {
>      > + num_bytes--;
>      > + dst[num_bytes] = src[num_bytes];
>      > }
>      > }
>      > +#endif
>      >
>      > -static void iolog(struct mg_connection *c, char *buf, long n,
>     bool r) {
>      > - if (n == MG_IO_WAIT) {
>      > - // Do nothing
>      > - } else if (n <= 0) {
>      > - c->is_closing = 1; // Termination. Don't call mg_error(): #1529
>      > - } else if (n > 0) {
>      > - if (c->is_hexdumping) {
>      > - union usa usa;
>      > - socklen_t slen = sizeof(usa.sin);
>      > - if (getsockname(FD(c), &usa.sa <http://usa.sa>, &slen) < 0)
>     (void) 0; // Ignore result
>      > - MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
>      > - r ? "<-" : "->", 4, &c->rem.ip, n));
>      > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
>     *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words);
>      >
>      > - mg_hexdump(buf, (size_t) n);
>      > - }
>      > - if (r) {
>      > - c->recv.len += (size_t) n;
>      > - mg_call(c, MG_EV_READ, &n);
>      > - } else {
>      > - mg_iobuf_del(&c->send, 0, (size_t) n);
>      > - // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
>      > - if (c->send.len == 0) {
>      > - MG_EPOLL_MOD(c, 0);
>      > - }
>      > - mg_call(c, MG_EV_WRITE, &n);
>      > - }
>      > - }
>      > -}
>      > +#if (MG_UECC_PLATFORM == mg_uecc_arm || \
>      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
>      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
>      >
>      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > - long n;
>      > - if (c->is_udp) {
>      > - union usa usa;
>      > - socklen_t slen = tousa(&c->rem, &usa);
>      > - n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
>     <http://usa.sa>, slen);
>      > - if (n > 0) setlocaddr(FD(c), &c->loc);
>      > - } else {
>      > - n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>      > -#if MG_ARCH == MG_ARCH_RTX
>      > - if (n == EWOULDBLOCK) return MG_IO_WAIT;
>      > #endif
>      > - }
>      > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
>      > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > -}
>      >
>      > -bool mg_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > - if (c->is_udp) {
>      > - long n = mg_io_send(c, buf, len);
>      > - MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int)
>     c->send.len,
>      > - (int) c->recv.len, n, MG_SOCKET_ERRNO));
>      > - iolog(c, (char *) buf, n, false);
>      > - return n > 0;
>      > - } else {
>      > - return mg_iobuf_add(&c->send, c->send.len, buf, len);
>      > - }
>      > -}
>      > +#if (MG_UECC_PLATFORM == mg_uecc_avr)
>      >
>      > -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
>      > -#if defined(MG_CUSTOM_NONBLOCK)
>      > - MG_CUSTOM_NONBLOCK(fd);
>      > -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>      > - unsigned long on = 1;
>      > - ioctlsocket(fd, FIONBIO, &on);
>      > -#elif MG_ARCH == MG_ARCH_RTX
>      > - unsigned long on = 1;
>      > - ioctlsocket(fd, FIONBIO, &on);
>      > -#elif MG_ENABLE_FREERTOS_TCP
>      > - const BaseType_t off = 0;
>      > - if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
>     != 0) (void) 0;
>      > - if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
>     != 0) (void) 0;
>      > -#elif MG_ENABLE_LWIP
>      > - lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
>      > -#elif MG_ARCH == MG_ARCH_AZURERTOS
>      > - fcntl(fd, F_SETFL, O_NONBLOCK);
>      > -#elif MG_ARCH == MG_ARCH_TIRTOS
>      > - int val = 0;
>      > - setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
>      > - // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
>      > - int sz = sizeof(val);
>      > - getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
>      > - val /= 2; // set send low-water mark at half send buffer size
>      > - setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
>      > -#else
>      > - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
>     Non-blocking mode
>      > - fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
>      > #endif
>      > -}
>      >
>      > -bool mg_open_listener(struct mg_connection *c, const char *url) {
>      > - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>      > - bool success = false;
>      > - c->loc.port = mg_htons(mg_url_port(url));
>      > - if (!mg_aton(mg_url_host(url), &c->loc)) {
>      > - MG_ERROR(("invalid listening URL: %s", url));
>      > - } else {
>      > - union usa usa;
>      > - socklen_t slen = tousa(&c->loc, &usa);
>      > - int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
>      > - int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
>     SOCK_STREAM;
>      > - int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
>      > - (void) on;
>      > +#ifndef asm_clear
>      > +#define asm_clear 0
>      > +#endif
>      > +#ifndef asm_set
>      > +#define asm_set 0
>      > +#endif
>      > +#ifndef asm_add
>      > +#define asm_add 0
>      > +#endif
>      > +#ifndef asm_sub
>      > +#define asm_sub 0
>      > +#endif
>      > +#ifndef asm_mult
>      > +#define asm_mult 0
>      > +#endif
>      > +#ifndef asm_rshift1
>      > +#define asm_rshift1 0
>      > +#endif
>      > +#ifndef asm_mmod_fast_secp256r1
>      > +#define asm_mmod_fast_secp256r1 0
>      > +#endif
>      >
>      > - if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
>      > - MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
>      > -#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
>      > - (defined(LWIP_SOCKET) && SO_REUSE == 1))
>      > - } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
>      > - sizeof(on)) != 0) {
>      > - // 1. SO_RESUSEADDR is not enabled on Windows because the
>     semantics of
>      > - // SO_REUSEADDR on UNIX and Windows is different. On Windows,
>      > - // SO_REUSEADDR allows to bind a socket to a port without error
>     even
>      > - // if the port is already open by another program. This is not the
>      > - // behavior SO_REUSEADDR was designed for, and leads to
>     hard-to-track
>      > - // failure scenarios. Therefore, SO_REUSEADDR was disabled on
>     Windows
>      > - // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
>      > - // 2. In case of LWIP, SO_REUSEADDR should be explicitly
>     enabled, by
>      > - // defining
>      > - // SO_REUSE (in lwipopts.h), otherwise the code below will compile
>      > - // but won't work! (setsockopt will return EINVAL)
>      > - MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
>      > -#endif
>      > -#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) &&
>     !defined(WINCE)
>      > - } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
>     (char *) &on,
>      > - sizeof(on)) != 0) {
>      > - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
>      > - MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
>      > +#if defined(default_RNG_defined) && default_RNG_defined
>      > +static MG_UECC_RNG_Function g_rng_function = &default_RNG;
>      > +#else
>      > +static MG_UECC_RNG_Function g_rng_function = 0;
>      > #endif
>      > - } else if (bind(fd, &usa.sa <http://usa.sa>, slen) != 0) {
>      > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
>      > - } else if ((type == SOCK_STREAM &&
>      > - listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
>      > - // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
>      > - // In case port was set to 0, get the real port number
>      > - MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
>      > - } else {
>      > - setlocaddr(fd, &c->loc);
>      > - mg_set_non_blocking_mode(fd);
>      > - c->fd = S2PTR(fd);
>      > - MG_EPOLL_ADD(c);
>      > - success = true;
>      > - }
>      > - }
>      > - if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
>      > - return success;
>      > +
>      > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
>      > + g_rng_function = rng_function;
>      > +}
>      > +
>      > +MG_UECC_RNG_Function mg_uecc_get_rng(void) {
>      > + return g_rng_function;
>      > }
>      >
>      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>      > - long n = 0;
>      > - if (c->is_udp) {
>      > - union usa usa;
>      > - socklen_t slen = tousa(&c->rem, &usa);
>      > - n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
>     <http://usa.sa>, &slen);
>      > - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
>      > - } else {
>      > - n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>      > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
>      > + return BITS_TO_BYTES(curve->num_n_bits);
>      > +}
>      > +
>      > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
>      > + return 2 * curve->num_bytes;
>      > +}
>      > +
>      > +#if !asm_clear
>      > +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
>      > + wordcount_t num_words) {
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words; ++i) {
>      > + vli[i] = 0;
>      > }
>      > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
>      > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > }
>      > +#endif /* !asm_clear */
>      >
>      > -// NOTE(lsm): do only one iteration of reads, cause some systems
>      > -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
>     no data
>      > -static void read_conn(struct mg_connection *c) {
>      > - long n = -1;
>      > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
>      > - mg_error(c, "max_recv_buf_size reached");
>      > - } else if (c->recv.size <= c->recv.len &&
>      > - !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
>      > - mg_error(c, "oom");
>      > - } else {
>      > - char *buf = (char *) &c->recv.buf[c->recv.len];
>      > - size_t len = c->recv.size - c->recv.len;
>      > - n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf,
>     len);
>      > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
>     c->fd,
>      > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>      > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
>      > - iolog(c, buf, n, true);
>      > +/* Constant-time comparison to zero - secure way to compare long
>     integers */
>      > +/* Returns 1 if vli == 0, 0 otherwise. */
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const
>     mg_uecc_word_t *vli,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t bits = 0;
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words; ++i) {
>      > + bits |= vli[i];
>      > }
>      > + return (bits == 0);
>      > }
>      >
>      > -static void write_conn(struct mg_connection *c) {
>      > - char *buf = (char *) c->send.buf;
>      > - size_t len = c->send.len;
>      > - long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
>     buf, len);
>      > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
>     c->fd,
>      > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>      > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
>      > - iolog(c, buf, n, false);
>      > +/* Returns nonzero if bit 'bit' of vli is set. */
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const
>     mg_uecc_word_t *vli,
>      > + bitcount_t bit) {
>      > + return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
>      > + ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
>      > }
>      >
>      > -static void close_conn(struct mg_connection *c) {
>      > - if (FD(c) != MG_INVALID_SOCKET) {
>      > -#if MG_ENABLE_EPOLL
>      > - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
>      > -#endif
>      > - closesocket(FD(c));
>      > -#if MG_ENABLE_FREERTOS_TCP
>      > - FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
>      > -#endif
>      > +/* Counts the number of words in vli. */
>      > +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
>      > + const wordcount_t max_words) {
>      > + wordcount_t i;
>      > + /* Search from the end until we find a non-zero digit.
>      > + We do it in reverse because we expect that most digits will be
>     nonzero. */
>      > + for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
>      > }
>      > - mg_close_conn(c);
>      > +
>      > + return (i + 1);
>      > }
>      >
>      > -static void connect_conn(struct mg_connection *c) {
>      > - union usa usa;
>      > - socklen_t n = sizeof(usa);
>      > - // Use getpeername() to test whether we have connected
>      > - if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
>      > - c->is_connecting = 0;
>      > - mg_call(c, MG_EV_CONNECT, NULL);
>      > - MG_EPOLL_MOD(c, 0);
>      > - if (c->is_tls_hs) mg_tls_handshake(c);
>      > - } else {
>      > - mg_error(c, "socket error");
>      > +/* Counts the number of bits required to represent vli. */
>      > +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const
>     mg_uecc_word_t *vli,
>      > + const wordcount_t max_words) {
>      > + mg_uecc_word_t i;
>      > + mg_uecc_word_t digit;
>      > +
>      > + wordcount_t num_digits = vli_numDigits(vli, max_words);
>      > + if (num_digits == 0) {
>      > + return 0;
>      > }
>      > -}
>      >
>      > -static void setsockopts(struct mg_connection *c) {
>      > -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
>      > - MG_ARCH == MG_ARCH_TIRTOS
>      > - (void) c;
>      > -#else
>      > - int on = 1;
>      > -#if !defined(SOL_TCP)
>      > -#define SOL_TCP IPPROTO_TCP
>      > -#endif
>      > - if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
>     sizeof(on)) != 0)
>      > - (void) 0;
>      > - if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
>     sizeof(on)) !=
>      > - 0)
>      > - (void) 0;
>      > -#endif
>      > + digit = vli[num_digits - 1];
>      > + for (i = 0; digit; ++i) {
>      > + digit >>= 1;
>      > + }
>      > +
>      > + return (((bitcount_t) ((num_digits - 1) <<
>     MG_UECC_WORD_BITS_SHIFT)) +
>      > + (bitcount_t) i);
>      > }
>      >
>      > -void mg_connect_resolved(struct mg_connection *c) {
>      > - int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
>      > - int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
>     resolved IP
>      > - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
>      > - c->is_resolving = 0; // Clear resolving flag
>      > - if (FD(c) == MG_INVALID_SOCKET) {
>      > - mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
>      > - } else if (c->is_udp) {
>      > - MG_EPOLL_ADD(c);
>      > -#if MG_ARCH == MG_ARCH_TIRTOS
>      > - union usa usa; // TI-RTOS NDK requires binding to receive on
>     UDP sockets
>      > - socklen_t slen = tousa(&c->loc, &usa);
>      > - if (bind(c->fd, &usa.sa <http://usa.sa>, slen) != 0)
>      > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
>      > -#endif
>      > - mg_call(c, MG_EV_RESOLVE, NULL);
>      > - mg_call(c, MG_EV_CONNECT, NULL);
>      > - } else {
>      > - union usa usa;
>      > - socklen_t slen = tousa(&c->rem, &usa);
>      > - mg_set_non_blocking_mode(FD(c));
>      > - setsockopts(c);
>      > - MG_EPOLL_ADD(c);
>      > - mg_call(c, MG_EV_RESOLVE, NULL);
>      > - if ((rc = connect(FD(c), &usa.sa <http://usa.sa>, slen)) == 0) {
>      > - mg_call(c, MG_EV_CONNECT, NULL);
>      > - } else if (mg_sock_would_block()) {
>      > - MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
>      > - mg_ntohs(c->rem.port)));
>      > - c->is_connecting = 1;
>      > - } else {
>      > - mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
>      > +/* Sets dest = src. */
>      > +#if !asm_set
>      > +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
>      > + const mg_uecc_word_t *src,
>      > + wordcount_t num_words) {
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words; ++i) {
>      > + dest[i] = src[i];
>      > + }
>      > +}
>      > +#endif /* !asm_set */
>      > +
>      > +/* Returns sign of left - right. */
>      > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
>     *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + wordcount_t i;
>      > + for (i = num_words - 1; i >= 0; --i) {
>      > + if (left[i] > right[i]) {
>      > + return 1;
>      > + } else if (left[i] < right[i]) {
>      > + return -1;
>      > }
>      > }
>      > - (void) rc;
>      > + return 0;
>      > }
>      >
>      > -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
>      > - socklen_t *len) {
>      > - MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
>      > - do {
>      > - memset(usa, 0, sizeof(*usa));
>      > - s = accept(sock, &usa->sa, len);
>      > - } while (s == MG_INVALID_SOCKET && errno == EINTR);
>      > - return s;
>      > +/* Constant-time comparison function - secure way to compare
>     long integers */
>      > +/* Returns one if left == right, zero otherwise. */
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t diff = 0;
>      > + wordcount_t i;
>      > + for (i = num_words - 1; i >= 0; --i) {
>      > + diff |= (left[i] ^ right[i]);
>      > + }
>      > + return (diff == 0);
>      > +}
>      > +
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words);
>      > +
>      > +/* Returns sign of left - right, in constant time. */
>      > +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t
>     *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right,
>     num_words);
>      > + mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
>      > + return (cmpresult_t) (!equal - 2 * neg);
>      > +}
>      > +
>      > +/* Computes vli = vli >> 1. */
>      > +#if !asm_rshift1
>      > +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t *end = vli;
>      > + mg_uecc_word_t carry = 0;
>      > +
>      > + vli += num_words;
>      > + while (vli-- > end) {
>      > + mg_uecc_word_t temp = *vli;
>      > + *vli = (temp >> 1) | carry;
>      > + carry = temp << (MG_UECC_WORD_BITS - 1);
>      > + }
>      > }
>      > +#endif /* !asm_rshift1 */
>      > +
>      > +/* Computes result = left + right, returning carry. Can modify
>     in place. */
>      > +#if !asm_add
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t carry = 0;
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words; ++i) {
>      > + mg_uecc_word_t sum = left[i] + right[i] + carry;
>      > + if (sum != left[i]) {
>      > + carry = (sum < left[i]);
>      > + }
>      > + result[i] = sum;
>      > + }
>      > + return carry;
>      > +}
>      > +#endif /* !asm_add */
>      > +
>      > +/* Computes result = left - right, returning borrow. Can modify
>     in place. */
>      > +#if !asm_sub
>      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t borrow = 0;
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words; ++i) {
>      > + mg_uecc_word_t diff = left[i] - right[i] - borrow;
>      > + if (diff != left[i]) {
>      > + borrow = (diff > left[i]);
>      > + }
>      > + result[i] = diff;
>      > + }
>      > + return borrow;
>      > +}
>      > +#endif /* !asm_sub */
>      > +
>      > +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) || \
>      > + (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0)
>     && \
>      > + ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
>      > +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b,
>     mg_uecc_word_t *r0,
>      > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
>      > +#if MG_UECC_WORD_SIZE == 8
>      > + uint64_t a0 = a & 0xffffffff;
>      > + uint64_t a1 = a >> 32;
>      > + uint64_t b0 = b & 0xffffffff;
>      > + uint64_t b1 = b >> 32;
>      > +
>      > + uint64_t i0 = a0 * b0;
>      > + uint64_t i1 = a0 * b1;
>      > + uint64_t i2 = a1 * b0;
>      > + uint64_t i3 = a1 * b1;
>      > +
>      > + uint64_t p0, p1;
>      > +
>      > + i2 += (i0 >> 32);
>      > + i2 += i1;
>      > + if (i2 < i1) { /* overflow */
>      > + i3 += 0x100000000;
>      > + }
>      >
>      > -static void accept_conn(struct mg_mgr *mgr, struct mg_connection
>     *lsn) {
>      > - struct mg_connection *c = NULL;
>      > - union usa usa;
>      > - socklen_t sa_len = sizeof(usa);
>      > - MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
>      > - if (fd == MG_INVALID_SOCKET) {
>      > -#if MG_ARCH == MG_ARCH_AZURERTOS
>      > - // AzureRTOS, in non-block socket mode can mark listening
>     socket readable
>      > - // even it is not. See comment for 'select' func implementation in
>      > - // nx_bsd.c That's not an error, just should try later
>      > - if (MG_SOCKET_ERRNO != EAGAIN)
>      > -#endif
>      > - MG_ERROR(("%lu accept failed, errno %d", lsn->id,
>     MG_SOCKET_ERRNO));
>      > -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
>      > - (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
>      > - } else if ((long) fd >= FD_SETSIZE) {
>      > - MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
>      > - closesocket(fd);
>      > + p0 = (i0 & 0xffffffff) | (i2 << 32);
>      > + p1 = i3 + (i2 >> 32);
>      > +
>      > + *r0 += p0;
>      > + *r1 += (p1 + (*r0 < p0));
>      > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
>      > +#else
>      > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
>      > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
>     MG_UECC_WORD_BITS) | *r0;
>      > + r01 += p;
>      > + *r2 += (r01 < p);
>      > + *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
>      > + *r0 = (mg_uecc_word_t) r01;
>      > #endif
>      > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>      > - MG_ERROR(("%lu OOM", lsn->id));
>      > - closesocket(fd);
>      > - } else {
>      > - tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
>      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>      > - c->fd = S2PTR(fd);
>      > - MG_EPOLL_ADD(c);
>      > - mg_set_non_blocking_mode(FD(c));
>      > - setsockopts(c);
>      > - c->is_accepted = 1;
>      > - c->is_hexdumping = lsn->is_hexdumping;
>      > - c->loc = lsn->loc;
>      > - c->pfn = lsn->pfn;
>      > - c->pfn_data = lsn->pfn_data;
>      > - c->fn = lsn->fn;
>      > - c->fn_data = lsn->fn_data;
>      > - MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4,
>     &c->rem.ip,
>      > - mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
>      > - mg_call(c, MG_EV_OPEN, NULL);
>      > - mg_call(c, MG_EV_ACCEPT, NULL);
>      > +}
>      > +#endif /* muladd needed */
>      > +
>      > +#if !asm_mult
>      > +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t r0 = 0;
>      > + mg_uecc_word_t r1 = 0;
>      > + mg_uecc_word_t r2 = 0;
>      > + wordcount_t i, k;
>      > +
>      > + /* Compute each digit of result in sequence, maintaining the
>     carries. */
>      > + for (k = 0; k < num_words; ++k) {
>      > + for (i = 0; i <= k; ++i) {
>      > + muladd(left[i], right[k - i], &r0, &r1, &r2);
>      > + }
>      > + result[k] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + r2 = 0;
>      > }
>      > + for (k = num_words; k < num_words * 2 - 1; ++k) {
>      > + for (i = (wordcount_t) ((k + 1) - num_words); i < num_words;
>     ++i) {
>      > + muladd(left[i], right[k - i], &r0, &r1, &r2);
>      > + }
>      > + result[k] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + r2 = 0;
>      > + }
>      > + result[num_words * 2 - 1] = r0;
>      > }
>      > +#endif /* !asm_mult */
>      >
>      > -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa
>     usa[2], bool udp) {
>      > - MG_SOCKET_TYPE sock;
>      > - socklen_t n = sizeof(usa[0].sin);
>      > - bool success = false;
>      > +#if MG_UECC_SQUARE_FUNC
>      >
>      > - sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > - (void) memset(&usa[0], 0, sizeof(usa[0]));
>      > - usa[0].sin.sin_family = AF_INET;
>      > - *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
>     127.0.0.1
>      > - usa[1] = usa[0];
>      > +#if !asm_square
>      > +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b,
>     mg_uecc_word_t *r0,
>      > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
>      > +#if MG_UECC_WORD_SIZE == 8
>      > + uint64_t a0 = a & 0xffffffffull;
>      > + uint64_t a1 = a >> 32;
>      > + uint64_t b0 = b & 0xffffffffull;
>      > + uint64_t b1 = b >> 32;
>      >
>      > - if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
>     MG_INVALID_SOCKET &&
>      > - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
>      > - bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n)
>     == 0 &&
>      > - getsockname(sp[0], &usa[0].sa, &n) == 0 &&
>      > - getsockname(sp[1], &usa[1].sa, &n) == 0 &&
>      > - connect(sp[0], &usa[1].sa, n) == 0 &&
>      > - connect(sp[1], &usa[0].sa, n) == 0) {
>      > - success = true;
>      > - } else if (!udp &&
>      > - (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
>      > - bind(sock, &usa[0].sa, n) == 0 &&
>      > - listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
>      > - getsockname(sock, &usa[0].sa, &n) == 0 &&
>      > - (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
>      > - connect(sp[0], &usa[0].sa, n) == 0 &&
>      > - (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
>      > - success = true;
>      > - }
>      > - if (success) {
>      > - mg_set_non_blocking_mode(sp[1]);
>      > - } else {
>      > - if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
>      > - if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
>      > - sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > + uint64_t i0 = a0 * b0;
>      > + uint64_t i1 = a0 * b1;
>      > + uint64_t i2 = a1 * b0;
>      > + uint64_t i3 = a1 * b1;
>      > +
>      > + uint64_t p0, p1;
>      > +
>      > + i2 += (i0 >> 32);
>      > + i2 += i1;
>      > + if (i2 < i1) { /* overflow */
>      > + i3 += 0x100000000ull;
>      > }
>      > - if (sock != MG_INVALID_SOCKET) closesocket(sock);
>      > - return success;
>      > +
>      > + p0 = (i0 & 0xffffffffull) | (i2 << 32);
>      > + p1 = i3 + (i2 >> 32);
>      > +
>      > + *r2 += (p1 >> 63);
>      > + p1 = (p1 << 1) | (p0 >> 63);
>      > + p0 <<= 1;
>      > +
>      > + *r0 += p0;
>      > + *r1 += (p1 + (*r0 < p0));
>      > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
>      > +#else
>      > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
>      > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
>     MG_UECC_WORD_BITS) | *r0;
>      > + *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
>      > + p *= 2;
>      > + r01 += p;
>      > + *r2 += (r01 < p);
>      > + *r1 = r01 >> MG_UECC_WORD_BITS;
>      > + *r0 = (mg_uecc_word_t) r01;
>      > +#endif
>      > }
>      >
>      > -int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void
>     *fn_data,
>      > - bool udp) {
>      > - union usa usa[2];
>      > - MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
>      > - struct mg_connection *c = NULL;
>      > - if (!mg_socketpair(sp, usa, udp)) {
>      > - MG_ERROR(("Cannot create socket pair"));
>      > - } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) ==
>     NULL) {
>      > - closesocket(sp[0]);
>      > - closesocket(sp[1]);
>      > - sp[0] = sp[1] = MG_INVALID_SOCKET;
>      > - } else {
>      > - tomgaddr(&usa[0], &c->rem, false);
>      > - MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
>     sp[0]));
>      > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t r0 = 0;
>      > + mg_uecc_word_t r1 = 0;
>      > + mg_uecc_word_t r2 = 0;
>      > +
>      > + wordcount_t i, k;
>      > +
>      > + for (k = 0; k < num_words * 2 - 1; ++k) {
>      > + mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
>      > + for (i = min; i <= k && i <= k - i; ++i) {
>      > + if (i < k - i) {
>      > + mul2add(left[i], left[k - i], &r0, &r1, &r2);
>      > + } else {
>      > + muladd(left[i], left[k - i], &r0, &r1, &r2);
>      > + }
>      > + }
>      > + result[k] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + r2 = 0;
>      > }
>      > - return (int) sp[0];
>      > -}
>      >
>      > -static bool can_read(const struct mg_connection *c) {
>      > - return c->is_full == false;
>      > + result[num_words * 2 - 1] = r0;
>      > }
>      > +#endif /* !asm_square */
>      >
>      > -static bool can_write(const struct mg_connection *c) {
>      > - return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
>      > -}
>      > +#else /* MG_UECC_SQUARE_FUNC */
>      >
>      > -static bool skip_iotest(const struct mg_connection *c) {
>      > - return (c->is_closing || c->is_resolving || FD(c) ==
>     MG_INVALID_SOCKET) ||
>      > - (can_read(c) == false && can_write(c) == false);
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + wordcount_t num_words) {
>      > + mg_uecc_vli_mult(result, left, left, num_words);
>      > }
>      > +#endif /* MG_UECC_ENABLE_VLI_API */
>      >
>      > -static void mg_iotest(struct mg_mgr *mgr, int ms) {
>      > -#if MG_ENABLE_FREERTOS_TCP
>      > - struct mg_connection *c;
>      > - for (c = mgr->conns; c != NULL; c = c->next) {
>      > - c->is_readable = c->is_writable = 0;
>      > - if (skip_iotest(c)) continue;
>      > - if (can_read(c))
>      > - FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
>      > - if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
>      > - }
>      > - FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
>      > - for (c = mgr->conns; c != NULL; c = c->next) {
>      > - EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
>      > - c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
>      > - c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
>      > - FreeRTOS_FD_CLR(c->fd, mgr->ss,
>      > - eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
>      > +#endif /* MG_UECC_SQUARE_FUNC */
>      > +
>      > +/* Computes result = (left + right) % mod.
>      > + Assumes that left < mod and right < mod, and that result does
>     not overlap
>      > + mod. */
>      > +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right,
>     num_words);
>      > + if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) !=
>     1) {
>      > + /* result > mod (result = mod + remainder), so subtract mod to get
>      > + * remainder. */
>      > + mg_uecc_vli_sub(result, result, mod, num_words);
>      > }
>      > -#elif MG_ENABLE_EPOLL
>      > - size_t max = 1;
>      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > - c->is_readable = c->is_writable = 0;
>      > - if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
>      > - if (can_write(c)) MG_EPOLL_MOD(c, 1);
>      > - max++;
>      > +}
>      > +
>      > +/* Computes result = (left - right) % mod.
>      > + Assumes that left < mod and right < mod, and that result does
>     not overlap
>      > + mod. */
>      > +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right,
>     num_words);
>      > + if (l_borrow) {
>      > + /* In this case, result == -diff == (max int) - diff. Since -x
>     % d == d - x,
>      > + we can get the correct result from result + mod (with
>     overflow). */
>      > + mg_uecc_vli_add(result, result, mod, num_words);
>      > }
>      > - struct epoll_event *evs = (struct epoll_event *) alloca(max *
>     sizeof(evs[0]));
>      > - int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
>      > - for (int i = 0; i < n; i++) {
>      > - struct mg_connection *c = (struct mg_connection *)
>     evs[i].data.ptr;
>      > - if (evs[i].events & EPOLLERR) {
>      > - mg_error(c, "socket error");
>      > - } else if (c->is_readable == 0) {
>      > - bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
>      > - bool wr = evs[i].events & EPOLLOUT;
>      > - c->is_readable = can_read(c) && rd ? 1U : 0;
>      > - c->is_writable = can_write(c) && wr ? 1U : 0;
>      > +}
>      > +
>      > +/* Computes result = product % mod, where product is 2N words
>     long. */
>      > +/* Currently only designed to work for curve_p or curve_n. */
>      > +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t *v[2] = {tmp, product};
>      > + mg_uecc_word_t index;
>      > +
>      > + /* Shift mod so its highest set bit is at the maximum position. */
>      > + bitcount_t shift = (bitcount_t) ((num_words * 2 *
>     MG_UECC_WORD_BITS) -
>      > + mg_uecc_vli_numBits(mod, num_words));
>      > + wordcount_t word_shift = (wordcount_t) (shift /
>     MG_UECC_WORD_BITS);
>      > + wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
>      > + mg_uecc_word_t carry = 0;
>      > + mg_uecc_vli_clear(mod_multiple, word_shift);
>      > + if (bit_shift > 0) {
>      > + for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
>      > + mod_multiple[(mg_uecc_word_t) word_shift + index] =
>      > + (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
>      > + carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
>      > }
>      > + } else {
>      > + mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
>      > }
>      > - (void) skip_iotest;
>      > -#elif MG_ENABLE_POLL
>      > - nfds_t n = 0;
>      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) n++;
>      > - struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
>      > - memset(fds, 0, n * sizeof(fds[0]));
>      > - n = 0;
>      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > - c->is_readable = c->is_writable = 0;
>      > - if (skip_iotest(c)) {
>      > - // Socket not valid, ignore
>      > - } else if (mg_tls_pending(c) > 0) {
>      > - ms = 1; // Don't wait if TLS is ready
>      > - } else {
>      > - fds[n].fd = FD(c);
>      > - if (can_read(c)) fds[n].events |= POLLIN;
>      > - if (can_write(c)) fds[n].events |= POLLOUT;
>      > - n++;
>      > +
>      > + for (index = 1; shift >= 0; --shift) {
>      > + mg_uecc_word_t borrow = 0;
>      > + wordcount_t i;
>      > + for (i = 0; i < num_words * 2; ++i) {
>      > + mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
>      > + if (diff != v[index][i]) {
>      > + borrow = (diff > v[index][i]);
>      > + }
>      > + v[1 - index][i] = diff;
>      > }
>      > + index = !(index ^ borrow); /* Swap the index if there was no
>     borrow */
>      > + mg_uecc_vli_rshift1(mod_multiple, num_words);
>      > + mod_multiple[num_words - 1] |= mod_multiple[num_words]
>      > + << (MG_UECC_WORD_BITS - 1);
>      > + mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
>      > + }
>      > + mg_uecc_vli_set(result, v[index], num_words);
>      > +}
>      > +
>      > +/* Computes result = (left * right) % mod. */
>      > +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_vli_mult(product, left, right, num_words);
>      > + mg_uecc_vli_mmod(result, product, mod, num_words);
>      > +}
>      > +
>      > +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_vli_mult(product, left, right, curve->num_words);
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + curve->mmod_fast(result, product);
>      > +#else
>      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>      > +#endif
>      > +}
>      > +
>      > +#if MG_UECC_SQUARE_FUNC
>      > +
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +/* Computes result = left^2 % mod. */
>      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_vli_square(product, left, num_words);
>      > + mg_uecc_vli_mmod(result, product, mod, num_words);
>      > +}
>      > +#endif /* MG_UECC_ENABLE_VLI_API */
>      > +
>      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>      > + mg_uecc_vli_square(product, left, curve->num_words);
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + curve->mmod_fast(result, product);
>      > +#else
>      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>      > +#endif
>      > +}
>      > +
>      > +#else /* MG_UECC_SQUARE_FUNC */
>      > +
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_vli_modMult(result, left, left, mod, num_words);
>      > +}
>      > +#endif /* MG_UECC_ENABLE_VLI_API */
>      > +
>      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
>     *result,
>      > + const mg_uecc_word_t *left,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_vli_modMult_fast(result, left, left, curve);
>      > +}
>      > +
>      > +#endif /* MG_UECC_SQUARE_FUNC */
>      > +
>      > +#define EVEN(vli) (!(vli[0] & 1))
>      > +static void vli_modInv_update(mg_uecc_word_t *uv, const
>     mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t carry = 0;
>      > + if (!EVEN(uv)) {
>      > + carry = mg_uecc_vli_add(uv, uv, mod, num_words);
>      > }
>      > + mg_uecc_vli_rshift1(uv, num_words);
>      > + if (carry) {
>      > + uv[num_words - 1] |= HIGH_BIT_SET;
>      > + }
>      > +}
>      >
>      > - // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
>      > - if (poll(fds, n, ms) < 0) {
>      > -#if MG_ARCH == MG_ARCH_WIN32
>      > - if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
>      > -#endif
>      > - memset(fds, 0, n * sizeof(fds[0]));
>      > +/* Computes result = (1 / input) % mod. All VLIs are the same size.
>      > + See "From Euclid's GCD to Montgomery Multiplication to the
>     Great Divide" */
>      > +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *input,
>      > + const mg_uecc_word_t *mod,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
>      > + u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
>      > + cmpresult_t cmpResult;
>      > +
>      > + if (mg_uecc_vli_isZero(input, num_words)) {
>      > + mg_uecc_vli_clear(result, num_words);
>      > + return;
>      > }
>      > - n = 0;
>      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>     c->next) {
>      > - if (skip_iotest(c)) {
>      > - // Socket not valid, ignore
>      > - } else if (mg_tls_pending(c) > 0) {
>      > - c->is_readable = 1;
>      > +
>      > + mg_uecc_vli_set(a, input, num_words);
>      > + mg_uecc_vli_set(b, mod, num_words);
>      > + mg_uecc_vli_clear(u, num_words);
>      > + u[0] = 1;
>      > + mg_uecc_vli_clear(v, num_words);
>      > + while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) !=
>     0) {
>      > + if (EVEN(a)) {
>      > + mg_uecc_vli_rshift1(a, num_words);
>      > + vli_modInv_update(u, mod, num_words);
>      > + } else if (EVEN(b)) {
>      > + mg_uecc_vli_rshift1(b, num_words);
>      > + vli_modInv_update(v, mod, num_words);
>      > + } else if (cmpResult > 0) {
>      > + mg_uecc_vli_sub(a, a, b, num_words);
>      > + mg_uecc_vli_rshift1(a, num_words);
>      > + if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
>      > + mg_uecc_vli_add(u, u, mod, num_words);
>      > + }
>      > + mg_uecc_vli_sub(u, u, v, num_words);
>      > + vli_modInv_update(u, mod, num_words);
>      > } else {
>      > - if (fds[n].revents & POLLERR) {
>      > - mg_error(c, "socket error");
>      > - } else {
>      > - c->is_readable =
>      > - (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
>      > - c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
>      > + mg_uecc_vli_sub(b, b, a, num_words);
>      > + mg_uecc_vli_rshift1(b, num_words);
>      > + if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
>      > + mg_uecc_vli_add(v, v, mod, num_words);
>      > }
>      > - n++;
>      > + mg_uecc_vli_sub(v, v, u, num_words);
>      > + vli_modInv_update(v, mod, num_words);
>      > }
>      > }
>      > -#else
>      > - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
>     {0, 0};
>      > - struct mg_connection *c;
>      > - fd_set rset, wset, eset;
>      > - MG_SOCKET_TYPE maxfd = 0;
>      > - int rc;
>      > + mg_uecc_vli_set(result, u, num_words);
>      > +}
>      >
>      > - FD_ZERO(&rset);
>      > - FD_ZERO(&wset);
>      > - FD_ZERO(&eset);
>      > - for (c = mgr->conns; c != NULL; c = c->next) {
>      > - c->is_readable = c->is_writable = 0;
>      > - if (skip_iotest(c)) continue;
>      > - FD_SET(FD(c), &eset);
>      > - if (can_read(c)) FD_SET(FD(c), &rset);
>      > - if (can_write(c)) FD_SET(FD(c), &wset);
>      > - if (mg_tls_pending(c) > 0) tv = tv_zero;
>      > - if (FD(c) > maxfd) maxfd = FD(c);
>      > +/* ------ Point operations ------ */
>      > +
>      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>     2-clause license. */
>      > +
>      > +#ifndef _UECC_CURVE_SPECIFIC_H_
>      > +#define _UECC_CURVE_SPECIFIC_H_
>      > +
>      > +#define num_bytes_secp160r1 20
>      > +#define num_bytes_secp192r1 24
>      > +#define num_bytes_secp224r1 28
>      > +#define num_bytes_secp256r1 32
>      > +#define num_bytes_secp256k1 32
>      > +
>      > +#if (MG_UECC_WORD_SIZE == 1)
>      > +
>      > +#define num_words_secp160r1 20
>      > +#define num_words_secp192r1 24
>      > +#define num_words_secp224r1 28
>      > +#define num_words_secp256r1 32
>      > +#define num_words_secp256k1 32
>      > +
>      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
>      > + 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
>      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
>      > +
>      > +#elif (MG_UECC_WORD_SIZE == 4)
>      > +
>      > +#define num_words_secp160r1 5
>      > +#define num_words_secp192r1 6
>      > +#define num_words_secp224r1 7
>      > +#define num_words_secp256r1 8
>      > +#define num_words_secp256k1 8
>      > +
>      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a,
>     0x##h##g##f##e
>      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
>      > +
>      > +#elif (MG_UECC_WORD_SIZE == 8)
>      > +
>      > +#define num_words_secp160r1 3
>      > +#define num_words_secp192r1 3
>      > +#define num_words_secp224r1 4
>      > +#define num_words_secp256r1 4
>      > +#define num_words_secp256k1 4
>      > +
>      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h)
>     0x##h##g##f##e##d##c##b##a##U
>      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
>      > +
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +
>      > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
>      > + MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
>      > +static void double_jacobian_default(mg_uecc_word_t *X1,
>     mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
>      > + /* t1 = X, t2 = Y, t3 = Z */
>      > + mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + if (mg_uecc_vli_isZero(Z1, num_words)) {
>      > + return;
>      > }
>      >
>      > - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) <
>     0) {
>      > -#if MG_ARCH == MG_ARCH_WIN32
>      > - if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
>     sockets
>      > + mg_uecc_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */
>      > + mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 =
>     A */
>      > + mg_uecc_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */
>      > + mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
>      > + mg_uecc_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */
>      > +
>      > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1
>     + z1^2 */
>      > + mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 =
>     2*z1^2 */
>      > + mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1
>     - z1^2 */
>      > + mg_uecc_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 -
>     z1^4 */
>      > +
>      > + mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
>      > + num_words); /* t3 = 2*(x1^2 - z1^4) */
>      > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
>      > + num_words); /* t1 = 3*(x1^2 - z1^4) */
>      > + if (mg_uecc_vli_testBit(X1, 0)) {
>      > + mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p,
>     num_words);
>      > + mg_uecc_vli_rshift1(X1, num_words);
>      > + X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
>      > + } else {
>      > + mg_uecc_vli_rshift1(X1, num_words);
>      > + }
>      > + /* t1 = 3/2*(x1^2 - z1^4) = B */
>      > +
>      > + mg_uecc_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */
>      > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
>     B^2 - A */
>      > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
>     B^2 - 2A = x3 */
>      > + mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A
>     - x3 */
>      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A -
>     x3) */
>      > + mg_uecc_vli_modSub(t4, X1, t4, curve->p,
>      > + num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
>      > +
>      > + mg_uecc_vli_set(X1, Z1, num_words);
>      > + mg_uecc_vli_set(Z1, Y1, num_words);
>      > + mg_uecc_vli_set(Y1, t4, num_words);
>      > +}
>      > +
>      > +/* Computes result = x^3 + ax + b. result must not overlap x. */
>      > +static void x_side_default(mg_uecc_word_t *result, const
>     mg_uecc_word_t *x,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
>      > + mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /*
>     r = x^2 - 3 */
>      > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3
>     - 3x */
>      > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
>      > + num_words); /* r = x^3 - 3x + b */
>      > +}
>      > +#endif /* MG_UECC_SUPPORTS_secp... */
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
>      > + MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
>      > +/* Compute a = sqrt(a) (mod curve_p). */
>      > +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve
>     curve) {
>      > + bitcount_t i;
>      > + mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
>      > + mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + /* When curve->p == 3 (mod 4), we can compute
>      > + sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
>      > + mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p +
>     1 */
>      > + for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
>      > + mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
>      > + if (mg_uecc_vli_testBit(p1, i)) {
>      > + mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
>      > + }
>      > + }
>      > + mg_uecc_vli_set(a, l_result, num_words);
>      > +}
>      > +#endif /* MG_UECC_SUPPORTS_secp... */
>      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>      > +
>      > +#if MG_UECC_SUPPORTS_secp160r1
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product);
>      > +#endif
>      > +
>      > +static const struct MG_UECC_Curve_t curve_secp160r1 = {
>      > + num_words_secp160r1,
>      > + num_bytes_secp160r1,
>      > + 161, /* num_n_bits */
>      > + {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
>      > + BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
>      > + {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
>      > + BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
>      > + BYTES_TO_WORDS_4(68, B5, 96, 4A),
>      > +
>      > + BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
>      > + BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
>      > + BYTES_TO_WORDS_4(55, 28, A6, 23)},
>      > + {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
>      > + BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
>      > + BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
>      > + &double_jacobian_default,
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + &mod_sqrt_default,
>      > +#endif
>      > + &x_side_default,
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + &vli_mmod_fast_secp160r1
>      > +#endif
>      > +};
>      > +
>      > +MG_UECC_Curve mg_uecc_secp160r1(void) {
>      > + return &curve_secp160r1;
>      > +}
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
>      > +/* Computes result = product % curve_p
>      > + see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
>     <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf> page 354
>      > +
>      > + Note that this only works if log2(omega) < log2(p) / 2 */
>      > +static void omega_mult_secp160r1(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *right);
>      > +#if MG_UECC_WORD_SIZE == 8
>      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product) {
>      > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
>      > + mg_uecc_word_t copy;
>      > +
>      > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
>      > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
>      > +
>      > + omega_mult_secp160r1(tmp,
>      > + product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
>      > +
>      > + product[num_words_secp160r1 - 1] &= 0xffffffff;
>      > + copy = tmp[num_words_secp160r1 - 1];
>      > + tmp[num_words_secp160r1 - 1] &= 0xffffffff;
>      > + mg_uecc_vli_add(result, product, tmp,
>      > + num_words_secp160r1); /* (C, r) = r + q */
>      > + mg_uecc_vli_clear(product, num_words_secp160r1);
>      > + tmp[num_words_secp160r1 - 1] = copy;
>      > + omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1);
>     /* Rq*c */
>      > + mg_uecc_vli_add(result, result, product,
>      > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
>      > +
>      > + while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
>      > + num_words_secp160r1) > 0) {
>      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>     num_words_secp160r1);
>      > + }
>      > +}
>      > +
>      > +static void omega_mult_secp160r1(uint64_t *result, const
>     uint64_t *right) {
>      > + uint32_t carry;
>      > + unsigned i;
>      > +
>      > + /* Multiply by (2^31 + 1). */
>      > + carry = 0;
>      > + for (i = 0; i < num_words_secp160r1; ++i) {
>      > + uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
>      > + result[i] = (tmp << 31) + tmp + carry;
>      > + carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i]
>     == tmp));
>      > + }
>      > + result[i] = carry;
>      > +}
>      > #else
>      > - MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
>      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product) {
>      > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
>      > + mg_uecc_word_t carry;
>      > +
>      > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
>      > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
>      > +
>      > + omega_mult_secp160r1(tmp,
>      > + product + num_words_secp160r1); /* (Rq, q) = q * c */
>      > +
>      > + carry = mg_uecc_vli_add(result, product, tmp,
>      > + num_words_secp160r1); /* (C, r) = r + q */
>      > + mg_uecc_vli_clear(product, num_words_secp160r1);
>      > + omega_mult_secp160r1(product, tmp + num_words_secp160r1); /*
>     Rq*c */
>      > + carry += mg_uecc_vli_add(result, result, product,
>      > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
>      > +
>      > + while (carry > 0) {
>      > + --carry;
>      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>     num_words_secp160r1);
>      > + }
>      > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
>     num_words_secp160r1) >
>      > + 0) {
>      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>     num_words_secp160r1);
>      > + }
>      > +}
>      > #endif
>      > - FD_ZERO(&rset);
>      > - FD_ZERO(&wset);
>      > - FD_ZERO(&eset);
>      > +
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +static void omega_mult_secp160r1(uint8_t *result, const uint8_t
>     *right) {
>      > + uint8_t carry;
>      > + uint8_t i;
>      > +
>      > + /* Multiply by (2^31 + 1). */
>      > + mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /*
>     2^32 */
>      > + mg_uecc_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */
>      > + result[3] = right[0] << 7; /* get last bit from shift */
>      > +
>      > + carry = mg_uecc_vli_add(result, result, right,
>      > + num_words_secp160r1); /* 2^31 + 1 */
>      > + for (i = num_words_secp160r1; carry; ++i) {
>      > + uint16_t sum = (uint16_t) result[i] + carry;
>      > + result[i] = (uint8_t) sum;
>      > + carry = sum >> 8;
>      > }
>      > +}
>      > +#elif MG_UECC_WORD_SIZE == 4
>      > +static void omega_mult_secp160r1(uint32_t *result, const
>     uint32_t *right) {
>      > + uint32_t carry;
>      > + unsigned i;
>      >
>      > - for (c = mgr->conns; c != NULL; c = c->next) {
>      > - if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
>      > - mg_error(c, "socket error");
>      > - } else {
>      > - c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>     &rset);
>      > - c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>     &wset);
>      > - if (mg_tls_pending(c) > 0) c->is_readable = 1;
>      > - }
>      > + /* Multiply by (2^31 + 1). */
>      > + mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /*
>     2^32 */
>      > + mg_uecc_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */
>      > + result[0] = right[0] << 31; /* get last bit from shift */
>      > +
>      > + carry = mg_uecc_vli_add(result, result, right,
>      > + num_words_secp160r1); /* 2^31 + 1 */
>      > + for (i = num_words_secp160r1; carry; ++i) {
>      > + uint64_t sum = (uint64_t) result[i] + carry;
>      > + result[i] = (uint32_t) sum;
>      > + carry = sum >> 32;
>      > }
>      > +}
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
>     !asm_mmod_fast_secp160r1) */
>      > +
>      > +#endif /* MG_UECC_SUPPORTS_secp160r1 */
>      > +
>      > +#if MG_UECC_SUPPORTS_secp192r1
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product);
>      > #endif
>      > +
>      > +static const struct MG_UECC_Curve_t curve_secp192r1 = {
>      > + num_words_secp192r1,
>      > + num_bytes_secp192r1,
>      > + 192, /* num_n_bits */
>      > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
>      > + BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
>      > + BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
>      > + BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
>      > +
>      > + BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
>      > + BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
>      > + BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
>      > + {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
>      > + BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
>      > + BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
>      > + &double_jacobian_default,
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + &mod_sqrt_default,
>      > +#endif
>      > + &x_side_default,
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + &vli_mmod_fast_secp192r1
>      > +#endif
>      > +};
>      > +
>      > +MG_UECC_Curve mg_uecc_secp192r1(void) {
>      > + return &curve_secp192r1;
>      > }
>      >
>      > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>      > - struct mg_connection *c, *tmp;
>      > - uint64_t now;
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +/* Computes result = product % curve_p.
>      > + See algorithm 5 and 6 from
>     http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
>     <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf>
>      > + */
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t
>     *product) {
>      > + uint8_t tmp[num_words_secp192r1];
>      > + uint8_t carry;
>      > +
>      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>      > +
>      > + mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
>      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
>      > +
>      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] =
>     tmp[7] = 0;
>      > + tmp[8] = product[24];
>      > + tmp[9] = product[25];
>      > + tmp[10] = product[26];
>      > + tmp[11] = product[27];
>      > + tmp[12] = product[28];
>      > + tmp[13] = product[29];
>      > + tmp[14] = product[30];
>      > + tmp[15] = product[31];
>      > + tmp[16] = product[32];
>      > + tmp[17] = product[33];
>      > + tmp[18] = product[34];
>      > + tmp[19] = product[35];
>      > + tmp[20] = product[36];
>      > + tmp[21] = product[37];
>      > + tmp[22] = product[38];
>      > + tmp[23] = product[39];
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + tmp[0] = tmp[8] = product[40];
>      > + tmp[1] = tmp[9] = product[41];
>      > + tmp[2] = tmp[10] = product[42];
>      > + tmp[3] = tmp[11] = product[43];
>      > + tmp[4] = tmp[12] = product[44];
>      > + tmp[5] = tmp[13] = product[45];
>      > + tmp[6] = tmp[14] = product[46];
>      > + tmp[7] = tmp[15] = product[47];
>      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] =
>     tmp[22] =
>      > + tmp[23] = 0;
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>      > + num_words_secp192r1) != 1) {
>      > + carry -=
>      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>     num_words_secp192r1);
>      > + }
>      > +}
>      > +#elif MG_UECC_WORD_SIZE == 4
>      > +static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t
>     *product) {
>      > + uint32_t tmp[num_words_secp192r1];
>      > + int carry;
>      >
>      > - mg_iotest(mgr, ms);
>      > - now = mg_millis();
>      > - mg_timer_poll(&mgr->timers, now);
>      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>      >
>      > - for (c = mgr->conns; c != NULL; c = tmp) {
>      > - bool is_resp = c->is_resp;
>      > - tmp = c->next;
>      > - mg_call(c, MG_EV_POLL, &now);
>      > - if (is_resp && !c->is_resp) {
>      > - long n = 0;
>      > - mg_call(c, MG_EV_READ, &n);
>      > - }
>      > - MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r'
>     : '-',
>      > - c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
>      > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>      > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>      > - if (c->is_resolving || c->is_closing) {
>      > - // Do nothing
>      > - } else if (c->is_listening && c->is_udp == 0) {
>      > - if (c->is_readable) accept_conn(mgr, c);
>      > - } else if (c->is_connecting) {
>      > - if (c->is_readable || c->is_writable) connect_conn(c);
>      > - } else if (c->is_tls_hs) {
>      > - if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
>      > - } else {
>      > - if (c->is_readable) read_conn(c);
>      > - if (c->is_writable) write_conn(c);
>      > - }
>      > + mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
>      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
>      >
>      > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>      > - if (c->is_closing) close_conn(c);
>      > + tmp[0] = tmp[1] = 0;
>      > + tmp[2] = product[6];
>      > + tmp[3] = product[7];
>      > + tmp[4] = product[8];
>      > + tmp[5] = product[9];
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + tmp[0] = tmp[2] = product[10];
>      > + tmp[1] = tmp[3] = product[11];
>      > + tmp[4] = tmp[5] = 0;
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>      > + num_words_secp192r1) != 1) {
>      > + carry -=
>      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>     num_words_secp192r1);
>      > + }
>      > +}
>      > +#else
>      > +static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t
>     *product) {
>      > + uint64_t tmp[num_words_secp192r1];
>      > + int carry;
>      > +
>      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>      > +
>      > + mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
>      > + carry = (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + tmp[0] = 0;
>      > + tmp[1] = product[3];
>      > + tmp[2] = product[4];
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + tmp[0] = tmp[1] = product[5];
>      > + tmp[2] = 0;
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp192r1);
>      > +
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>      > + num_words_secp192r1) != 1) {
>      > + carry -=
>      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>     num_words_secp192r1);
>      > }
>      > }
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
>      > +
>      > +#endif /* MG_UECC_SUPPORTS_secp192r1 */
>      > +
>      > +#if MG_UECC_SUPPORTS_secp224r1
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
>     curve);
>      > +#endif
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product);
>      > #endif
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/ssi.c"
>      > +static const struct MG_UECC_Curve_t curve_secp224r1 = {
>      > + num_words_secp224r1,
>      > + num_bytes_secp224r1,
>      > + 224, /* num_n_bits */
>      > + {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
>      > + BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
>      > + BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
>      > + BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
>      > + BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
>      > +
>      > + BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
>      > + BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
>      > + BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
>      > + BYTES_TO_WORDS_4(88, 63, 37, BD)},
>      > + {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
>      > + BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
>      > + BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
>      > + BYTES_TO_WORDS_4(85, 0A, 05, B4)},
>      > + &double_jacobian_default,
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + &mod_sqrt_secp224r1,
>      > #endif
>      > + &x_side_default,
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + &vli_mmod_fast_secp224r1
>      > +#endif
>      > +};
>      > +
>      > +MG_UECC_Curve mg_uecc_secp224r1(void) {
>      > + return &curve_secp224r1;
>      > +}
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +/* Routine 3.2.4 RS; from
>     http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1,
>     mg_uecc_word_t *e1,
>      > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
>      > + const mg_uecc_word_t *e0,
>      > + const mg_uecc_word_t *f0) {
>      > + mg_uecc_word_t t[num_words_secp224r1];
>      > +
>      > + mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <--
>     d0 ^ 2 */
>      > + mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1
>     <-- d0 * e0 */
>      > + mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
>      > + num_words_secp224r1); /* d1 <-- t + f0 */
>      > + mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
>      > + num_words_secp224r1); /* e1 <-- e1 + e1 */
>      > + mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1
>     <-- t * f0 */
>      > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
>      > + num_words_secp224r1); /* f1 <-- f1 + f1 */
>      > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
>      > + num_words_secp224r1); /* f1 <-- f1 + f1 */
>      > +}
>      > +
>      > +/* Routine 3.2.5 RSS; from
>     http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1,
>     mg_uecc_word_t *e1,
>      > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
>      > + const mg_uecc_word_t *e0,
>      > + const mg_uecc_word_t *f0,
>      > + const bitcount_t j) {
>      > + bitcount_t i;
>      > +
>      > + mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
>      > + mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
>      > + mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
>      > + for (i = 1; i <= j; i++) {
>      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS
>     (d1,e1,f1,d1,e1,f1) */
>      > + }
>      > +}
>      > +
>      > +/* Routine 3.2.6 RM; from
>     http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2,
>     mg_uecc_word_t *e2,
>      > + mg_uecc_word_t *f2, const mg_uecc_word_t *c,
>      > + const mg_uecc_word_t *d0,
>      > + const mg_uecc_word_t *e0,
>      > + const mg_uecc_word_t *d1,
>      > + const mg_uecc_word_t *e1) {
>      > + mg_uecc_word_t t1[num_words_secp224r1];
>      > + mg_uecc_word_t t2[num_words_secp224r1];
>      > +
>      > + mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1
>     <-- e0 * e1 */
>      > + mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1
>     <-- t1 * c */
>      > + /* t1 <-- p - t1 */
>      > + mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2
>     <-- d0 * d1 */
>      > + mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
>      > + num_words_secp224r1); /* t2 <-- t2 + t1 */
>      > + mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1
>     <-- d0 * e1 */
>      > + mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2
>     <-- d1 * e0 */
>      > + mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
>      > + num_words_secp224r1); /* e2 <-- e2 + t1 */
>      > + mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <--
>     e2^2 */
>      > + mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2
>     <-- f2 * c */
>      > + /* f2 <-- p - f2 */
>      > + mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
>      > +}
>      > +
>      > +/* Routine 3.2.7 RP; from
>     http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1,
>     mg_uecc_word_t *e1,
>      > + mg_uecc_word_t *f1, const mg_uecc_word_t *c,
>      > + const mg_uecc_word_t *r) {
>      > + wordcount_t i;
>      > + wordcount_t pow2i = 1;
>      > + mg_uecc_word_t d0[num_words_secp224r1];
>      > + mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
>      > + mg_uecc_word_t f0[num_words_secp224r1];
>      > +
>      > + mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
>      > + /* f0 <-- p - c */
>      > + mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + for (i = 0; i <= 6; i++) {
>      > + mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
>      > + pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
>      > + mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
>      > + e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
>      > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
>      > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
>      > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
>      > + pow2i *= 2;
>      > + }
>      > +}
>      >
>      > +/* Compute a = sqrt(a) (mod curve_p). */
>      > +/* Routine 3.2.8 mp_mod_sqrt_224; from
>      > + * http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
>     curve) {
>      > + (void) curve;
>      > + bitcount_t i;
>      > + mg_uecc_word_t e1[num_words_secp224r1];
>      > + mg_uecc_word_t f1[num_words_secp224r1];
>      > + mg_uecc_word_t d0[num_words_secp224r1];
>      > + mg_uecc_word_t e0[num_words_secp224r1];
>      > + mg_uecc_word_t f0[num_words_secp224r1];
>      > + mg_uecc_word_t d1[num_words_secp224r1];
>      > +
>      > + /* s = a; using constant instead of random value */
>      > + mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c,
>     s) */
>      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
>      > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
>      > + for (i = 1; i <= 95; i++) {
>      > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
>      > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
>      > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
>      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
>      > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
>      > + if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 ==
>     0 */
>      > + break;
>      > + }
>      > + }
>      > + mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
>      > + num_words_secp224r1); /* f1 <-- 1 / e0 */
>      > + mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <--
>     d0 / e0 */
>      > +}
>      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +/* Computes result = product % curve_p
>      > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t
>     *product) {
>      > + uint8_t tmp[num_words_secp224r1];
>      > + int8_t carry;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>      > +
>      > + /* s1 */
>      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
>      > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
>      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>      > + tmp[12] = product[28];
>      > + tmp[13] = product[29];
>      > + tmp[14] = product[30];
>      > + tmp[15] = product[31];
>      > + tmp[16] = product[32];
>      > + tmp[17] = product[33];
>      > + tmp[18] = product[34];
>      > + tmp[19] = product[35];
>      > + tmp[20] = product[36];
>      > + tmp[21] = product[37];
>      > + tmp[22] = product[38];
>      > + tmp[23] = product[39];
>      > + tmp[24] = product[40];
>      > + tmp[25] = product[41];
>      > + tmp[26] = product[42];
>      > + tmp[27] = product[43];
>      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>      > +
>      > + /* s2 */
>      > + tmp[12] = product[44];
>      > + tmp[13] = product[45];
>      > + tmp[14] = product[46];
>      > + tmp[15] = product[47];
>      > + tmp[16] = product[48];
>      > + tmp[17] = product[49];
>      > + tmp[18] = product[50];
>      > + tmp[19] = product[51];
>      > + tmp[20] = product[52];
>      > + tmp[21] = product[53];
>      > + tmp[22] = product[54];
>      > + tmp[23] = product[55];
>      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = product[28];
>      > + tmp[1] = product[29];
>      > + tmp[2] = product[30];
>      > + tmp[3] = product[31];
>      > + tmp[4] = product[32];
>      > + tmp[5] = product[33];
>      > + tmp[6] = product[34];
>      > + tmp[7] = product[35];
>      > + tmp[8] = product[36];
>      > + tmp[9] = product[37];
>      > + tmp[10] = product[38];
>      > + tmp[11] = product[39];
>      > + tmp[12] = product[40];
>      > + tmp[13] = product[41];
>      > + tmp[14] = product[42];
>      > + tmp[15] = product[43];
>      > + tmp[16] = product[44];
>      > + tmp[17] = product[45];
>      > + tmp[18] = product[46];
>      > + tmp[19] = product[47];
>      > + tmp[20] = product[48];
>      > + tmp[21] = product[49];
>      > + tmp[22] = product[50];
>      > + tmp[23] = product[51];
>      > + tmp[24] = product[52];
>      > + tmp[25] = product[53];
>      > + tmp[26] = product[54];
>      > + tmp[27] = product[55];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = product[44];
>      > + tmp[1] = product[45];
>      > + tmp[2] = product[46];
>      > + tmp[3] = product[47];
>      > + tmp[4] = product[48];
>      > + tmp[5] = product[49];
>      > + tmp[6] = product[50];
>      > + tmp[7] = product[51];
>      > + tmp[8] = product[52];
>      > + tmp[9] = product[53];
>      > + tmp[10] = product[54];
>      > + tmp[11] = product[55];
>      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>      > + num_words_secp224r1) != 1) {
>      > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + }
>      > + }
>      > +}
>      > +#elif MG_UECC_WORD_SIZE == 4
>      > +static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t
>     *product) {
>      > + uint32_t tmp[num_words_secp224r1];
>      > + int carry;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>      > +
>      > + /* s1 */
>      > + tmp[0] = tmp[1] = tmp[2] = 0;
>      > + tmp[3] = product[7];
>      > + tmp[4] = product[8];
>      > + tmp[5] = product[9];
>      > + tmp[6] = product[10];
>      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>      > +
>      > + /* s2 */
>      > + tmp[3] = product[11];
>      > + tmp[4] = product[12];
>      > + tmp[5] = product[13];
>      > + tmp[6] = 0;
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = product[7];
>      > + tmp[1] = product[8];
>      > + tmp[2] = product[9];
>      > + tmp[3] = product[10];
>      > + tmp[4] = product[11];
>      > + tmp[5] = product[12];
>      > + tmp[6] = product[13];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = product[11];
>      > + tmp[1] = product[12];
>      > + tmp[2] = product[13];
>      > + tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>      > + num_words_secp224r1) != 1) {
>      > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + }
>      > + }
>      > +}
>      > +#else
>      > +static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t
>     *product) {
>      > + uint64_t tmp[num_words_secp224r1];
>      > + int carry = 0;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>      > + result[num_words_secp224r1 - 1] &= 0xffffffff;
>      > +
>      > + /* s1 */
>      > + tmp[0] = 0;
>      > + tmp[1] = product[3] & 0xffffffff00000000ull;
>      > + tmp[2] = product[4];
>      > + tmp[3] = product[5] & 0xffffffff;
>      > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>      > +
>      > + /* s2 */
>      > + tmp[1] = product[5] & 0xffffffff00000000ull;
>      > + tmp[2] = product[6];
>      > + tmp[3] = 0;
>      > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = (product[3] >> 32) | (product[4] << 32);
>      > + tmp[1] = (product[4] >> 32) | (product[5] << 32);
>      > + tmp[2] = (product[5] >> 32) | (product[6] << 32);
>      > + tmp[3] = product[6] >> 32;
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
>      > + tmp[1] = product[6] >> 32;
>      > + tmp[2] = tmp[3] = 0;
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp224r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>      > + num_words_secp224r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>      > + num_words_secp224r1) != 1) {
>      > + mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>     num_words_secp224r1);
>      > + }
>      > + }
>      > +}
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
>      >
>      > +#endif /* MG_UECC_SUPPORTS_secp224r1 */
>      >
>      > +#if MG_UECC_SUPPORTS_secp256r1
>      >
>      > -#ifndef MG_MAX_SSI_DEPTH
>      > -#define MG_MAX_SSI_DEPTH 5
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product);
>      > #endif
>      >
>      > -#ifndef MG_SSI_BUFSIZ
>      > -#define MG_SSI_BUFSIZ 1024
>      > +static const struct MG_UECC_Curve_t curve_secp256r1 = {
>      > + num_words_secp256r1,
>      > + num_bytes_secp256r1,
>      > + 256, /* num_n_bits */
>      > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
>      > + BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
>      > + BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
>      > + BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
>      > + BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
>      > +
>      > + BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
>      > + BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
>      > + BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
>      > + BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
>      > + {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
>      > + BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
>      > + BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
>      > + BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
>      > + &double_jacobian_default,
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + &mod_sqrt_default,
>      > +#endif
>      > + &x_side_default,
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + &vli_mmod_fast_secp256r1
>      > #endif
>      > +};
>      >
>      > -#if MG_ENABLE_SSI
>      > -static char *mg_ssi(const char *path, const char *root, int
>     depth) {
>      > - struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
>      > - FILE *fp = fopen(path, "rb");
>      > - if (fp != NULL) {
>      > - char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
>      > - int ch, intag = 0;
>      > - size_t len = 0;
>      > - buf[0] = arg[0] = '\0';
>      > - while ((ch = fgetc(fp)) != EOF) {
>      > - if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
>     == '-') {
>      > - buf[len++] = (char) (ch & 0xff);
>      > - buf[len] = '\0';
>      > - if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
>      > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
>      > - *p = (char *) path + strlen(path), *data;
>      > - while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
>      > - mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
>     arg);
>      > - if (depth < MG_MAX_SSI_DEPTH &&
>      > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>      > - mg_iobuf_add(&b, b.len, data, strlen(data));
>      > - free(data);
>      > - } else {
>      > - MG_ERROR(("%s: file=%s error or too deep", path, arg));
>      > - }
>      > - } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
>      > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
>      > - mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
>      > - if (depth < MG_MAX_SSI_DEPTH &&
>      > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>      > - mg_iobuf_add(&b, b.len, data, strlen(data));
>      > - free(data);
>      > - } else {
>      > - MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
>      > - }
>      > - } else {
>      > - // Unknown SSI tag
>      > - MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
>      > - mg_iobuf_add(&b, b.len, buf, len);
>      > - }
>      > - intag = 0;
>      > - len = 0;
>      > - } else if (ch == '<') {
>      > - intag = 1;
>      > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>      > - len = 0;
>      > - buf[len++] = (char) (ch & 0xff);
>      > - } else if (intag) {
>      > - if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
>      > - intag = 0;
>      > - } else if (len >= sizeof(buf) - 2) {
>      > - MG_ERROR(("%s: SSI tag is too large", path));
>      > - len = 0;
>      > - }
>      > - buf[len++] = (char) (ch & 0xff);
>      > - } else {
>      > - buf[len++] = (char) (ch & 0xff);
>      > - if (len >= sizeof(buf)) {
>      > - mg_iobuf_add(&b, b.len, buf, len);
>      > - len = 0;
>      > - }
>      > - }
>      > +MG_UECC_Curve mg_uecc_secp256r1(void) {
>      > + return &curve_secp256r1;
>      > +}
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
>      > +/* Computes result = product % curve_p
>      > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
>     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t
>     *product) {
>      > + uint8_t tmp[num_words_secp256r1];
>      > + int8_t carry;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>      > +
>      > + /* s1 */
>      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
>      > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
>      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>      > + tmp[12] = product[44];
>      > + tmp[13] = product[45];
>      > + tmp[14] = product[46];
>      > + tmp[15] = product[47];
>      > + tmp[16] = product[48];
>      > + tmp[17] = product[49];
>      > + tmp[18] = product[50];
>      > + tmp[19] = product[51];
>      > + tmp[20] = product[52];
>      > + tmp[21] = product[53];
>      > + tmp[22] = product[54];
>      > + tmp[23] = product[55];
>      > + tmp[24] = product[56];
>      > + tmp[25] = product[57];
>      > + tmp[26] = product[58];
>      > + tmp[27] = product[59];
>      > + tmp[28] = product[60];
>      > + tmp[29] = product[61];
>      > + tmp[30] = product[62];
>      > + tmp[31] = product[63];
>      > + carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s2 */
>      > + tmp[12] = product[48];
>      > + tmp[13] = product[49];
>      > + tmp[14] = product[50];
>      > + tmp[15] = product[51];
>      > + tmp[16] = product[52];
>      > + tmp[17] = product[53];
>      > + tmp[18] = product[54];
>      > + tmp[19] = product[55];
>      > + tmp[20] = product[56];
>      > + tmp[21] = product[57];
>      > + tmp[22] = product[58];
>      > + tmp[23] = product[59];
>      > + tmp[24] = product[60];
>      > + tmp[25] = product[61];
>      > + tmp[26] = product[62];
>      > + tmp[27] = product[63];
>      > + tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
>      > + carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s3 */
>      > + tmp[0] = product[32];
>      > + tmp[1] = product[33];
>      > + tmp[2] = product[34];
>      > + tmp[3] = product[35];
>      > + tmp[4] = product[36];
>      > + tmp[5] = product[37];
>      > + tmp[6] = product[38];
>      > + tmp[7] = product[39];
>      > + tmp[8] = product[40];
>      > + tmp[9] = product[41];
>      > + tmp[10] = product[42];
>      > + tmp[11] = product[43];
>      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>      > + tmp[24] = product[56];
>      > + tmp[25] = product[57];
>      > + tmp[26] = product[58];
>      > + tmp[27] = product[59];
>      > + tmp[28] = product[60];
>      > + tmp[29] = product[61];
>      > + tmp[30] = product[62];
>      > + tmp[31] = product[63];
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s4 */
>      > + tmp[0] = product[36];
>      > + tmp[1] = product[37];
>      > + tmp[2] = product[38];
>      > + tmp[3] = product[39];
>      > + tmp[4] = product[40];
>      > + tmp[5] = product[41];
>      > + tmp[6] = product[42];
>      > + tmp[7] = product[43];
>      > + tmp[8] = product[44];
>      > + tmp[9] = product[45];
>      > + tmp[10] = product[46];
>      > + tmp[11] = product[47];
>      > + tmp[12] = product[52];
>      > + tmp[13] = product[53];
>      > + tmp[14] = product[54];
>      > + tmp[15] = product[55];
>      > + tmp[16] = product[56];
>      > + tmp[17] = product[57];
>      > + tmp[18] = product[58];
>      > + tmp[19] = product[59];
>      > + tmp[20] = product[60];
>      > + tmp[21] = product[61];
>      > + tmp[22] = product[62];
>      > + tmp[23] = product[63];
>      > + tmp[24] = product[52];
>      > + tmp[25] = product[53];
>      > + tmp[26] = product[54];
>      > + tmp[27] = product[55];
>      > + tmp[28] = product[32];
>      > + tmp[29] = product[33];
>      > + tmp[30] = product[34];
>      > + tmp[31] = product[35];
>      > + carry += mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = product[44];
>      > + tmp[1] = product[45];
>      > + tmp[2] = product[46];
>      > + tmp[3] = product[47];
>      > + tmp[4] = product[48];
>      > + tmp[5] = product[49];
>      > + tmp[6] = product[50];
>      > + tmp[7] = product[51];
>      > + tmp[8] = product[52];
>      > + tmp[9] = product[53];
>      > + tmp[10] = product[54];
>      > + tmp[11] = product[55];
>      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>      > + tmp[24] = product[32];
>      > + tmp[25] = product[33];
>      > + tmp[26] = product[34];
>      > + tmp[27] = product[35];
>      > + tmp[28] = product[40];
>      > + tmp[29] = product[41];
>      > + tmp[30] = product[42];
>      > + tmp[31] = product[43];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = product[48];
>      > + tmp[1] = product[49];
>      > + tmp[2] = product[50];
>      > + tmp[3] = product[51];
>      > + tmp[4] = product[52];
>      > + tmp[5] = product[53];
>      > + tmp[6] = product[54];
>      > + tmp[7] = product[55];
>      > + tmp[8] = product[56];
>      > + tmp[9] = product[57];
>      > + tmp[10] = product[58];
>      > + tmp[11] = product[59];
>      > + tmp[12] = product[60];
>      > + tmp[13] = product[61];
>      > + tmp[14] = product[62];
>      > + tmp[15] = product[63];
>      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>      > + tmp[24] = product[36];
>      > + tmp[25] = product[37];
>      > + tmp[26] = product[38];
>      > + tmp[27] = product[39];
>      > + tmp[28] = product[44];
>      > + tmp[29] = product[45];
>      > + tmp[30] = product[46];
>      > + tmp[31] = product[47];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d3 */
>      > + tmp[0] = product[52];
>      > + tmp[1] = product[53];
>      > + tmp[2] = product[54];
>      > + tmp[3] = product[55];
>      > + tmp[4] = product[56];
>      > + tmp[5] = product[57];
>      > + tmp[6] = product[58];
>      > + tmp[7] = product[59];
>      > + tmp[8] = product[60];
>      > + tmp[9] = product[61];
>      > + tmp[10] = product[62];
>      > + tmp[11] = product[63];
>      > + tmp[12] = product[32];
>      > + tmp[13] = product[33];
>      > + tmp[14] = product[34];
>      > + tmp[15] = product[35];
>      > + tmp[16] = product[36];
>      > + tmp[17] = product[37];
>      > + tmp[18] = product[38];
>      > + tmp[19] = product[39];
>      > + tmp[20] = product[40];
>      > + tmp[21] = product[41];
>      > + tmp[22] = product[42];
>      > + tmp[23] = product[43];
>      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>      > + tmp[28] = product[48];
>      > + tmp[29] = product[49];
>      > + tmp[30] = product[50];
>      > + tmp[31] = product[51];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d4 */
>      > + tmp[0] = product[56];
>      > + tmp[1] = product[57];
>      > + tmp[2] = product[58];
>      > + tmp[3] = product[59];
>      > + tmp[4] = product[60];
>      > + tmp[5] = product[61];
>      > + tmp[6] = product[62];
>      > + tmp[7] = product[63];
>      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>      > + tmp[12] = product[36];
>      > + tmp[13] = product[37];
>      > + tmp[14] = product[38];
>      > + tmp[15] = product[39];
>      > + tmp[16] = product[40];
>      > + tmp[17] = product[41];
>      > + tmp[18] = product[42];
>      > + tmp[19] = product[43];
>      > + tmp[20] = product[44];
>      > + tmp[21] = product[45];
>      > + tmp[22] = product[46];
>      > + tmp[23] = product[47];
>      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>      > + tmp[28] = product[52];
>      > + tmp[29] = product[53];
>      > + tmp[30] = product[54];
>      > + tmp[31] = product[55];
>      > + carry -= mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>      > + num_words_secp256r1) != 1) {
>      > + carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > }
>      > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>      > - if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
>      > - fclose(fp);
>      > }
>      > - (void) depth;
>      > - (void) root;
>      > - return (char *) b.buf;
>      > }
>      > -
>      > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>      > - const char *fullpath) {
>      > - const char *headers = "Content-Type: text/html;
>     charset=utf-8\r\n";
>      > - char *data = mg_ssi(fullpath, root, 0);
>      > - mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
>      > - free(data);
>      > +#elif MG_UECC_WORD_SIZE == 4
>      > +static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t
>     *product) {
>      > + uint32_t tmp[num_words_secp256r1];
>      > + int carry;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>      > +
>      > + /* s1 */
>      > + tmp[0] = tmp[1] = tmp[2] = 0;
>      > + tmp[3] = product[11];
>      > + tmp[4] = product[12];
>      > + tmp[5] = product[13];
>      > + tmp[6] = product[14];
>      > + tmp[7] = product[15];
>      > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s2 */
>      > + tmp[3] = product[12];
>      > + tmp[4] = product[13];
>      > + tmp[5] = product[14];
>      > + tmp[6] = product[15];
>      > + tmp[7] = 0;
>      > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
>     num_words_secp256r1);
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s3 */
>      > + tmp[0] = product[8];
>      > + tmp[1] = product[9];
>      > + tmp[2] = product[10];
>      > + tmp[3] = tmp[4] = tmp[5] = 0;
>      > + tmp[6] = product[14];
>      > + tmp[7] = product[15];
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s4 */
>      > + tmp[0] = product[9];
>      > + tmp[1] = product[10];
>      > + tmp[2] = product[11];
>      > + tmp[3] = product[13];
>      > + tmp[4] = product[14];
>      > + tmp[5] = product[15];
>      > + tmp[6] = product[13];
>      > + tmp[7] = product[8];
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = product[11];
>      > + tmp[1] = product[12];
>      > + tmp[2] = product[13];
>      > + tmp[3] = tmp[4] = tmp[5] = 0;
>      > + tmp[6] = product[8];
>      > + tmp[7] = product[10];
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = product[12];
>      > + tmp[1] = product[13];
>      > + tmp[2] = product[14];
>      > + tmp[3] = product[15];
>      > + tmp[4] = tmp[5] = 0;
>      > + tmp[6] = product[9];
>      > + tmp[7] = product[11];
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d3 */
>      > + tmp[0] = product[13];
>      > + tmp[1] = product[14];
>      > + tmp[2] = product[15];
>      > + tmp[3] = product[8];
>      > + tmp[4] = product[9];
>      > + tmp[5] = product[10];
>      > + tmp[6] = 0;
>      > + tmp[7] = product[12];
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d4 */
>      > + tmp[0] = product[14];
>      > + tmp[1] = product[15];
>      > + tmp[2] = 0;
>      > + tmp[3] = product[9];
>      > + tmp[4] = product[10];
>      > + tmp[5] = product[11];
>      > + tmp[6] = 0;
>      > + tmp[7] = product[13];
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>      > + num_words_secp256r1) != 1) {
>      > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > + }
>      > + }
>      > }
>      > #else
>      > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>      > - const char *fullpath) {
>      > - mg_http_reply(c, 501, NULL, "SSI not enabled");
>      > - (void) root, (void) fullpath;
>      > +static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t
>     *product) {
>      > + uint64_t tmp[num_words_secp256r1];
>      > + int carry;
>      > +
>      > + /* t */
>      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>      > +
>      > + /* s1 */
>      > + tmp[0] = 0;
>      > + tmp[1] = product[5] & 0xffffffff00000000U;
>      > + tmp[2] = product[6];
>      > + tmp[3] = product[7];
>      > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s2 */
>      > + tmp[1] = product[6] << 32;
>      > + tmp[2] = (product[6] >> 32) | (product[7] << 32);
>      > + tmp[3] = product[7] >> 32;
>      > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
>     num_words_secp256r1);
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s3 */
>      > + tmp[0] = product[4];
>      > + tmp[1] = product[5] & 0xffffffff;
>      > + tmp[2] = 0;
>      > + tmp[3] = product[7];
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* s4 */
>      > + tmp[0] = (product[4] >> 32) | (product[5] << 32);
>      > + tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
>      > + tmp[2] = product[7];
>      > + tmp[3] = (product[6] >> 32) | (product[4] << 32);
>      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d1 */
>      > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
>      > + tmp[1] = (product[6] >> 32);
>      > + tmp[2] = 0;
>      > + tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d2 */
>      > + tmp[0] = product[6];
>      > + tmp[1] = product[7];
>      > + tmp[2] = 0;
>      > + tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d3 */
>      > + tmp[0] = (product[6] >> 32) | (product[7] << 32);
>      > + tmp[1] = (product[7] >> 32) | (product[4] << 32);
>      > + tmp[2] = (product[4] >> 32) | (product[5] << 32);
>      > + tmp[3] = (product[6] << 32);
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + /* d4 */
>      > + tmp[0] = product[7];
>      > + tmp[1] = product[4] & 0xffffffff00000000U;
>      > + tmp[2] = product[5];
>      > + tmp[3] = product[6] & 0xffffffff00000000U;
>      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>     num_words_secp256r1);
>      > +
>      > + if (carry < 0) {
>      > + do {
>      > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > + } while (carry < 0);
>      > + } else {
>      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>      > + num_words_secp256r1) != 1) {
>      > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>      > + num_words_secp256r1);
>      > + }
>      > + }
>      > }
>      > -#endif
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
>     !asm_mmod_fast_secp256r1) */
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/str.c"
>      > +#endif /* MG_UECC_SUPPORTS_secp256r1 */
>      > +
>      > +#if MG_UECC_SUPPORTS_secp256k1
>      > +
>      > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
>     mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
>      > +static void x_side_secp256k1(mg_uecc_word_t *result, const
>     mg_uecc_word_t *x,
>      > + MG_UECC_Curve curve);
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product);
>      > #endif
>      >
>      > +static const struct MG_UECC_Curve_t curve_secp256k1 = {
>      > + num_words_secp256k1,
>      > + num_bytes_secp256k1,
>      > + 256, /* num_n_bits */
>      > + {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
>      > + BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
>      > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
>      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>      > + {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
>      > + BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
>      > + BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
>      > + BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
>      > +
>      > + BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
>      > + BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
>      > + BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
>      > + BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
>      > + {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
>      > + &double_jacobian_secp256k1,
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > + &mod_sqrt_default,
>      > +#endif
>      > + &x_side_secp256k1,
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + &vli_mmod_fast_secp256k1
>      > +#endif
>      > +};
>      >
>      > -struct mg_str mg_str_s(const char *s) {
>      > - struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
>      > - return str;
>      > +MG_UECC_Curve mg_uecc_secp256k1(void) {
>      > + return &curve_secp256k1;
>      > }
>      >
>      > -struct mg_str mg_str_n(const char *s, size_t n) {
>      > - struct mg_str str = {s, n};
>      > - return str;
>      > -}
>      > +/* Double in place */
>      > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
>     mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
>      > + /* t1 = X, t2 = Y, t3 = Z */
>      > + mg_uecc_word_t t4[num_words_secp256k1];
>      > + mg_uecc_word_t t5[num_words_secp256k1];
>      >
>      > -int mg_lower(const char *s) {
>      > - int c = *s;
>      > - if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
>      > - return c;
>      > -}
>      > + if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
>      > + return;
>      > + }
>      >
>      > -int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
>      > - int diff = 0;
>      > - if (len > 0) do {
>      > - diff = mg_lower(s1++) - mg_lower(s2++);
>      > - } while (diff == 0 && s1[-1] != '\0' && --len > 0);
>      > - return diff;
>      > + mg_uecc_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */
>      > + mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 =
>     A */
>      > + mg_uecc_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */
>      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */
>      > + mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
>      > +
>      > + mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
>      > + num_words_secp256k1); /* t2 = 2*x1^2 */
>      > + mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
>      > + num_words_secp256k1); /* t2 = 3*x1^2 */
>      > + if (mg_uecc_vli_testBit(Y1, 0)) {
>      > + mg_uecc_word_t carry =
>      > + mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
>      > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
>      > + Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
>      > + } else {
>      > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
>      > + }
>      > + /* t2 = 3/2*(x1^2) = B */
>      > +
>      > + mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
>      > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
>      > + num_words_secp256k1); /* t1 = B^2 - A */
>      > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
>      > + num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
>      > +
>      > + mg_uecc_vli_modSub(t4, t4, X1, curve->p,
>      > + num_words_secp256k1); /* t4 = A - x3 */
>      > + mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A -
>     x3) */
>      > + mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
>      > + num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
>      > +}
>      > +
>      > +/* Computes result = x^3 + b. result must not overlap x. */
>      > +static void x_side_secp256k1(mg_uecc_word_t *result, const
>     mg_uecc_word_t *x,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
>      > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
>      > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
>      > + num_words_secp256k1); /* r = x^3 + b */
>      > +}
>      > +
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
>      > +static void omega_mult_secp256k1(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *right);
>      > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
>      > + mg_uecc_word_t *product) {
>      > + mg_uecc_word_t tmp[2 * num_words_secp256k1];
>      > + mg_uecc_word_t carry;
>      > +
>      > + mg_uecc_vli_clear(tmp, num_words_secp256k1);
>      > + mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
>      > +
>      > + omega_mult_secp256k1(tmp,
>      > + product + num_words_secp256k1); /* (Rq, q) = q * c */
>      > +
>      > + carry = mg_uecc_vli_add(result, product, tmp,
>      > + num_words_secp256k1); /* (C, r) = r + q */
>      > + mg_uecc_vli_clear(product, num_words_secp256k1);
>      > + omega_mult_secp256k1(product, tmp + num_words_secp256k1); /*
>     Rq*c */
>      > + carry += mg_uecc_vli_add(result, result, product,
>      > + num_words_secp256k1); /* (C1, r) = r + Rq*c */
>      > +
>      > + while (carry > 0) {
>      > + --carry;
>      > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
>     num_words_secp256k1);
>      > + }
>      > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p,
>     num_words_secp256k1) >
>      > + 0) {
>      > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
>     num_words_secp256k1);
>      > + }
>      > }
>      >
>      > -int mg_casecmp(const char *s1, const char *s2) {
>      > - return mg_ncasecmp(s1, s2, (size_t) ~0);
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +static void omega_mult_secp256k1(uint8_t *result, const uint8_t
>     *right) {
>      > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>      > + mg_uecc_word_t r0 = 0;
>      > + mg_uecc_word_t r1 = 0;
>      > + mg_uecc_word_t r2 = 0;
>      > + wordcount_t k;
>      > +
>      > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>      > + muladd(0xD1, right[0], &r0, &r1, &r2);
>      > + result[0] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + /* r2 is still 0 */
>      > +
>      > + for (k = 1; k < num_words_secp256k1; ++k) {
>      > + muladd(0x03, right[k - 1], &r0, &r1, &r2);
>      > + muladd(0xD1, right[k], &r0, &r1, &r2);
>      > + result[k] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + r2 = 0;
>      > + }
>      > + muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
>      > + result[num_words_secp256k1] = r0;
>      > + result[num_words_secp256k1 + 1] = r1;
>      > + /* add the 2^32 multiple */
>      > + result[4 + num_words_secp256k1] =
>      > + mg_uecc_vli_add(result + 4, result + 4, right,
>     num_words_secp256k1);
>      > +}
>      > +#elif MG_UECC_WORD_SIZE == 4
>      > +static void omega_mult_secp256k1(uint32_t *result, const
>     uint32_t *right) {
>      > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>      > + uint32_t carry = 0;
>      > + wordcount_t k;
>      > +
>      > + for (k = 0; k < num_words_secp256k1; ++k) {
>      > + uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
>      > + result[k] = (uint32_t) p;
>      > + carry = p >> 32;
>      > + }
>      > + result[num_words_secp256k1] = carry;
>      > + /* add the 2^32 multiple */
>      > + result[1 + num_words_secp256k1] =
>      > + mg_uecc_vli_add(result + 1, result + 1, right,
>     num_words_secp256k1);
>      > }
>      > -
>      > -int mg_vcmp(const struct mg_str *s1, const char *s2) {
>      > - size_t n2 = strlen(s2), n1 = s1->len;
>      > - int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
>      > - if (r == 0) return (int) (n1 - n2);
>      > - return r;
>      > +#else
>      > +static void omega_mult_secp256k1(uint64_t *result, const
>     uint64_t *right) {
>      > + mg_uecc_word_t r0 = 0;
>      > + mg_uecc_word_t r1 = 0;
>      > + mg_uecc_word_t r2 = 0;
>      > + wordcount_t k;
>      > +
>      > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>      > + for (k = 0; k < num_words_secp256k1; ++k) {
>      > + muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
>      > + result[k] = r0;
>      > + r0 = r1;
>      > + r1 = r2;
>      > + r2 = 0;
>      > + }
>      > + result[num_words_secp256k1] = r0;
>      > }
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && &&
>     !asm_mmod_fast_secp256k1) */
>      >
>      > -int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
>      > - size_t n2 = strlen(str2), n1 = str1->len;
>      > - int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
>      > - if (r == 0) return (int) (n1 - n2);
>      > - return r;
>      > +#endif /* MG_UECC_SUPPORTS_secp256k1 */
>      > +
>      > +#endif /* _UECC_CURVE_SPECIFIC_H_ */
>      > +
>      > +/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
>      > +#define EccPoint_isZero(point, curve) \
>      > + mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words *
>     2))
>      > +
>      > +/* Point multiplication algorithm using Montgomery's ladder with
>     co-Z
>      > +coordinates. From http://eprint.iacr.org/2011/338.pdf
>     <http://eprint.iacr.org/2011/338.pdf>
>      > +*/
>      > +
>      > +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
>      > +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>      > + const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
>      > + mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
>      > +
>      > + mg_uecc_vli_modSquare_fast(t1, Z, curve); /* z^2 */
>      > + mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
>      > + mg_uecc_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */
>      > + mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
>      > }
>      >
>      > -struct mg_str mg_strdup(const struct mg_str s) {
>      > - struct mg_str r = {NULL, 0};
>      > - if (s.len > 0 && s.ptr != NULL) {
>      > - char *sc = (char *) calloc(1, s.len + 1);
>      > - if (sc != NULL) {
>      > - memcpy(sc, s.ptr, s.len);
>      > - sc[s.len] = '\0';
>      > - r.ptr = sc;
>      > - r.len = s.len;
>      > - }
>      > +/* P = (x1, y1) => 2P, (x2, y2) => P' */
>      > +static void XYcZ_initial_double(mg_uecc_word_t *X1,
>     mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
>      > + const mg_uecc_word_t *const initial_Z,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>      > + wordcount_t num_words = curve->num_words;
>      > + if (initial_Z) {
>      > + mg_uecc_vli_set(z, initial_Z, num_words);
>      > + } else {
>      > + mg_uecc_vli_clear(z, num_words);
>      > + z[0] = 1;
>      > }
>      > - return r;
>      > -}
>      >
>      > -int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
>      > - size_t i = 0;
>      > - while (i < str1.len && i < str2.len) {
>      > - int c1 = str1.ptr[i];
>      > - int c2 = str2.ptr[i];
>      > - if (c1 < c2) return -1;
>      > - if (c1 > c2) return 1;
>      > - i++;
>      > + mg_uecc_vli_set(X2, X1, num_words);
>      > + mg_uecc_vli_set(Y2, Y1, num_words);
>      > +
>      > + apply_z(X1, Y1, z, curve);
>      > + curve->double_jacobian(X1, Y1, z, curve);
>      > + apply_z(X2, Y2, z, curve);
>      > +}
>      > +
>      > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
>      > + Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
>      > + or P => P', Q => P + Q
>      > +*/
>      > +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>     mg_uecc_word_t *X2,
>      > + mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
>      > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
>      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
>     - x1 */
>      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
>     = A */
>      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
>      > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
>      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
>     - y1 */
>      > + mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2
>     = D */
>      > +
>      > + mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D
>     - B */
>      > + mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D
>     - B - C = x3 */
>      > + mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C
>     - B */
>      > + mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */
>      > + mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B
>     - x3 */
>      > + mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 -
>     y1)*(B - x3) */
>      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
>      > +
>      > + mg_uecc_vli_set(X2, t5, num_words);
>      > +}
>      > +
>      > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
>      > + Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
>      > + or P => P - Q, Q => P + Q
>      > +*/
>      > +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>      > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
>      > + MG_UECC_Curve curve) {
>      > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
>      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
>      > + mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
>     - x1 */
>      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
>     = A */
>      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
>      > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
>      > + mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2
>     + y1 */
>      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
>     - y1 */
>      > +
>      > + mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C
>     - B */
>      > + mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C -
>     B) = E */
>      > + mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B
>     + C */
>      > + mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2
>     = D */
>      > + mg_uecc_vli_modSub(X2, X2, t6, curve->p,
>      > + num_words); /* t3 = D - (B + C) = x3 */
>      > +
>      > + mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B
>     - x3 */
>      > + mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 -
>     y1)*(B - x3) */
>      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
>      > + num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
>      > +
>      > + mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2
>     = F */
>      > + mg_uecc_vli_modSub(t7, t7, t6, curve->p,
>      > + num_words); /* t7 = F - (B + C) = x3' */
>      > + mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 =
>     x3' - B */
>      > + mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 =
>     (y2+y1)*(x3' - B) */
>      > + mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
>      > + num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
>      > +
>      > + mg_uecc_vli_set(X1, t7, num_words);
>      > +}
>      > +
>      > +/* result may overlap point. */
>      > +static void EccPoint_mult(mg_uecc_word_t *result, const
>     mg_uecc_word_t *point,
>      > + const mg_uecc_word_t *scalar,
>      > + const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
>      > + MG_UECC_Curve curve) {
>      > + /* R0 and R1 */
>      > + mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>      > + bitcount_t i;
>      > + mg_uecc_word_t nb;
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + mg_uecc_vli_set(Rx[1], point, num_words);
>      > + mg_uecc_vli_set(Ry[1], point + num_words, num_words);
>      > +
>      > + XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
>      > +
>      > + for (i = num_bits - 2; i > 0; --i) {
>      > + nb = !mg_uecc_vli_testBit(scalar, i);
>      > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
>      > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
>      > + }
>      > +
>      > + nb = !mg_uecc_vli_testBit(scalar, 0);
>      > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
>      > +
>      > + /* Find final 1/Z value. */
>      > + mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1
>     - X0 */
>      > + mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1
>     - X0) */
>      > + mg_uecc_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1
>     - X0) */
>      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb
>     * (X1 - X0)) */
>      > + /* yP / (xP * Yb * (X1 - X0)) */
>      > + mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
>      > + mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
>      > + curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
>      > + /* End 1/Z calculation */
>      > +
>      > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
>      > + apply_z(Rx[0], Ry[0], z, curve);
>      > +
>      > + mg_uecc_vli_set(result, Rx[0], num_words);
>      > + mg_uecc_vli_set(result + num_words, Ry[0], num_words);
>      > +}
>      > +
>      > +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
>      > + mg_uecc_word_t *k0, mg_uecc_word_t *k1,
>      > + MG_UECC_Curve curve) {
>      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>      > + bitcount_t num_n_bits = curve->num_n_bits;
>      > + mg_uecc_word_t carry =
>      > + mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
>      > + (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE *
>     8) &&
>      > + mg_uecc_vli_testBit(k0, num_n_bits));
>      > + mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
>      > + return carry;
>      > +}
>      > +
>      > +/* Generates a random integer in the range 0 < random < top.
>      > + Both random and top have num_words words. */
>      > +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t
>     *random,
>      > + const mg_uecc_word_t *top,
>      > + wordcount_t num_words) {
>      > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
>      > + mg_uecc_word_t tries;
>      > + bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
>      > +
>      > + if (!g_rng_function) {
>      > + return 0;
>      > + }
>      > +
>      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>      > + if (!g_rng_function((uint8_t *) random,
>      > + (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
>      > + return 0;
>      > + }
>      > + random[num_words - 1] &=
>      > + mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 -
>     num_bits));
>      > + if (!mg_uecc_vli_isZero(random, num_words) &&
>      > + mg_uecc_vli_cmp(top, random, num_words) == 1) {
>      > + return 1;
>      > + }
>      > }
>      > - if (i < str1.len) return 1;
>      > - if (i < str2.len) return -1;
>      > return 0;
>      > }
>      >
>      > -const char *mg_strstr(const struct mg_str haystack,
>      > - const struct mg_str needle) {
>      > - size_t i;
>      > - if (needle.len > haystack.len) return NULL;
>      > - for (i = 0; i <= haystack.len - needle.len; i++) {
>      > - if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
>      > - return haystack.ptr + i;
>      > +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t
>     *result,
>      > + mg_uecc_word_t *private_key,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
>      > + mg_uecc_word_t *initial_Z = 0;
>      > + mg_uecc_word_t carry;
>      > +
>      > + /* Regularize the bitcount for the private key so that
>     attackers cannot use a
>      > + side channel attack to learn the number of leading zeros. */
>      > + carry = regularize_k(private_key, tmp1, tmp2, curve);
>      > +
>      > + /* If an RNG function was specified, try to get a random
>     initial Z value to
>      > + improve protection against side-channel attacks. */
>      > + if (g_rng_function) {
>      > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
>     curve->num_words)) {
>      > + return 0;
>      > }
>      > + initial_Z = p2[carry];
>      > }
>      > - return NULL;
>      > + EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
>      > + (bitcount_t) (curve->num_n_bits + 1), curve);
>      > +
>      > + if (EccPoint_isZero(result, curve)) {
>      > + return 0;
>      > + }
>      > + return 1;
>      > }
>      >
>      > -static bool is_space(int c) {
>      > - return c == ' ' || c == '\r' || c == '\n' || c == '\t';
>      > +#if MG_UECC_WORD_SIZE == 1
>      > +
>      > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
>     int num_bytes,
>      > + const uint8_t *native) {
>      > + wordcount_t i;
>      > + for (i = 0; i < num_bytes; ++i) {
>      > + bytes[i] = native[(num_bytes - 1) - i];
>      > + }
>      > }
>      >
>      > -struct mg_str mg_strstrip(struct mg_str s) {
>      > - while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
>      > - while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
>      > - return s;
>      > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
>      > + const uint8_t *bytes,
>      > + int num_bytes) {
>      > + mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
>      > }
>      >
>      > -bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
>     *caps) {
>      > - size_t i = 0, j = 0, ni = 0, nj = 0;
>      > - if (caps) caps->ptr = NULL, caps->len = 0;
>      > - while (i < p.len || j < s.len) {
>      > - if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] ==
>     p.ptr[i])) {
>      > - if (caps == NULL) {
>      > - } else if (p.ptr[i] == '?') {
>      > - caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap
>      > - caps++, caps->ptr = NULL, caps->len = 0; // Init next cap
>      > - } else if (caps->ptr != NULL && caps->len == 0) {
>      > - caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize
>     current cap
>      > - caps++, caps->len = 0, caps->ptr = NULL; // Init next cap
>      > - }
>      > - i++, j++;
>      > - } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
>      > - if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];
>     // Init cap
>      > - ni = i++, nj = j + 1;
>      > - } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' ||
>     s.ptr[j] != '/')) {
>      > - i = ni, j = nj;
>      > - if (caps && caps->ptr == NULL && caps->len == 0) {
>      > - caps--, caps->len = 0; // Restart previous cap
>      > - }
>      > - } else {
>      > - return false;
>      > - }
>      > - }
>      > - if (caps && caps->ptr && caps->len == 0) {
>      > - caps->len = (size_t) (&s.ptr[j] - caps->ptr);
>      > +#else
>      > +
>      > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
>     int num_bytes,
>      > + const mg_uecc_word_t *native) {
>      > + int i;
>      > + for (i = 0; i < num_bytes; ++i) {
>      > + unsigned b = (unsigned) (num_bytes - 1 - i);
>      > + bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
>      > + (8 * (b % MG_UECC_WORD_SIZE)));
>      > }
>      > - return true;
>      > }
>      >
>      > -bool mg_globmatch(const char *s1, size_t n1, const char *s2,
>     size_t n2) {
>      > - return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
>      > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t
>     *native,
>      > + const uint8_t *bytes,
>      > + int num_bytes) {
>      > + int i;
>      > + mg_uecc_vli_clear(native,
>      > + (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
>      > + MG_UECC_WORD_SIZE));
>      > + for (i = 0; i < num_bytes; ++i) {
>      > + unsigned b = (unsigned) (num_bytes - 1 - i);
>      > + native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
>      > + << (8 * (b % MG_UECC_WORD_SIZE));
>      > + }
>      > }
>      >
>      > -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t
>     *koff,
>      > - size_t *klen, size_t *voff, size_t *vlen, char delim) {
>      > - size_t kvlen, kl;
>      > - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;)
>     kvlen++;
>      > - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
>      > - if (koff != NULL) *koff = ofs;
>      > - if (klen != NULL) *klen = kl;
>      > - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
>      > - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
>      > - ofs += kvlen + 1;
>      > - return ofs > n ? n : ofs;
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +
>      > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
>      > + MG_UECC_Curve curve) {
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
>      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>      > +#else
>      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>      > +#endif
>      > + mg_uecc_word_t tries;
>      > +
>      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>      > + if (!mg_uecc_generate_random_int(_private, curve->n,
>      > + BITS_TO_WORDS(curve->num_n_bits))) {
>      > + return 0;
>      > + }
>      > +
>      > + if (EccPoint_compute_public_key(_public, _private, curve)) {
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_nativeToBytes(private_key,
>     BITS_TO_BYTES(curve->num_n_bits),
>      > + _private);
>      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
>      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>     curve->num_bytes,
>      > + _public + curve->num_words);
>      > +#endif
>      > + return 1;
>      > + }
>      > + }
>      > + return 0;
>      > }
>      >
>      > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
>     *v, char sep) {
>      > - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
>      > - if (s->ptr == NULL || s->len == 0) return 0;
>      > - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
>      > - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
>      > - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
>      > - *s = mg_str_n(s->ptr + off, s->len - off);
>      > - return off > 0;
>      > +int mg_uecc_shared_secret(const uint8_t *public_key, const
>     uint8_t *private_key,
>      > + uint8_t *secret, MG_UECC_Curve curve) {
>      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>      > +
>      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t *p2[2] = {_private, tmp};
>      > + mg_uecc_word_t *initial_Z = 0;
>      > + mg_uecc_word_t carry;
>      > + wordcount_t num_words = curve->num_words;
>      > + wordcount_t num_bytes = curve->num_bytes;
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) _private, private_key, num_bytes);
>      > + bcopy((uint8_t *) _public, public_key, num_bytes * 2);
>      > +#else
>      > + mg_uecc_vli_bytesToNative(_private, private_key,
>      > + BITS_TO_BYTES(curve->num_n_bits));
>      > + mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
>      > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
>     num_bytes,
>      > + num_bytes);
>      > +#endif
>      > +
>      > + /* Regularize the bitcount for the private key so that
>     attackers cannot use a
>      > + side channel attack to learn the number of leading zeros. */
>      > + carry = regularize_k(_private, _private, tmp, curve);
>      > +
>      > + /* If an RNG function was specified, try to get a random
>     initial Z value to
>      > + improve protection against side-channel attacks. */
>      > + if (g_rng_function) {
>      > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
>     num_words)) {
>      > + return 0;
>      > + }
>      > + initial_Z = p2[carry];
>      > + }
>      > +
>      > + EccPoint_mult(_public, _public, p2[!carry], initial_Z,
>      > + (bitcount_t) (curve->num_n_bits + 1), curve);
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
>      > +#else
>      > + mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
>      > +#endif
>      > + return !EccPoint_isZero(_public, curve);
>      > }
>      >
>      > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
>     mg_str *v) {
>      > - return mg_split(s, k, v, ',');
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
>     *compressed,
>      > + MG_UECC_Curve curve) {
>      > + wordcount_t i;
>      > + for (i = 0; i < curve->num_bytes; ++i) {
>      > + compressed[i + 1] = public_key[i];
>      > + }
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
>      > +#else
>      > + compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
>      > +#endif
>      > }
>      >
>      > -char *mg_hex(const void *buf, size_t len, char *to) {
>      > - const unsigned char *p = (const unsigned char *) buf;
>      > - const char *hex = "0123456789abcdef";
>      > - size_t i = 0;
>      > - for (; len--; p++) {
>      > - to[i++] = hex[p[0] >> 4];
>      > - to[i++] = hex[p[0] & 0x0f];
>      > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
>     *public_key,
>      > + MG_UECC_Curve curve) {
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
>      > +#else
>      > + mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
>      > +#endif
>      > + mg_uecc_word_t *y = point + curve->num_words;
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy(public_key, compressed + 1, curve->num_bytes);
>      > +#else
>      > + mg_uecc_vli_bytesToNative(point, compressed + 1,
>     curve->num_bytes);
>      > +#endif
>      > + curve->x_side(y, point, curve);
>      > + curve->mod_sqrt(y, curve);
>      > +
>      > + if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
>      > + mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
>      > }
>      > - to[i] = '\0';
>      > - return to;
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
>      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>     curve->num_bytes, y);
>      > +#endif
>      > }
>      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>      > +
>      > +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t
>     *point,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>      > + wordcount_t num_words = curve->num_words;
>      > +
>      > + /* The point at infinity is invalid. */
>      > + if (EccPoint_isZero(point, curve)) {
>      > + return 0;
>      > + }
>      >
>      > -static unsigned char mg_unhex_nimble(unsigned char c) {
>      > - return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
>      > - : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
>      > - : (unsigned char) (c - 'W');
>      > + /* x and y must be smaller than p. */
>      > + if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
>      > + mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words)
>     != 1) {
>      > + return 0;
>      > + }
>      > +
>      > + mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
>      > + curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
>      > +
>      > + /* Make sure that y^2 == x^3 + ax + b */
>      > + return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
>      > }
>      >
>      > -unsigned long mg_unhexn(const char *s, size_t len) {
>      > - unsigned long i = 0, v = 0;
>      > - for (i = 0; i < len; i++) v <<= 4, v |=
>     mg_unhex_nimble(((uint8_t *) s)[i]);
>      > - return v;
>      > +int mg_uecc_valid_public_key(const uint8_t *public_key,
>     MG_UECC_Curve curve) {
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>      > +#else
>      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>      > +#endif
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
>      > + mg_uecc_vli_bytesToNative(_public + curve->num_words,
>      > + public_key + curve->num_bytes, curve->num_bytes);
>      > +#endif
>      > + return mg_uecc_valid_point(_public, curve);
>      > }
>      >
>      > -void mg_unhex(const char *buf, size_t len, unsigned char *to) {
>      > - size_t i;
>      > - for (i = 0; i < len; i += 2) {
>      > - to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
>      > +int mg_uecc_compute_public_key(const uint8_t *private_key,
>     uint8_t *public_key,
>      > + MG_UECC_Curve curve) {
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
>      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>      > +#else
>      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>      > +#endif
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_bytesToNative(_private, private_key,
>      > + BITS_TO_BYTES(curve->num_n_bits));
>      > +#endif
>      > +
>      > + /* Make sure the private key is in the range [1, n-1]. */
>      > + if (mg_uecc_vli_isZero(_private,
>     BITS_TO_WORDS(curve->num_n_bits))) {
>      > + return 0;
>      > }
>      > -}
>      >
>      > -uint64_t mg_tou64(struct mg_str str) {
>      > - uint64_t result = 0;
>      > - size_t i = 0;
>      > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
>     '\t')) i++;
>      > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>      > - result *= 10;
>      > - result += (unsigned) (str.ptr[i] - '0');
>      > - i++;
>      > + if (mg_uecc_vli_cmp(curve->n, _private,
>     BITS_TO_WORDS(curve->num_n_bits)) !=
>      > + 1) {
>      > + return 0;
>      > }
>      > - return result;
>      > +
>      > + /* Compute public key. */
>      > + if (!EccPoint_compute_public_key(_public, _private, curve)) {
>      > + return 0;
>      > + }
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
>      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>     curve->num_bytes,
>      > + _public + curve->num_words);
>      > +#endif
>      > + return 1;
>      > }
>      >
>      > -int64_t mg_to64(struct mg_str str) {
>      > - int64_t result = 0, neg = 1, max = 922337203685477570 /*
>     INT64_MAX/10-10 */;
>      > - size_t i = 0;
>      > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
>     '\t')) i++;
>      > - if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
>      > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>      > - if (result > max) return 0;
>      > - result *= 10;
>      > - result += (str.ptr[i] - '0');
>      > - i++;
>      > +/* -------- ECDSA code -------- */
>      > +
>      > +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
>      > + unsigned bits_size, MG_UECC_Curve curve) {
>      > + unsigned num_n_bytes = (unsigned)
>     BITS_TO_BYTES(curve->num_n_bits);
>      > + unsigned num_n_words = (unsigned)
>     BITS_TO_WORDS(curve->num_n_bits);
>      > + int shift;
>      > + mg_uecc_word_t carry;
>      > + mg_uecc_word_t *ptr;
>      > +
>      > + if (bits_size > num_n_bytes) {
>      > + bits_size = num_n_bytes;
>      > + }
>      > +
>      > + mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) native, bits, bits_size);
>      > +#else
>      > + mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
>      > +#endif
>      > + if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
>      > + return;
>      > + }
>      > + shift = (int) bits_size * 8 - curve->num_n_bits;
>      > + carry = 0;
>      > + ptr = native + num_n_words;
>      > + while (ptr-- > native) {
>      > + mg_uecc_word_t temp = *ptr;
>      > + *ptr = (temp >> shift) | carry;
>      > + carry = temp << (MG_UECC_WORD_BITS - shift);
>      > + }
>      > +
>      > + /* Reduce mod curve_n */
>      > + if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t)
>     num_n_words) !=
>      > + 1) {
>      > + mg_uecc_vli_sub(native, native, curve->n, (wordcount_t)
>     num_n_words);
>      > }
>      > - return result * neg;
>      > }
>      >
>      > -char *mg_remove_double_dots(char *s) {
>      > - char *saved = s, *p = s;
>      > - while (*s != '\0') {
>      > - *p++ = *s++;
>      > - if (s[-1] == '/' || s[-1] == '\\') {
>      > - while (s[0] != '\0') {
>      > - if (s[0] == '/' || s[0] == '\\') {
>      > - s++;
>      > - } else if (s[0] == '.' && s[1] == '.' &&
>      > - (s[2] == '/' || s[2] == '\\')) {
>      > - s += 2;
>      > - } else {
>      > - break;
>      > - }
>      > - }
>      > +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
>      > + const uint8_t *message_hash,
>      > + unsigned hash_size, mg_uecc_word_t *k,
>      > + uint8_t *signature,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t s[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t *k2[2] = {tmp, s};
>      > + mg_uecc_word_t *initial_Z = 0;
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
>      > +#else
>      > + mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
>      > +#endif
>      > + mg_uecc_word_t carry;
>      > + wordcount_t num_words = curve->num_words;
>      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>      > + bitcount_t num_n_bits = curve->num_n_bits;
>      > +
>      > + /* Make sure 0 < k < curve_n */
>      > + if (mg_uecc_vli_isZero(k, num_words) ||
>      > + mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
>      > + return 0;
>      > + }
>      > +
>      > + carry = regularize_k(k, tmp, s, curve);
>      > + /* If an RNG function was specified, try to get a random
>     initial Z value to
>      > + improve protection against side-channel attacks. */
>      > + if (g_rng_function) {
>      > + if (!mg_uecc_generate_random_int(k2[carry], curve->p,
>     num_words)) {
>      > + return 0;
>      > }
>      > + initial_Z = k2[carry];
>      > + }
>      > + EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
>      > + (bitcount_t) (num_n_bits + 1), curve);
>      > + if (mg_uecc_vli_isZero(p, num_words)) {
>      > + return 0;
>      > }
>      > - *p = '\0';
>      > - return saved;
>      > -}
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/timer.c"
>      > + /* If an RNG function was specified, get a random number
>      > + to prevent side channel analysis of k. */
>      > + if (!g_rng_function) {
>      > + mg_uecc_vli_clear(tmp, num_n_words);
>      > + tmp[0] = 1;
>      > + } else if (!mg_uecc_generate_random_int(tmp, curve->n,
>     num_n_words)) {
>      > + return 0;
>      > + }
>      > +
>      > + /* Prevent side channel analysis of mg_uecc_vli_modInv() to
>     determine
>      > + bits of k / the private key by premultiplying by a random
>     number */
>      > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' =
>     rand * k */
>      > + mg_uecc_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */
>      > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1
>     / k */
>      > +
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>      > + mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /*
>     store r */
>      > #endif
>      >
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) tmp, private_key,
>     BITS_TO_BYTES(curve->num_n_bits));
>      > +#else
>      > + mg_uecc_vli_bytesToNative(tmp, private_key,
>      > + BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
>      > +#endif
>      >
>      > + s[num_n_words - 1] = 0;
>      > + mg_uecc_vli_set(s, p, num_words);
>      > + mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s =
>     r*d */
>      >
>      > -#define MG_TIMER_CALLED 4
>      > + bits2int(tmp, message_hash, hash_size, curve);
>      > + mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e
>     + r*d */
>      > + mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e
>     + r*d) / k */
>      > + if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t)
>     curve->num_bytes * 8) {
>      > + return 0;
>      > + }
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
>      > + curve->num_bytes);
>      > +#else
>      > + mg_uecc_vli_nativeToBytes(signature + curve->num_bytes,
>     curve->num_bytes, s);
>      > +#endif
>      > + return 1;
>      > +}
>      >
>      > -void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
>     uint64_t ms,
>      > - unsigned flags, void (*fn)(void *), void *arg) {
>      > - t->id = 0, t->period_ms = ms, t->expire = 0;
>      > - t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
>      > - *head = t;
>      > +#if 0
>      > +/* For testing - sign with an explicitly specified k value */
>      > +int mg_uecc_sign_with_k(const uint8_t *private_key, const
>     uint8_t *message_hash,
>      > + unsigned hash_size, const uint8_t *k, uint8_t *signature,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
>      > + bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits),
>     curve);
>      > + return mg_uecc_sign_with_k_internal(private_key, message_hash,
>     hash_size, k2,
>      > + signature, curve);
>      > }
>      > +#endif
>      >
>      > -void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
>      > - while (*head && *head != t) head = &(*head)->next;
>      > - if (*head) *head = t->next;
>      > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
>     *message_hash,
>      > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
>      > + mg_uecc_word_t k[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tries;
>      > +
>      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>      > + if (!mg_uecc_generate_random_int(k, curve->n,
>      > + BITS_TO_WORDS(curve->num_n_bits))) {
>      > + return 0;
>      > + }
>      > +
>      > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
>     hash_size, k,
>      > + signature, curve)) {
>      > + return 1;
>      > + }
>      > + }
>      > + return 0;
>      > }
>      >
>      > -// t: expiration time, prd: period, now: current time. Return
>     true if expired
>      > -bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
>      > - if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
>      > - if (*t == 0) *t = now + prd; // Firt poll? Set expiration
>      > - if (*t > now) return false; // Not expired yet, return
>      > - *t = (now - *t) > prd ? now + prd : *t + prd; // Next
>     expiration time
>      > - return true; // Expired, return true
>      > +/* Compute an HMAC using K as a key (as in RFC 6979). Note that
>     K is always
>      > + the same size as the hash result size. */
>      > +static void HMAC_init(const MG_UECC_HashContext *hash_context,
>      > + const uint8_t *K) {
>      > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
>      > + unsigned i;
>      > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
>     0x36;
>      > + for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
>      > +
>      > + hash_context->init_hash(hash_context);
>      > + hash_context->update_hash(hash_context, pad,
>     hash_context->block_size);
>      > }
>      >
>      > -void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
>      > - struct mg_timer *t, *tmp;
>      > - for (t = *head; t != NULL; t = tmp) {
>      > - bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
>      > - !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
>      > - bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
>      > - tmp = t->next;
>      > - if (!once && !expired) continue;
>      > - if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
>     MG_TIMER_CALLED)) {
>      > - t->fn(t->arg);
>      > +static void HMAC_update(const MG_UECC_HashContext *hash_context,
>      > + const uint8_t *message, unsigned message_size) {
>      > + hash_context->update_hash(hash_context, message, message_size);
>      > +}
>      > +
>      > +static void HMAC_finish(const MG_UECC_HashContext *hash_context,
>      > + const uint8_t *K, uint8_t *result) {
>      > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
>      > + unsigned i;
>      > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
>     0x5c;
>      > + for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
>      > +
>      > + hash_context->finish_hash(hash_context, result);
>      > +
>      > + hash_context->init_hash(hash_context);
>      > + hash_context->update_hash(hash_context, pad,
>     hash_context->block_size);
>      > + hash_context->update_hash(hash_context, result,
>     hash_context->result_size);
>      > + hash_context->finish_hash(hash_context, result);
>      > +}
>      > +
>      > +/* V = HMAC_K(V) */
>      > +static void update_V(const MG_UECC_HashContext *hash_context,
>     uint8_t *K,
>      > + uint8_t *V) {
>      > + HMAC_init(hash_context, K);
>      > + HMAC_update(hash_context, V, hash_context->result_size);
>      > + HMAC_finish(hash_context, K, V);
>      > +}
>      > +
>      > +/* Deterministic signing, similar to RFC 6979. Differences are:
>      > + * We just use H(m) directly rather than bits2octets(H(m))
>      > + (it is not reduced modulo curve_n).
>      > + * We generate a value for k (aka T) directly rather than
>     converting
>      > + endianness.
>      > +
>      > + Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped
>     0x00 or 0x01) /
>      > + <HMAC pad> */
>      > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
>      > + const uint8_t *message_hash, unsigned hash_size,
>      > + const MG_UECC_HashContext *hash_context,
>      > + uint8_t *signature, MG_UECC_Curve curve) {
>      > + uint8_t *K = hash_context->tmp;
>      > + uint8_t *V = K + hash_context->result_size;
>      > + wordcount_t num_bytes = curve->num_bytes;
>      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>      > + bitcount_t num_n_bits = curve->num_n_bits;
>      > + mg_uecc_word_t tries;
>      > + unsigned i;
>      > + for (i = 0; i < hash_context->result_size; ++i) {
>      > + V[i] = 0x01;
>      > + K[i] = 0;
>      > + }
>      > +
>      > + /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
>      > + HMAC_init(hash_context, K);
>      > + V[hash_context->result_size] = 0x00;
>      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>      > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
>      > + HMAC_update(hash_context, message_hash, hash_size);
>      > + HMAC_finish(hash_context, K, K);
>      > +
>      > + update_V(hash_context, K, V);
>      > +
>      > + /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
>      > + HMAC_init(hash_context, K);
>      > + V[hash_context->result_size] = 0x01;
>      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>      > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
>      > + HMAC_update(hash_context, message_hash, hash_size);
>      > + HMAC_finish(hash_context, K, K);
>      > +
>      > + update_V(hash_context, K, V);
>      > +
>      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>      > + mg_uecc_word_t T[MG_UECC_MAX_WORDS];
>      > + uint8_t *T_ptr = (uint8_t *) T;
>      > + wordcount_t T_bytes = 0;
>      > + for (;;) {
>      > + update_V(hash_context, K, V);
>      > + for (i = 0; i < hash_context->result_size; ++i) {
>      > + T_ptr[T_bytes++] = V[i];
>      > + if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
>      > + goto filled;
>      > + }
>      > + }
>      > + }
>      > + filled:
>      > + if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 >
>     num_n_bits) {
>      > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
>      > + T[num_n_words - 1] &=
>      > + mask >>
>      > + ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
>      > + }
>      > +
>      > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
>     hash_size, T,
>      > + signature, curve)) {
>      > + return 1;
>      > }
>      > - t->flags |= MG_TIMER_CALLED;
>      > +
>      > + /* K = HMAC_K(V || 0x00) */
>      > + HMAC_init(hash_context, K);
>      > + V[hash_context->result_size] = 0x00;
>      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>      > + HMAC_finish(hash_context, K, K);
>      > +
>      > + update_V(hash_context, K, V);
>      > }
>      > + return 0;
>      > }
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/tls_dummy.c"
>      > +static bitcount_t smax(bitcount_t a, bitcount_t b) {
>      > + return (a > b ? a : b);
>      > +}
>      > +
>      > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
>     *message_hash,
>      > + unsigned hash_size, const uint8_t *signature,
>      > + MG_UECC_Curve curve) {
>      > + mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
>      > + mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
>      > + const mg_uecc_word_t *points[4];
>      > + const mg_uecc_word_t *point;
>      > + bitcount_t num_bits;
>      > + bitcount_t i;
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>      > +#else
>      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>      > #endif
>      > + mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
>      > + wordcount_t num_words = curve->num_words;
>      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>      >
>      > + rx[num_n_words - 1] = 0;
>      > + r[num_n_words - 1] = 0;
>      > + s[num_n_words - 1] = 0;
>      >
>      > -#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL &&
>     !MG_ENABLE_CUSTOM_TLS
>      > -void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > - (void) opts;
>      > - mg_error(c, "TLS is not enabled");
>      > -}
>      > -void mg_tls_handshake(struct mg_connection *c) {
>      > - (void) c;
>      > -}
>      > -void mg_tls_free(struct mg_connection *c) {
>      > - (void) c;
>      > -}
>      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>      > -}
>      > -long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>      > -}
>      > -size_t mg_tls_pending(struct mg_connection *c) {
>      > - (void) c;
>      > - return 0;
>      > -}
>      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > + bcopy((uint8_t *) r, signature, curve->num_bytes);
>      > + bcopy((uint8_t *) s, signature + curve->num_bytes,
>     curve->num_bytes);
>      > +#else
>      > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
>      > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
>     curve->num_bytes,
>      > + curve->num_bytes);
>      > + mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
>      > + mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes,
>     curve->num_bytes);
>      > #endif
>      >
>      > -#ifdef MG_ENABLE_LINES
>      > -#line 1 "src/tls_mbed.c"
>      > -#endif
>      > + /* r, s must not be 0. */
>      > + if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s,
>     num_words)) {
>      > + return 0;
>      > + }
>      >
>      > + /* r, s must be < n. */
>      > + if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
>      > + mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
>      > + return 0;
>      > + }
>      >
>      > + /* Calculate u1 and u2. */
>      > + mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
>      > + u1[num_n_words - 1] = 0;
>      > + bits2int(u1, message_hash, hash_size, curve);
>      > + mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 =
>     e/s */
>      > + mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 =
>     r/s */
>      > +
>      > + /* Calculate sum = G + Q. */
>      > + mg_uecc_vli_set(sum, _public, num_words);
>      > + mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
>      > + mg_uecc_vli_set(tx, curve->G, num_words);
>      > + mg_uecc_vli_set(ty, curve->G + num_words, num_words);
>      > + mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2
>     - x1 */
>      > + XYcZ_add(tx, ty, sum, sum + num_words, curve);
>      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
>      > + apply_z(sum, sum + num_words, z, curve);
>      > +
>      > + /* Use Shamir's trick to calculate u1*G + u2*Q */
>      > + points[0] = 0;
>      > + points[1] = curve->G;
>      > + points[2] = _public;
>      > + points[3] = sum;
>      > + num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
>      > + mg_uecc_vli_numBits(u2, num_n_words));
>      > + point =
>      > + points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
>      > + ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
>      > + mg_uecc_vli_set(rx, point, num_words);
>      > + mg_uecc_vli_set(ry, point + num_words, num_words);
>      > + mg_uecc_vli_clear(z, num_words);
>      > + z[0] = 1;
>      > +
>      > + for (i = num_bits - 2; i >= 0; --i) {
>      > + mg_uecc_word_t index;
>      > + curve->double_jacobian(rx, ry, z, curve);
>      > +
>      > + index = (!!mg_uecc_vli_testBit(u1, i)) |
>      > + (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
>      > + point = points[index];
>      > + if (point) {
>      > + mg_uecc_vli_set(tx, point, num_words);
>      > + mg_uecc_vli_set(ty, point + num_words, num_words);
>      > + apply_z(tx, ty, z, curve);
>      > + mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2
>     - x1 */
>      > + XYcZ_add(tx, ty, rx, ry, curve);
>      > + mg_uecc_vli_modMult_fast(z, z, tz, curve);
>      > + }
>      > + }
>      >
>      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
>      > + apply_z(rx, ry, z, curve);
>      >
>      > -#if MG_ENABLE_MBEDTLS
>      > + /* v = x1 (mod n) */
>      > + if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
>      > + mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
>      > + }
>      >
>      > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>     0x03000000
>      > -#define MGRNG , rng_get, NULL
>      > -#else
>      > -#define MGRNG
>      > -#endif
>      > + /* Accept only if v == r. */
>      > + return (int) (mg_uecc_vli_equal(rx, r, num_words));
>      > +}
>      >
>      > -void mg_tls_free(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - if (tls != NULL) {
>      > - free(tls->cafile);
>      > - mbedtls_ssl_free(&tls->ssl);
>      > - mbedtls_pk_free(&tls->pk);
>      > - mbedtls_x509_crt_free(&tls->ca);
>      > - mbedtls_x509_crt_free(&tls->cert);
>      > - mbedtls_ssl_config_free(&tls->conf);
>      > - free(tls);
>      > - c->tls = NULL;
>      > - }
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +
>      > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
>      > + return curve->num_words;
>      > }
>      >
>      > -static int mg_net_send(void *ctx, const unsigned char *buf,
>     size_t len) {
>      > - long n = mg_io_send((struct mg_connection *) ctx, buf, len);
>      > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>      > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>      > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>      > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
>      > - return (int) n;
>      > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
>      > + return curve->num_bytes;
>      > }
>      >
>      > -static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
>      > - long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
>      > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>      > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>      > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>      > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
>      > - return (int) n;
>      > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
>      > + return curve->num_bytes * 8;
>      > }
>      >
>      > -void mg_tls_handshake(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - int rc = mbedtls_ssl_handshake(&tls->ssl);
>      > - if (rc == 0) { // Success
>      > - MG_DEBUG(("%lu success", c->id));
>      > - c->is_tls_hs = 0;
>      > - mg_call(c, MG_EV_TLS_HS, NULL);
>      > - } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
>      > - rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
>      > - MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
>     c->is_connecting,
>      > - c->is_tls_hs, rc, -rc));
>      > - } else {
>      > - mg_error(c, "TLS handshake: -%#x", -rc); // Error
>      > - }
>      > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
>      > + return BITS_TO_WORDS(curve->num_n_bits);
>      > }
>      >
>      > -static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
>      > - mg_random(buf, len);
>      > - (void) ctx;
>      > - return 0;
>      > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
>      > + return BITS_TO_BYTES(curve->num_n_bits);
>      > }
>      >
>      > -static void debug_cb(void *c, int lev, const char *s, int n,
>     const char *s2) {
>      > - n = (int) strlen(s2) - 1;
>      > - MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id,
>     lev, n, s2));
>      > - (void) s;
>      > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
>      > + return curve->num_n_bits;
>      > }
>      >
>      > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>     0x03000000
>      > -static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
>      > - (void) p_rng;
>      > - mg_random(buf, len);
>      > - return 0;
>      > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
>      > + return curve->p;
>      > }
>      > -#endif
>      >
>      > -static struct mg_str mg_loadfile(struct mg_fs *fs, const char
>     *path) {
>      > - size_t n = 0;
>      > - if (path[0] == '-') return mg_str(path);
>      > - char *p = mg_file_read(fs, path, &n);
>      > - return mg_str_n(p, n);
>      > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
>      > + return curve->n;
>      > }
>      >
>      > -void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>      > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>      > - int rc = 0;
>      > - c->tls = tls;
>      > - if (c->tls == NULL) {
>      > - mg_error(c, "TLS OOM");
>      > - goto fail;
>      > - }
>      > - MG_DEBUG(("%lu Setting TLS", c->id));
>      > - mbedtls_ssl_init(&tls->ssl);
>      > - mbedtls_ssl_config_init(&tls->conf);
>      > - mbedtls_x509_crt_init(&tls->ca);
>      > - mbedtls_x509_crt_init(&tls->cert);
>      > - mbedtls_pk_init(&tls->pk);
>      > - mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
>      > -#if defined(MG_MBEDTLS_DEBUG_LEVEL)
>      > - mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
>      > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
>      > + return curve->G;
>      > +}
>      > +
>      > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
>      > + return curve->b;
>      > +}
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
>      > + curve->mod_sqrt(a, curve);
>      > +}
>      > #endif
>      > - if ((rc = mbedtls_ssl_config_defaults(
>      > - &tls->conf,
>      > - c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
>      > - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
>      > - mg_error(c, "tls defaults %#x", -rc);
>      > - goto fail;
>      > - }
>      > - mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
>      > - if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
>      > - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
>      > - } else if (opts->ca != NULL && opts->ca[0] != '\0') {
>      > -#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
>      > - tls->cafile = strdup(opts->ca);
>      > - rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile,
>     NULL);
>      > - if (rc != 0) {
>      > - mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
>      > - goto fail;
>      > - }
>      > +
>      > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
>     mg_uecc_word_t *product,
>      > + MG_UECC_Curve curve) {
>      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>      > + curve->mmod_fast(result, product);
>      > #else
>      > - struct mg_str s = mg_loadfile(fs, opts->ca);
>      > - rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len
>     + 1);
>      > - if (opts->ca[0] != '-') free((char *) s.ptr);
>      > - if (rc != 0) {
>      > - mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
>      > - goto fail;
>      > - }
>      > - mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
>      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>      > #endif
>      > - if (opts->srvname.len > 0) {
>      > - char *x = mg_mprintf("%.*s", (int) opts->srvname.len,
>     opts->srvname.ptr);
>      > - mbedtls_ssl_set_hostname(&tls->ssl, x);
>      > - free(x);
>      > - }
>      > - mbedtls_ssl_conf_authmode(&tls->conf,
>     MBEDTLS_SSL_VERIFY_REQUIRED);
>      > - }
>      > - if (opts->cert != NULL && opts->cert[0] != '\0') {
>      > - struct mg_str s = mg_loadfile(fs, opts->cert);
>      > - const char *key = opts->certkey == NULL ? opts->cert :
>     opts->certkey;
>      > - rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr,
>     s.len + 1);
>      > - if (opts->cert[0] != '-') free((char *) s.ptr);
>      > - if (rc != 0) {
>      > - mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
>      > - goto fail;
>      > - }
>      > - s = mg_loadfile(fs, key);
>      > - rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len +
>     1, NULL,
>      > - 0 MGRNG);
>      > - if (key[0] != '-') free((char *) s.ptr);
>      > - if (rc != 0) {
>      > - mg_error(c, "tls key(%s) %#x", key, -rc);
>      > - goto fail;
>      > - }
>      > - rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
>      > - if (rc != 0) {
>      > - mg_error(c, "own cert %#x", -rc);
>      > - goto fail;
>      > - }
>      > - }
>      > - if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
>      > - mg_error(c, "setup err %#x", -rc);
>      > - goto fail;
>      > - }
>      > - c->tls = tls;
>      > - c->is_tls = 1;
>      > - c->is_tls_hs = 1;
>      > - mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
>      > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>     0) {
>      > - mg_tls_handshake(c);
>      > - }
>      > - return;
>      > -fail:
>      > - mg_tls_free(c);
>      > }
>      >
>      > -size_t mg_tls_pending(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
>      > -}
>      > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
>     mg_uecc_word_t *point,
>      > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
>      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>      > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
>      > + mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
>      >
>      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
>      > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>     MBEDTLS_ERR_SSL_WANT_WRITE)
>      > - return MG_IO_WAIT;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > + EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits +
>     1, curve);
>      > }
>      >
>      > -long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
>      > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>     MBEDTLS_ERR_SSL_WANT_WRITE)
>      > - return MG_IO_WAIT;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > -}
>      > -#endif
>      > +#endif /* MG_UECC_ENABLE_VLI_API */
>      > +#endif // MG_TLS_BUILTIN
>      > +// End of uecc BSD-2
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "src/tls_openssl.c"
>      > +#line 1 "src/tls_x25519.c"
>      > #endif
>      > +/**
>      > + * Adapted from STROBE: https://strobe.sourceforge.io/
>     <https://strobe.sourceforge.io/>
>      > + * Copyright (c) 2015-2016 Cryptography Research, Inc.
>      > + * Author: Mike Hamburg
>      > + * License: MIT License
>      > + */
>      >
>      >
>      >
>      > -#if MG_ENABLE_OPENSSL
>      > -static int mg_tls_err(struct mg_tls *tls, int res) {
>      > - int err = SSL_get_error(tls->ssl, res);
>      > - // We've just fetched the last error from the queue.
>      > - // Now we need to clear the error queue. If we do not, then the
>     following
>      > - // can happen (actually reported):
>      > - // - A new connection is accept()-ed with cert error (e.g.
>     self-signed cert)
>      > - // - Since all accept()-ed connections share listener's context,
>      > - // - *ALL* SSL accepted connection report read error on the
>     next poll cycle.
>      > - // Thus a single errored connection can close all the rest,
>     unrelated ones.
>      > - // Clearing the error keeps the shared SSL_CTX in an OK state.
>      > +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
>      >
>      > - if (err != 0) ERR_print_errors_fp(stderr);
>      > - ERR_clear_error();
>      > - if (err == SSL_ERROR_WANT_READ) return 0;
>      > - if (err == SSL_ERROR_WANT_WRITE) return 0;
>      > - return err;
>      > -}
>      > +#define X25519_WBITS 32
>      >
>      > -void mg_tls_init(struct mg_connection *c, const struct
>     mg_tls_opts *opts) {
>      > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>      > - const char *id = "mongoose";
>      > - static unsigned char s_initialised = 0;
>      > - int rc;
>      > +typedef uint32_t limb_t;
>      > +typedef uint64_t dlimb_t;
>      > +typedef int64_t sdlimb_t;
>      >
>      > - if (tls == NULL) {
>      > - mg_error(c, "TLS OOM");
>      > - goto fail;
>      > - }
>      > +#define NLIMBS (256 / X25519_WBITS)
>      > +typedef limb_t mg_fe[NLIMBS];
>      >
>      > - if (!s_initialised) {
>      > - SSL_library_init();
>      > - s_initialised++;
>      > - }
>      > - MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
>      > - opts->ca == NULL ? "null" : opts->ca,
>      > - opts->cert == NULL ? "null" : opts->cert,
>      > - opts->certkey == NULL ? "null" : opts->certkey));
>      > - tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
>      > - : SSL_CTX_new(SSLv23_server_method());
>      > - if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
>      > - mg_error(c, "SSL_new");
>      > - goto fail;
>      > - }
>      > - SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
>      > - (unsigned) strlen(id));
>      > - // Disable deprecated protocols
>      > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
>      > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
>      > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
>      > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
>      > -#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
>      > - SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
>      > -#endif
>      > -#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
>      > - SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
>      > -#endif
>      > +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand,
>     limb_t mier) {
>      > + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
>      > + *carry = (limb_t) (tmp >> X25519_WBITS);
>      > + return (limb_t) tmp;
>      > +}
>      >
>      > - if (opts->ca != NULL && opts->ca[0] != '\0') {
>      > - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
>     SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
>      > - NULL);
>      > - if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca,
>     NULL)) != 1) {
>      > - mg_error(c, "load('%s') %d err %d", opts->ca, rc,
>     mg_tls_err(tls, rc));
>      > - goto fail;
>      > - }
>      > - }
>      > - if (opts->cert != NULL && opts->cert[0] != '\0') {
>      > - const char *key = opts->certkey;
>      > - if (key == NULL) key = opts->cert;
>      > - if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) !=
>     1) {
>      > - mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
>      > - goto fail;
>      > - } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) !=
>     1) {
>      > - mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
>      > - goto fail;
>      > -#if OPENSSL_VERSION_NUMBER > 0x10100000L
>      > - } else if ((rc = SSL_use_certificate_chain_file(tls->ssl,
>     opts->cert)) !=
>      > - 1) {
>      > - mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
>      > - goto fail;
>      > -#endif
>      > - } else {
>      > - SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
>      > -#if OPENSSL_VERSION_NUMBER > 0x10002000L
>      > - SSL_set_ecdh_auto(tls->ssl, 1);
>      > -#endif
>      > - }
>      > - }
>      > - if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl,
>     opts->ciphers);
>      > -#if OPENSSL_VERSION_NUMBER >= 0x10100000L
>      > - if (opts->srvname.len > 0) {
>      > - char *s = mg_mprintf("%.*s", (int) opts->srvname.len,
>     opts->srvname.ptr);
>      > - SSL_set1_host(tls->ssl, s);
>      > - free(s);
>      > - }
>      > -#endif
>      > - c->tls = tls;
>      > - c->is_tls = 1;
>      > - c->is_tls_hs = 1;
>      > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>     0) {
>      > - mg_tls_handshake(c);
>      > - }
>      > - MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
>     "client"));
>      > - return;
>      > -fail:
>      > - c->is_closing = 1;
>      > - free(tls);
>      > +// These functions are implemented in terms of umaal on ARM
>      > +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
>      > + dlimb_t total = (dlimb_t) *carry + acc + mand;
>      > + *carry = (limb_t) (total >> X25519_WBITS);
>      > + return (limb_t) total;
>      > +}
>      > +
>      > +static limb_t adc0(limb_t *carry, limb_t acc) {
>      > + dlimb_t total = (dlimb_t) *carry + acc;
>      > + *carry = (limb_t) (total >> X25519_WBITS);
>      > + return (limb_t) total;
>      > +}
>      > +
>      > +// - Precondition: carry is small.
>      > +// - Invariant: result of propagate is < 2^255 + 1 word
>      > +// - In particular, always less than 2p.
>      > +// - Also, output x >= min(x,19)
>      > +static void propagate(mg_fe x, limb_t over) {
>      > + unsigned i;
>      > + limb_t carry;
>      > + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
>      > + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
>      > +
>      > + carry = over * 19;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + x[i] = adc0(&carry, x[i]);
>      > + }
>      > }
>      >
>      > -void mg_tls_handshake(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - int rc;
>      > - SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
>      > - rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
>      > - if (rc == 1) {
>      > - MG_DEBUG(("%lu success", c->id));
>      > - c->is_tls_hs = 0;
>      > - mg_call(c, MG_EV_TLS_HS, NULL);
>      > - } else {
>      > - int code = mg_tls_err(tls, rc);
>      > - if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
>      > +static void add(mg_fe out, const mg_fe a, const mg_fe b) {
>      > + unsigned i;
>      > + limb_t carry = 0;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + out[i] = adc(&carry, a[i], b[i]);
>      > }
>      > + propagate(out, carry);
>      > }
>      >
>      > -void mg_tls_free(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - if (tls == NULL) return;
>      > - SSL_free(tls->ssl);
>      > - SSL_CTX_free(tls->ctx);
>      > - free(tls);
>      > - c->tls = NULL;
>      > +static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
>      > + unsigned i;
>      > + sdlimb_t carry = -38;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + carry = carry + a[i] - b[i];
>      > + out[i] = (limb_t) carry;
>      > + carry >>= X25519_WBITS;
>      > + }
>      > + propagate(out, (limb_t) (1 + carry));
>      > +}
>      > +
>      > +// `b` can contain less than 8 limbs, thus we use `limb_t *`
>     instead of `mg_fe`
>      > +// to avoid build warnings
>      > +static void mul(mg_fe out, const mg_fe a, const limb_t *b,
>     unsigned nb) {
>      > + limb_t accum[2 * NLIMBS] = {0};
>      > + unsigned i, j;
>      > +
>      > + limb_t carry2;
>      > + for (i = 0; i < nb; i++) {
>      > + limb_t mand = b[i];
>      > + carry2 = 0;
>      > + for (j = 0; j < NLIMBS; j++) {
>      > + limb_t tmp; // "a" may be misaligned
>      > + memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy
>      > + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
>      > + }
>      > + accum[i + j] = carry2;
>      > + }
>      > +
>      > + carry2 = 0;
>      > + for (j = 0; j < NLIMBS; j++) {
>      > + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
>      > + }
>      > + propagate(out, carry2);
>      > }
>      >
>      > -size_t mg_tls_pending(struct mg_connection *c) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
>      > +static void sqr(mg_fe out, const mg_fe a) {
>      > + mul(out, a, a, NLIMBS);
>      > +}
>      > +static void mul1(mg_fe out, const mg_fe a) {
>      > + mul(out, a, out, NLIMBS);
>      > +}
>      > +static void sqr1(mg_fe a) {
>      > + mul1(a, a);
>      > }
>      >
>      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - int n = SSL_read(tls->ssl, buf, (int) len);
>      > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
>      > + limb_t doswap) {
>      > + unsigned i;
>      > + for (i = 0; i < 2 * NLIMBS; i++) {
>      > + limb_t xor_ab = (a[i] ^ b[i]) & doswap;
>      > + a[i] ^= xor_ab;
>      > + b[i] ^= xor_ab;
>      > + }
>      > }
>      >
>      > -long mg_tls_send(struct mg_connection *c, const void *buf,
>     size_t len) {
>      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>      > - int n = SSL_write(tls->ssl, buf, (int) len);
>      > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
>      > - if (n <= 0) return MG_IO_ERR;
>      > - return n;
>      > +// Canonicalize a field element x, reducing it to the least
>     residue which is
>      > +// congruent to it mod 2^255-19
>      > +// - Precondition: x < 2^255 + 1 word
>      > +static limb_t canon(mg_fe x) {
>      > + // First, add 19.
>      > + unsigned i;
>      > + limb_t carry0 = 19;
>      > + limb_t res;
>      > + sdlimb_t carry;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + x[i] = adc0(&carry0, x[i]);
>      > + }
>      > + propagate(x, carry0);
>      > +
>      > + // Here, 19 <= x2 < 2^255
>      > + // - This is because we added 19, so before propagate it can't
>     be less
>      > + // than 19. After propagate, it still can't be less than 19,
>     because if
>      > + // propagate does anything it adds 19.
>      > + // - We know that the high bit must be clear, because either
>     the input was ~
>      > + // 2^255 + one word + 19 (in which case it propagates to at
>     most 2 words) or
>      > + // it was < 2^255. So now, if we subtract 19, we will get back
>     to something in
>      > + // [0,2^255-19).
>      > + carry = -19;
>      > + res = 0;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + carry += x[i];
>      > + res |= x[i] = (limb_t) carry;
>      > + carry >>= X25519_WBITS;
>      > + }
>      > + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
>      > +}
>      > +
>      > +static const limb_t a24[1] = {121665};
>      > +
>      > +static void ladder_part1(mg_fe xs[5]) {
>      > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
>     = xs[4];
>      > + add(t1, x2, z2); // t1 = A
>      > + sub(z2, x2, z2); // z2 = B
>      > + add(x2, x3, z3); // x2 = C
>      > + sub(z3, x3, z3); // z3 = D
>      > + mul1(z3, t1); // z3 = DA
>      > + mul1(x2, z2); // x3 = BC
>      > + add(x3, z3, x2); // x3 = DA+CB
>      > + sub(z3, z3, x2); // z3 = DA-CB
>      > + sqr1(t1); // t1 = AA
>      > + sqr1(z2); // z2 = BB
>      > + sub(x2, t1, z2); // x2 = E = AA-BB
>      > + mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24
>      > + add(z2, z2, t1); // z2 = E*a24 + AA
>      > +}
>      > +
>      > +static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
>      > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
>     = xs[4];
>      > + sqr1(z3); // z3 = (DA-CB)^2
>      > + mul1(z3, x1); // z3 = x1 * (DA-CB)^2
>      > + sqr1(x3); // x3 = (DA+CB)^2
>      > + mul1(z2, x2); // z2 = AA*(E*a24+AA)
>      > + sub(x2, t1, x2); // x2 = BB again
>      > + mul1(x2, t1); // x2 = AA*BB
>      > +}
>      > +
>      > +static void x25519_core(mg_fe xs[5], const uint8_t
>     scalar[X25519_BYTES],
>      > + const uint8_t *x1, int clamp) {
>      > + int i;
>      > + mg_fe x1_limbs;
>      > + limb_t swap = 0;
>      > + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
>      > + memset(xs, 0, 4 * sizeof(mg_fe));
>      > + x2[0] = z3[0] = 1;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + x3[i] = x1_limbs[i] =
>      > + MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
>      > + }
>      > +
>      > + for (i = 255; i >= 0; i--) {
>      > + uint8_t bytei = scalar[i / 8];
>      > + limb_t doswap;
>      > + if (clamp) {
>      > + if (i / 8 == 0) {
>      > + bytei &= (uint8_t) ~7U;
>      > + } else if (i / 8 == X25519_BYTES - 1) {
>      > + bytei &= 0x7F;
>      > + bytei |= 0x40;
>      > + }
>      > + }
>      > + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
>      > + condswap(x2, x3, swap ^ doswap);
>      > + swap = doswap;
>      > +
>      > + ladder_part1(xs);
>      > + ladder_part2(xs, (const limb_t *) x1_limbs);
>      > + }
>      > + condswap(x2, x3, swap);
>      > +}
>      > +
>      > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
>     scalar[X25519_BYTES],
>      > + const uint8_t x1[X25519_BYTES], int clamp) {
>      > + int i, ret;
>      > + mg_fe xs[5], out_limbs;
>      > + limb_t *x2, *z2, *z3, *prev;
>      > + static const struct {
>      > + uint8_t a, c, n;
>      > + } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3,
>     1, 1},
>      > + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
>      > + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}};
>      > + x25519_core(xs, scalar, x1, clamp);
>      > +
>      > + // Precomputed inversion chain
>      > + x2 = xs[0];
>      > + z2 = xs[1];
>      > + z3 = xs[3];
>      > +
>      > + prev = z2;
>      > + for (i = 0; i < 13; i++) {
>      > + int j;
>      > + limb_t *a = xs[steps[i].a];
>      > + for (j = steps[i].n; j > 0; j--) {
>      > + sqr(a, prev);
>      > + prev = a;
>      > + }
>      > + mul1(a, xs[steps[i].c]);
>      > + }
>      > +
>      > + // Here prev = z3
>      > + // x2 /= z2
>      > + mul(out_limbs, x2, z3, NLIMBS);
>      > + ret = (int) canon(out_limbs);
>      > + if (!clamp) ret = 0;
>      > + for (i = 0; i < NLIMBS; i++) {
>      > + uint32_t n = out_limbs[i];
>      > + out[i * 4] = (uint8_t) (n & 0xff);
>      > + out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
>      > + out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
>      > + out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
>      > + }
>      > + return ret;
>      > }
>      > -#endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > #line 1 "src/url.c"
>      > @@ -5382,7 +14797,7 @@ struct url {
>      > int mg_url_is_ssl(const char *url) {
>      > return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6)
>     == 0 ||
>      > strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
>      > - strncmp(url, "tls:", 4) == 0;
>      > + strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
>      > }
>      >
>      > static struct url urlparse(const char *url) {
>      > @@ -5464,6 +14879,14 @@ struct mg_str mg_url_pass(const char *url) {
>      > #endif
>      >
>      >
>      > +// Not using memset for zeroing memory, cause it can be dropped
>     by compiler
>      > +// See https://github.com/cesanta/mongoose/pull/1265
>     <https://github.com/cesanta/mongoose/pull/1265>
>      > +void mg_bzero(volatile unsigned char *buf, size_t len) {
>      > + if (buf != NULL) {
>      > + while (len--) *buf++ = 0;
>      > + }
>      > +}
>      > +
>      > #if MG_ENABLE_CUSTOM_RANDOM
>      > #else
>      > void mg_random(void *buf, size_t len) {
>      > @@ -5512,11 +14935,16 @@ uint16_t mg_ntohs(uint16_t net) {
>      > }
>      >
>      > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
>      > - int i;
>      > + static const uint32_t crclut[16] = {
>      > + // table for polynomial 0xEDB88320 (reflected)
>      > + 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190,
>     0x6B6B51F4,
>      > + 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8,
>     0xCB61B38C,
>      > + 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
>      > crc = ~crc;
>      > while (len--) {
>      > - crc ^= *(unsigned char *) buf++;
>      > - for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320
>     : crc >> 1;
>      > + uint8_t b = *(uint8_t *) buf++;
>      > + crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
>      > + crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
>      > }
>      > return ~crc;
>      > }
>      > @@ -5539,18 +14967,36 @@ static int parse_net(const char *spec,
>     uint32_t *net, uint32_t *mask) {
>      > return len;
>      > }
>      >
>      > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
>      > - struct mg_str k, v;
>      > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
>      > + struct mg_str entry;
>      > int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set,
>     deny by default
>      > - while (mg_commalist(&acl, &k, &v)) {
>      > - uint32_t net, mask;
>      > - if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
>      > - if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
>      > - if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
>      > + uint32_t remote_ip4;
>      > + if (remote_ip->is_ip6) {
>      > + return -1; // TODO(): handle IPv6 ACL and addresses
>      > + } else { // IPv4
>      > + memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
>      > + while (mg_span(acl, &entry, &acl, ',')) {
>      > + uint32_t net, mask;
>      > + if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
>      > + if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
>      > + if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
>      > + }
>      > }
>      > return allowed == '+';
>      > }
>      >
>      > +bool mg_path_is_sane(const struct mg_str path) {
>      > + const char *s = path.buf;
>      > + size_t n = path.len;
>      > + if (path.buf[0] == '.' && path.buf[1] == '.') return false; //
>     Starts with ..
>      > + for (; s[0] != '\0' && n > 0; s++, n--) {
>      > + if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir?
>      > + if (s[1] == '.' && s[2] == '.') return false; // Starts with ..
>      > + }
>      > + }
>      > + return true;
>      > +}
>      > +
>      > #if MG_ENABLE_CUSTOM_MILLIS
>      > #else
>      > uint64_t mg_millis(void) {
>      > @@ -5558,9 +15004,8 @@ uint64_t mg_millis(void) {
>      > return GetTickCount();
>      > #elif MG_ARCH == MG_ARCH_RP2040
>      > return time_us_64() / 1000;
>      > -#elif MG_ARCH == MG_ARCH_ESP32
>      > - return esp_timer_get_time() / 1000;
>      > -#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
>      > +#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
>      > + MG_ARCH == MG_ARCH_FREERTOS
>      > return xTaskGetTickCount() * portTICK_PERIOD_MS;
>      > #elif MG_ARCH == MG_ARCH_AZURERTOS
>      > return tx_time_get() * (1000 /* MS per SEC */ /
>     TX_TIMER_TICKS_PER_SECOND);
>      > @@ -5568,6 +15013,12 @@ uint64_t mg_millis(void) {
>      > return (uint64_t) Clock_getTicks();
>      > #elif MG_ARCH == MG_ARCH_ZEPHYR
>      > return (uint64_t) k_uptime_get();
>      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
>      > + return (uint64_t) rt_time_get();
>      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
>      > + return (uint64_t) ((osKernelGetTickCount() * 1000) /
>     osKernelGetTickFreq());
>      > +#elif MG_ARCH == MG_ARCH_RTTHREAD
>      > + return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
>      > #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
>      > // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on
>     linux
>      > // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on
>     linux
>      > @@ -5595,7 +15046,6 @@ uint64_t mg_millis(void) {
>      > }
>      > #endif
>      >
>      > -
>      > #ifdef MG_ENABLE_LINES
>      > #line 1 "src/ws.c"
>      > #endif
>      > @@ -5609,6 +15059,7 @@ uint64_t mg_millis(void) {
>      >
>      >
>      >
>      > +
>      > struct ws_msg {
>      > uint8_t flags;
>      > size_t header_len;
>      > @@ -5640,10 +15091,10 @@ static void ws_handshake(struct
>     mg_connection *c, const struct mg_str *wskey,
>      >
>      > mg_sha1_ctx sha_ctx;
>      > mg_sha1_init(&sha_ctx);
>      > - mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr,
>     wskey->len);
>      > + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf,
>     wskey->len);
>      > mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
>      > mg_sha1_final(sha, &sha_ctx);
>      > - mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
>      > + mg_base64_encode(sha, sizeof(sha), (char *) b64_sha,
>     sizeof(b64_sha));
>      > mg_xprintf(mg_pfn_iobuf, &c->send,
>      > "HTTP/1.1 101 Switching Protocols\r\n"
>      > "Upgrade: websocket\r\n"
>      > @@ -5653,7 +15104,7 @@ static void ws_handshake(struct
>     mg_connection *c, const struct mg_str *wskey,
>      > if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>      > if (wsproto != NULL) {
>      > mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
>      > - wsproto->ptr);
>      > + wsproto->buf);
>      > }
>      > mg_send(c, "\r\n", 2);
>      > }
>      > @@ -5746,12 +15197,15 @@ static bool
>     mg_ws_client_handshake(struct mg_connection *c) {
>      > mg_error(c, "not http"); // Some just, not an HTTP request
>      > } else if (n > 0) {
>      > if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
>      > - mg_error(c, "handshake error");
>      > + mg_error(c, "ws handshake error");
>      > } else {
>      > struct mg_http_message hm;
>      > - mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
>      > - c->is_websocket = 1;
>      > - mg_call(c, MG_EV_WS_OPEN, &hm);
>      > + if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
>      > + c->is_websocket = 1;
>      > + mg_call(c, MG_EV_WS_OPEN, &hm);
>      > + } else {
>      > + mg_error(c, "ws handshake error");
>      > + }
>      > }
>      > mg_iobuf_del(&c->recv, 0, (size_t) n);
>      > } else {
>      > @@ -5760,8 +15214,7 @@ static bool mg_ws_client_handshake(struct
>     mg_connection *c) {
>      > return false; // Continue event handler
>      > }
>      >
>      > -static void mg_ws_cb(struct mg_connection *c, int ev, void
>     *ev_data,
>      > - void *fn_data) {
>      > +static void mg_ws_cb(struct mg_connection *c, int ev, void
>     *ev_data) {
>      > struct ws_msg msg;
>      > size_t ofs = (size_t) c->pfn_data;
>      >
>      > @@ -5775,7 +15228,7 @@ static void mg_ws_cb(struct mg_connection
>     *c, int ev, void *ev_data,
>      > size_t len = msg.header_len + msg.data_len;
>      > uint8_t final = msg.flags & 128, op = msg.flags & 15;
>      > // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
>      > - // (int) m.data.len, (int) m.data.len, m.data.ptr));
>      > + // (int) m.data.len, (int) m.data.len, m.data.buf));
>      > switch (op) {
>      > case WEBSOCKET_OP_CONTINUE:
>      > mg_call(c, MG_EV_WS_CTL, &m);
>      > @@ -5796,7 +15249,7 @@ static void mg_ws_cb(struct mg_connection
>     *c, int ev, void *ev_data,
>      > MG_DEBUG(("%lu WS CLOSE", c->id));
>      > mg_call(c, MG_EV_WS_CTL, &m);
>      > // Echo the payload of the received CLOSE message back to the sender
>      > - mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
>      > + mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
>      > c->is_draining = 1;
>      > break;
>      > default:
>      > @@ -5827,7 +15280,6 @@ static void mg_ws_cb(struct mg_connection
>     *c, int ev, void *ev_data,
>      > }
>      > }
>      > }
>      > - (void) fn_data;
>      > (void) ev_data;
>      > }
>      >
>      > @@ -5839,7 +15291,7 @@ struct mg_connection *mg_ws_connect(struct
>     mg_mgr *mgr, const char *url,
>      > char nonce[16], key[30];
>      > struct mg_str host = mg_url_host(url);
>      > mg_random(nonce, sizeof(nonce));
>      > - mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
>      > + mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key,
>     sizeof(key));
>      > mg_xprintf(mg_pfn_iobuf, &c->send,
>      > "GET %s HTTP/1.1\r\n"
>      > "Upgrade: websocket\r\n"
>      > @@ -5847,7 +15299,7 @@ struct mg_connection *mg_ws_connect(struct
>     mg_mgr *mgr, const char *url,
>      > "Connection: Upgrade\r\n"
>      > "Sec-WebSocket-Version: 13\r\n"
>      > "Sec-WebSocket-Key: %s\r\n",
>      > - mg_url_uri(url), (int) host.len, host.ptr, key);
>      > + mg_url_uri(url), (int) host.len, host.buf, key);
>      > if (fmt != NULL) {
>      > va_list ap;
>      > va_start(ap, fmt);
>      > @@ -5896,1591 +15348,2247 @@ size_t mg_ws_wrap(struct
>     mg_connection *c, size_t len, int op) {
>      > }
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "mip/driver_stm32.c"
>      > +#line 1 "src/drivers/cmsis.c"
>      > #endif
>      > +// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
>     <https://arm-software.github.io/CMSIS_5/Driver/html/index.html>
>      >
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
>     MG_ENABLE_DRIVER_CMSIS
>      >
>      > -#if MG_ENABLE_MIP && \
>      > - (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
>      > -struct stm32_eth {
>      > - volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
>     MACMIIDR, MACFCR,
>      > - MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
>     MACDBGR, MACSR,
>      > - MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
>     MACA3HR,
>      > - MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
>      > - RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
>      > - RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
>      > - RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
>     PTPTSLUR,
>      > - PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
>     RESERVED9[564],
>      > - DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
>     DMAIER,
>      > - DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
>      > - DMACHRBAR;
>      > -};
>      > -#undef ETH
>      > -#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
>      >
>      > -#undef BIT
>      > -#define BIT(x) ((uint32_t) 1 << (x))
>      > -#define ETH_PKT_SIZE 1540 // Max frame size
>      > -#define ETH_DESC_CNT 4 // Descriptors count
>      > -#define ETH_DS 4 // Descriptor size (words)
>      >
>      > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>     ethernet buffers
>      > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>     ethernet buffers
>      > -static struct mip_if *s_ifp; // MIP interface
>      > -enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 }; // PHY constants
>      >
>      > -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > - ETH->MACMIIAR &= (7 << 2);
>      > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
>      > - ETH->MACMIIAR |= BIT(0);
>      > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
>      > - return ETH->MACMIIDR;
>      > -}
>      >
>      > -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t
>     val) {
>      > - ETH->MACMIIDR = val;
>      > - ETH->MACMIIAR &= (7 << 2);
>      > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
>     6) | BIT(1);
>      > - ETH->MACMIIAR |= BIT(0);
>      > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
>      > -}
>      > +extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
>      > +extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
>      >
>      > -static uint32_t get_hclk(void) {
>      > - struct rcc {
>      > - volatile uint32_t CR, PLLCFGR, CFGR;
>      > - } *rcc = (struct rcc *) 0x40023800;
>      > - uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
>     8MHz */;
>      > +static struct mg_tcpip_if *s_ifp;
>      >
>      > - if (rcc->CFGR & (1 << 2)) {
>      > - clk = hse;
>      > - } else if (rcc->CFGR & (1 << 3)) {
>      > - uint32_t vco, m, n, p;
>      > - m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
>      > - n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
>      > - p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
>      > - clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
>      > - vco = (uint32_t) ((uint64_t) clk * n / m);
>      > - clk = vco / p;
>      > - } else {
>      > - clk = hsi;
>      > +static void mac_cb(uint32_t);
>      > +static bool cmsis_init(struct mg_tcpip_if *);
>      > +static bool cmsis_up(struct mg_tcpip_if *);
>      > +static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
>      > +static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
>      > +
>      > +struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init,
>     cmsis_tx, NULL,
>      > + cmsis_up};
>      > +
>      > +static bool cmsis_init(struct mg_tcpip_if *ifp) {
>      > + ARM_ETH_MAC_ADDR addr;
>      > + s_ifp = ifp;
>      > +
>      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>      > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
>      > + ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
>      > + if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
>      > + if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) !=
>     ARM_DRIVER_OK)
>      > + return false;
>      > + if (cap.event_rx_frame == 0) // polled mode driver
>      > + mg_tcpip_driver_cmsis.rx = cmsis_rx;
>      > + mac->PowerControl(ARM_POWER_FULL);
>      > + if (cap.mac_address) { // driver provides MAC address
>      > + mac->GetMacAddress(&addr);
>      > + memcpy(ifp->mac, &addr, sizeof(ifp->mac));
>      > + } else { // we provide MAC address
>      > + memcpy(&addr, ifp->mac, sizeof(addr));
>      > + mac->SetMacAddress(&addr);
>      > }
>      > - uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
>      > - if (hpre < 8) return clk;
>      > + phy->PowerControl(ARM_POWER_FULL);
>      > + phy->SetInterface(cap.media_interface);
>      > + phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
>      > + return true;
>      > +}
>      >
>      > - uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>      > - return ((uint32_t) clk) >> ahbptab[hpre - 8];
>      > +static size_t cmsis_tx(const void *buf, size_t len, struct
>     mg_tcpip_if *ifp) {
>      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>      > + if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
>      > + ifp->nerr++;
>      > + return 0;
>      > + }
>      > + ifp->nsent++;
>      > + return len;
>      > }
>      >
>      > -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
>     as per 802.3,
>      > -// it must not exceed 2.5MHz As the AHB clock can be (and
>     usually is) derived
>      > -// from the HSI (internal RC), and it can go above specs, the
>     datasheets
>      > -// specify a range of frequencies and activate one of a series
>     of dividers to
>      > -// keep the MDC clock safely below 2.5MHz. We guess a divider
>     setting based on
>      > -// HCLK with a +5% drift. If the user uses a different clock
>     from our
>      > -// defaults, needs to set the macros on top Valid for
>     STM32F74xxx/75xxx
>      > -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
>     drift)
>      > -static int guess_mdc_cr(void) {
>      > - uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
>      > - uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
>     dividers
>      > - uint32_t hclk = get_hclk(); // Guess system HCLK
>      > - int result = -1; // Invalid CR value
>      > - if (hclk < 25000000) {
>      > - MG_ERROR(("HCLK too low"));
>      > - } else {
>      > - for (int i = 0; i < 6; i++) {
>      > - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > - result = crs[i];
>      > - break;
>      > +static bool cmsis_up(struct mg_tcpip_if *ifp) {
>      > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
>      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>      > + bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0; //
>     link state
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // just went up
>      > + ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
>      > + mac->Control(ARM_ETH_MAC_CONFIGURE,
>      > + (st.speed << ARM_ETH_MAC_SPEED_Pos) |
>      > + (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
>      > + ARM_ETH_MAC_ADDRESS_BROADCAST);
>      > + MG_DEBUG(("Link is %uM %s-duplex",
>      > + (st.speed == 2) ? 1000
>      > + : st.speed ? 100
>      > + : 10,
>      > + st.duplex ? "full" : "half"));
>      > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
>      > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
>      > + } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) { //
>     just went down
>      > + mac->Control(ARM_ETH_MAC_FLUSH,
>      > + ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
>      > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
>      > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
>      > + }
>      > + return up;
>      > +}
>      > +
>      > +static void mac_cb(uint32_t ev) {
>      > + if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
>      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>      > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
>      > + if (len >= 60 && len <= 1518) { // proper frame
>      > + char *p;
>      > + if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) { //
>     have room
>      > + if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) { // copy
>     succeeds
>      > + mg_queue_add(&s_ifp->recv_queue, len);
>      > + s_ifp->nrecv++;
>      > }
>      > + return;
>      > }
>      > - if (result < 0) MG_ERROR(("HCLK too high"));
>      > + s_ifp->ndrop++;
>      > }
>      > - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>      > - return result;
>      > + mac->ReadFrame(NULL, 0); // otherwise, discard
>      > +}
>      > +
>      > +static size_t cmsis_rx(void *buf, size_t buflen, struct
>     mg_tcpip_if *ifp) {
>      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>      > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
>      > + if (len >= 60 && len <= 1518 &&
>      > + ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
>      > + return len;
>      > + if (len > 0) mac->ReadFrame(NULL, 0); // discard bad frames
>      > + (void) ifp;
>      > + return 0;
>      > }
>      >
>      > -static bool mip_driver_stm32_init(struct mip_if *ifp) {
>      > - struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data
>     *) ifp->driver_data;
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/imxrt.c"
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
>     MG_ENABLE_DRIVER_IMXRT
>      > +struct imxrt_enet {
>      > + volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR,
>     RESERVED2[3],
>      > + ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC,
>     RESERVED5[7], RCR,
>      > + RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0,
>     TXIC1, TXIC2,
>      > + RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR,
>     GALR,
>      > + RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL,
>     RSEM,
>      > + RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC,
>     RACC,
>      > + RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
>      > + RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
>      > + RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127,
>     RMON_T_P128TO255,
>      > + RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048,
>     RMON_T_GTE2048,
>      > + RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL,
>     IEEE_T_MCOL,
>      > + IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR,
>     IEEE_T_CSERR,
>      > + IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3],
>     RMON_R_PACKETS,
>      > + RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
>      > + RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
>      > + RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511,
>     RMON_R_P512TO1023,
>      > + RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
>      > + IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR,
>     IEEE_R_FDXFC,
>      > + IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER,
>     ATCOR, ATINC,
>      > + ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1,
>     TCSR2, TCCR2,
>      > + TCSR3;
>      > +};
>      > +
>      > +#undef ENET
>      > +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
>      > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
>      > +#define ETH_DESC_CNT 5 // Descriptors count
>      > +#else
>      > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#endif
>      > +
>      > +#define ETH_PKT_SIZE 1536 // Max frame size, 64-bit aligned
>      > +
>      > +struct enet_desc {
>      > + uint16_t length; // Data length
>      > + uint16_t control; // Control and status
>      > + uint32_t *buffer; // Data ptr
>      > +};
>      > +
>      > +// TODO(): handle these in a portable compiler-independent
>     CMSIS-friendly way
>      > +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
>      > +
>      > +// Descriptors: in non-cached area (TODO(scaprile)),
>     (37.5.1.22.2 37.5.1.23.2)
>      > +// Buffers: 64-byte aligned (37.3.14)
>      > +static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT]
>     MG_64BYTE_ALIGNED;
>      > +static volatile struct enet_desc s_txdesc[ETH_DESC_CNT]
>     MG_64BYTE_ALIGNED;
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>     MG_64BYTE_ALIGNED;
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>     MG_64BYTE_ALIGNED;
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +
>      > +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
>      > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
>      > + ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18)
>     | (2 << 16);
>      > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
>      > + return ENET->MMFR & 0xffff;
>      > +}
>      > +
>      > +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
>      > + ENET->MMFR =
>      > + (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16)
>     | val;
>      > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
>      > +}
>      > +
>      > +// MDC clock is generated from IPS Bus clock (ipg_clk); as per
>     802.3,
>      > +// it must not exceed 2.5MHz
>      > +// The PHY receives the PLL6-generated 50MHz clock
>      > +static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_imxrt_data *d =
>      > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
>      > s_ifp = ifp;
>      >
>      > // Init RX descriptors
>      > for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > - s_rxdesc[i][0] = BIT(31); // Own
>      > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
>     chained
>      > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>     data buffer
>      > - s_rxdesc[i][3] =
>      > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + s_rxdesc[i].control = MG_BIT(15); // Own (E)
>      > + s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i]; // Point to data
>     buffer
>      > }
>      > + s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
>     descriptor
>      >
>      > // Init TX descriptors
>      > for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>      > - s_txdesc[i][3] =
>      > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + // s_txdesc[i].control = MG_BIT(10); // Own (TC)
>      > + s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
>      > }
>      > -
>      > - ETH->DMABMR |= BIT(0); // Software reset
>      > - while ((ETH->DMABMR & BIT(0)) != 0) (void) 0; // Wait until done
>      > -
>      > - // Set MDC clock divider. If user told us the value, use it.
>     Otherwise, guess
>      > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>     d->mdc_cr;
>      > - ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
>      > -
>      > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
>     not use
>      > - // hardware checksum. Therefore, descriptor size is 4, not 8
>      > - // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
>      > - ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT
>      > - ETH->MACFCR = BIT(7); // Disable zero quarta pause
>      > - // ETH->MACFFR = BIT(31); // Receive all
>      > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY
>      > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12)); // Set autonegotiation
>      > - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
>      > - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
>      > - ETH->DMAIER = BIT(6) | BIT(16); // RIE, NISE
>      > - ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
>     Duplex, Fast
>      > - ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST,
>     TSF, RSF
>      > -
>      > - // MAC address filtering
>      > - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>      > - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>      > - ((uint32_t) ifp->mac[2] << 16) |
>      > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>      > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
>      > + s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
>     descriptor
>      > +
>      > + ENET->ECR = MG_BIT(0); // Software reset, disable
>      > + while ((ENET->ECR & MG_BIT(0))) (void) 0; // Wait until done
>      > +
>      > + // Set MDC clock divider. If user told us the value, use it.
>      > + // TODO(): Otherwise, guess (currently assuming max freq)
>      > + int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
>      > + ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1); // HOLDTIME 2 clks
>      > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
>      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC
>     clocks PHY
>      > + // Select RMII mode, 100M, keep CRC, set max rx length, disable
>     loop
>      > + ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
>      > + // ENET->RCR |= MG_BIT(3); // Receive all
>      > + ENET->TCR = MG_BIT(2); // Full-duplex
>      > + ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
>      > + ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
>      > + ENET->MRBR[0] = ETH_PKT_SIZE; // Same size for RX/TX buffers
>      > + // MAC address filtering (bytes in reversed order)
>      > + ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t)
>     ifp->mac[5] << 16U;
>      > + ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
>      > + ((uint32_t) ifp->mac[1] << 16U) |
>      > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
>      > + ENET->ECR = MG_BIT(8) | MG_BIT(1); // Little-endian CPU, Enable
>      > + ENET->EIMR = MG_BIT(25); // Set interrupt mask
>      > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
>      > + ENET->TDAR = MG_BIT(24); // Transmit Descriptors have changed
>      > + // ENET->OPD = 0x10014;
>      > return true;
>      > }
>      >
>      > -static uint32_t s_txno;
>      > -static size_t mip_driver_stm32_tx(const void *buf, size_t len,
>     struct mip_if *ifp) {
>      > - if (len > sizeof(s_txbuf[s_txno])) {
>      > +// Transmit frame
>      > +static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
>      > + struct mg_tcpip_if *ifp) {
>      > + static int s_txno; // Current descriptor index
>      > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>      > MG_ERROR(("Frame too big, %ld", (long) len));
>      > - len = 0; // Frame is too big
>      > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
>      > - MG_ERROR(("No free descriptors"));
>      > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>     ETH->DMASR);
>      > - len = 0; // All descriptors are busy, fail
>      > + len = (size_t) -1; // fail
>      > + } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No descriptors available"));
>      > + len = 0; // retry later
>      > } else {
>      > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>      > - s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30); //
>     Chain,FS,LS
>      > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno].length = (uint16_t) len; // Set data len
>      > + // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
>      > + s_txdesc[s_txno].control |=
>      > + (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
>      > + ENET->TDAR = MG_BIT(24); // Descriptor ring updated
>      > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > }
>      > - ETH->DMASR = BIT(2) | BIT(5); // Clear any prior TBUS/TUS
>      > - ETH->DMATPDR = 0; // and resume
>      > - return len;
>      > (void) ifp;
>      > + return len;
>      > }
>      >
>      > -static bool mip_driver_stm32_up(struct mip_if *ifp) {
>      > - uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
>      > - (void) ifp;
>      > - return bsr & BIT(2) ? 1 : 0;
>      > +static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_imxrt_data *d =
>      > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
>      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + // tmp = reg with flags set to the most likely situation: 100M
>     full-duplex
>      > + // if(link is slow or half) set flags otherwise
>      > + // reg = tmp
>      > + uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex
>      > + uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M
>      > + if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9); // 10M
>      > + if (full_duplex == false) tcr &= ~MG_BIT(2); // Half-duplex
>      > + ENET->TCR = tcr; // IRQ handler does not fiddle with these
>     registers
>      > + ENET->RCR = rcr;
>      > + MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
>      > + tcr & MG_BIT(2) ? "full" : "half"));
>      > + }
>      > + return up;
>      > }
>      >
>      > -void ETH_IRQHandler(void);
>      > +void ENET_IRQHandler(void);
>      > static uint32_t s_rxno;
>      > -void ETH_IRQHandler(void) {
>      > - qp_mark(QP_IRQTRIGGERED, 0);
>      > - if (ETH->DMASR & BIT(6)) { // Frame received, loop
>      > - ETH->DMASR = BIT(16) | BIT(6); // Clear flag
>      > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
>      > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
>     BIT(9))) &&
>      > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
>      > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
>      > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>      > - // ETH->DMASR);
>      > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > - }
>      > - s_rxdesc[s_rxno][0] = BIT(31);
>      > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > +void ENET_IRQHandler(void) {
>      > + ENET->EIR = MG_BIT(25); // Ack IRQ
>      > + // Frame received, loop
>      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > + uint32_t r = s_rxdesc[s_rxno].control;
>      > + if (r & MG_BIT(15)) break; // exit when done
>      > + // skip partial/errored frames (Table 37-32)
>      > + if ((r & MG_BIT(11)) &&
>      > + !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) |
>     MG_BIT(0)))) {
>      > + size_t len = s_rxdesc[s_rxno].length;
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > }
>      > + s_rxdesc[s_rxno].control |= MG_BIT(15);
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > }
>      > - ETH->DMASR = BIT(7); // Clear possible RBUS while processing
>      > - ETH->DMARPDR = 0; // and resume RX
>      > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
>      > + // If b24 == 0, descriptors were exhausted and probably frames
>     were dropped
>      > }
>      >
>      > -struct mip_driver mip_driver_stm32 = {
>      > - mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx,
>     mip_driver_stm32_up};
>      > +struct mg_tcpip_driver mg_tcpip_driver_imxrt =
>     {mg_tcpip_driver_imxrt_init,
>      > + mg_tcpip_driver_imxrt_tx, NULL,
>      > + mg_tcpip_driver_imxrt_up};
>      > +
>      > #endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "mip/driver_tm4c.c"
>      > +#line 1 "src/drivers/phy.c"
>      > #endif
>      >
>      >
>      > -#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>     MG_ENABLE_DRIVER_TM4C
>      > -struct tm4c_emac {
>      > - volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
>     EMACHASHTBLL,
>      > - EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
>     EMACSTATUS,
>      > - EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
>     EMACADDR0H,
>      > - EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
>     EMACADDR3H,
>      > - EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
>      > - EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
>      > - EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
>     RESERVED6[4],
>      > - EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
>     EMACRXCNTCRCERR,
>      > - EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
>      > - EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
>     EMACSUBSECINC,
>      > - EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
>      > - EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
>      > - RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
>      > - EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
>     EMACTXDLADDR,
>      > - EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
>      > - RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
>     EMACHOSRXBA,
>      > - RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
>      > - EMACEPHYIM, EMACEPHYIMSC;
>      > +enum { // ID1 ID2
>      > + MG_PHY_KSZ8x = 0x22, // 0022 1561 - KSZ8081RNB
>      > + MG_PHY_DP83x = 0x2000, // 2000 a140 - TI DP83825I
>      > + MG_PHY_DP83867 = 0xa231, // 2000 a231 - TI DP83867I
>      > + MG_PHY_LAN87x = 0x7, // 0007 c0fx - LAN8720
>      > + MG_PHY_RTL8201 = 0x1C // 001c c816 - RTL8201
>      > +};
>      > +
>      > +enum {
>      > + MG_PHY_REG_BCR = 0,
>      > + MG_PHY_REG_BSR = 1,
>      > + MG_PHY_REG_ID1 = 2,
>      > + MG_PHY_REG_ID2 = 3,
>      > + MG_PHY_DP83x_REG_PHYSTS = 16,
>      > + MG_PHY_DP83867_REG_PHYSTS = 17,
>      > + MG_PHY_DP83x_REG_RCSR = 23,
>      > + MG_PHY_DP83x_REG_LEDCR = 24,
>      > + MG_PHY_KSZ8x_REG_PC1R = 30,
>      > + MG_PHY_KSZ8x_REG_PC2R = 31,
>      > + MG_PHY_LAN87x_REG_SCSR = 31,
>      > + MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
>      > + MG_PHY_RTL8201_REG_PAGESEL = 31
>      > +};
>      > +
>      > +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
>      > + switch (id1) {
>      > + case MG_PHY_DP83x:
>      > + switch (id2) {
>      > + case MG_PHY_DP83867:
>      > + return "DP83867";
>      > + default:
>      > + return "DP83x";
>      > + }
>      > + case MG_PHY_KSZ8x:
>      > + return "KSZ8x";
>      > + case MG_PHY_LAN87x:
>      > + return "LAN87x";
>      > + case MG_PHY_RTL8201:
>      > + return "RTL8201";
>      > + default:
>      > + return "unknown";
>      > + }
>      > + (void) id2;
>      > +}
>      > +
>      > +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t
>     config) {
>      > + uint16_t id1, id2;
>      > + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15)); // Reset PHY
>      > + while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15))
>     (void) 0;
>      > + // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw
>     says otherwise
>      > +
>      > + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
>      > + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
>      > + MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2,
>     mg_phy_id_to_str(id1, id2)));
>      > +
>      > + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
>      > + phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to
>     IO_MUX_CFG (0x0170)
>      > + phy->write_reg(phy_addr, 0x0e, 0x170);
>      > + phy->write_reg(phy_addr, 0x0d, 0x401f);
>      > + phy->write_reg(phy_addr, 0x0e, 0x10d);
>      > + }
>      > +
>      > + if (config & MG_PHY_CLOCKS_MAC) {
>      > + // Use PHY crystal oscillator (preserve defaults)
>      > + // nothing to do
>      > + } else { // MAC clocks PHY, PHY has no xtal
>      > + // Enable 50 MHz external ref clock at XI (preserve defaults)
>      > + if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
>      > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) |
>     MG_BIT(0));
>      > + } else if (id1 == MG_PHY_KSZ8x) {
>      > + phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
>      > + MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
>      > + } else if (id1 == MG_PHY_LAN87x) {
>      > + // nothing to do
>      > + } else if (id1 == MG_PHY_RTL8201) {
>      > + // assume PHY has been hardware strapped properly
>      > +#if 0
>      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7); //
>     Select page 7
>      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
>      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0); //
>     Select page 0
>      > +#endif
>      > + }
>      > + }
>      > +
>      > + if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
>      > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
>      > + MG_BIT(9) | MG_BIT(7)); // LED status, active high
>      > + } // Other PHYs do not support this feature
>      > +}
>      > +
>      > +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool
>     *full_duplex,
>      > + uint8_t *speed) {
>      > + bool up = false;
>      > + uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
>      > + if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2))) // some PHYs latch
>     down events
>      > + bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); // read again
>      > + up = bsr & MG_BIT(2);
>      > + if (up && full_duplex != NULL && speed != NULL) {
>      > + uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
>      > + if (id1 == MG_PHY_DP83x) {
>      > + uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
>      > + if (id2 == MG_PHY_DP83867) {
>      > + uint16_t physts = phy->read_reg(phy_addr,
>     MG_PHY_DP83867_REG_PHYSTS);
>      > + *full_duplex = physts & MG_BIT(13);
>      > + *speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
>      > + : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
>      > + : MG_PHY_SPEED_10M;
>      > + } else {
>      > + uint16_t physts = phy->read_reg(phy_addr,
>     MG_PHY_DP83x_REG_PHYSTS);
>      > + *full_duplex = physts & MG_BIT(2);
>      > + *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M :
>     MG_PHY_SPEED_100M;
>      > + }
>      > + } else if (id1 == MG_PHY_KSZ8x) {
>      > + uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
>      > + *full_duplex = pc1r & MG_BIT(2);
>      > + *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
>      > + } else if (id1 == MG_PHY_LAN87x) {
>      > + uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
>      > + *full_duplex = scsr & MG_BIT(4);
>      > + *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M :
>     MG_PHY_SPEED_10M;
>      > + } else if (id1 == MG_PHY_RTL8201) {
>      > + uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
>      > + *full_duplex = bcr & MG_BIT(8);
>      > + *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M :
>     MG_PHY_SPEED_10M;
>      > + }
>      > + }
>      > + return up;
>      > +}
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/ra.c"
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
>     MG_ENABLE_DRIVER_RA
>      > +struct ra_etherc {
>      > + volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR,
>     RESERVED2, ECSIPR,
>      > + RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR,
>     RESERVED6[3], IPGR,
>      > + APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR,
>     RESERVED8[20], MAHR,
>      > + RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR,
>     RESERVED11, CEFCR,
>      > + FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
>      > };
>      > -#undef EMAC
>      > -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>      >
>      > -#undef BIT
>      > -#define BIT(x) ((uint32_t) 1 << (x))
>      > -#define ETH_PKT_SIZE 1540 // Max frame size
>      > +struct ra_edmac {
>      > + volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR,
>     RESERVED2, TDLAR,
>      > + RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR,
>     RESERVED6, TRSCER,
>      > + RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10,
>     RMCR,
>      > + RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR,
>     TRIMD,
>      > + RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
>      > +};
>      > +
>      > +#undef ETHERC
>      > +#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
>      > +#undef EDMAC
>      > +#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
>      > +#undef RASYSC
>      > +#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
>      > +#undef ICU_IELSR
>      > +#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
>      > +
>      > +#define ETH_PKT_SIZE 1536 // Max frame size, multiple of 32
>      > #define ETH_DESC_CNT 4 // Descriptors count
>      > -#define ETH_DS 4 // Descriptor size (words)
>      >
>      > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>     ethernet buffers
>      > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>     ethernet buffers
>      > -static struct mip_if *s_ifp; // MIP interface
>      > -enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 }; // PHY
>     constants
>      > +// TODO(): handle these in a portable compiler-independent
>     CMSIS-friendly way
>      > +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
>      > +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
>      >
>      > -static inline void tm4cspin(volatile uint32_t count) {
>      > +// Descriptors: 16-byte aligned
>      > +// Buffers: 32-byte aligned (27.3.1)
>      > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4]
>     MG_16BYTE_ALIGNED;
>      > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][4]
>     MG_16BYTE_ALIGNED;
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>     MG_32BYTE_ALIGNED;
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>     MG_32BYTE_ALIGNED;
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +
>      > +// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or
>     equivalent
>      > +static inline void raspin(volatile uint32_t count) {
>      > while (count--) (void) 0;
>      > }
>      > -
>      > -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
>      > - EMAC->EMACMIIADDR &= (0xf << 2);
>      > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>     << 6);
>      > - EMAC->EMACMIIADDR |= BIT(0);
>      > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
>      > - return EMAC->EMACMIIDATA;
>      > +// count to get the 200ns SMC semi-cycle period (2.5MHz) calling
>     raspin():
>      > +// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
>      > +static uint32_t s_smispin;
>      > +
>      > +// Bit-banged SMI
>      > +static void smi_preamble(void) {
>      > + unsigned int i = 32;
>      > + uint32_t pir = MG_BIT(1) | MG_BIT(2); // write, mdio = 1, mdc = 0
>      > + ETHERC->PIR = pir;
>      > + while (i--) {
>      > + pir &= ~MG_BIT(0); // mdc = 0
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + pir |= MG_BIT(0); // mdc = 1
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + }
>      > }
>      > -
>      > -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
>     val) {
>      > - EMAC->EMACMIIDATA = val;
>      > - EMAC->EMACMIIADDR &= (0xf << 2);
>      > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>     << 6) | BIT(1);
>      > - EMAC->EMACMIIADDR |= BIT(0);
>      > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
>      > +static void smi_wr(uint16_t header, uint16_t data) {
>      > + uint32_t word = (header << 16) | data;
>      > + smi_preamble();
>      > + unsigned int i = 32;
>      > + while (i--) {
>      > + uint32_t pir = MG_BIT(1) |
>      > + (word & 0x80000000 ? MG_BIT(2) : 0); // write, mdc = 0, data
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + pir |= MG_BIT(0); // mdc = 1
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + word <<= 1;
>      > + }
>      > }
>      > -
>      > -static uint32_t get_sysclk(void) {
>      > - struct sysctl {
>      > - volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
>     PLLFREQ0,
>      > - PLLFREQ1;
>      > - } *sysctl = (struct sysctl *) 0x400FE000;
>      > - uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
>     25000000 /* 25MHz */;
>      > - if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
>      > - uint32_t fin, vco, mdiv, n, q, psysdiv;
>      > - uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
>      > - if (pllsrc == 0) {
>      > - clk = piosc;
>      > - } else if (pllsrc == 3) {
>      > - clk = mosc;
>      > - } else {
>      > - MG_ERROR(("Unsupported clock source"));
>      > - }
>      > - q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
>      > - n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
>      > - fin = clk / ((q + 1) * (n + 1));
>      > - mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
>      > - 0; // mint + (mfrac / 1024); MFRAC not supported
>      > - psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
>      > - vco = (uint32_t) ((uint64_t) fin * mdiv);
>      > - return vco / (psysdiv + 1);
>      > +static uint16_t smi_rd(uint16_t header) {
>      > + smi_preamble();
>      > + unsigned int i = 16; // 2 LSb as turnaround
>      > + uint32_t pir;
>      > + while (i--) {
>      > + pir = (i > 1 ? MG_BIT(1) : 0) |
>      > + (header & 0x8000
>      > + ? MG_BIT(2)
>      > + : 0); // mdc = 0, header, set read direction at turnaround
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + pir |= MG_BIT(0); // mdc = 1
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > + header <<= 1;
>      > }
>      > - uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
>      > - if (oscsrc == 0) {
>      > - clk = piosc;
>      > - } else if (oscsrc == 3) {
>      > - clk = mosc;
>      > - } else {
>      > - MG_ERROR(("Unsupported clock source"));
>      > + i = 16;
>      > + uint16_t data = 0;
>      > + while (i--) {
>      > + data <<= 1;
>      > + pir = 0; // read, mdc = 0
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin / 2); // 1/4 clock period, 300ns max access time
>      > + data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0); // read mdio
>      > + raspin(s_smispin / 2); // 1/4 clock period
>      > + pir |= MG_BIT(0); // mdc = 1
>      > + ETHERC->PIR = pir;
>      > + raspin(s_smispin);
>      > }
>      > - uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
>      > - return clk / (osysdiv + 1);
>      > + return data;
>      > }
>      >
>      > -// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
>     (AHB); as per
>      > -// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
>     clock can be
>      > -// derived from the PIOSC (internal RC), and it can go above
>     specs, the
>      > -// datasheets specify a range of frequencies and activate one of
>     a series of
>      > -// dividers to keep the MDC clock safely below 2.5MHz. We guess
>     a divider
>      > -// setting based on SYSCLK with a +5% drift. If the user uses a
>     different clock
>      > -// from our defaults, needs to set the macros on top Valid for
>     TM4C129x (20.7)
>      > -// (4.5% worst case drift)
>      > -// The PHY receives the main oscillator (MOSC) (20.3.1)
>      > -static int guess_mdc_cr(void) {
>      > - uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
>      > - uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
>      > - uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
>      > - int result = -1; // Invalid CR value
>      > - if (sysclk < 25000000) {
>      > - MG_ERROR(("SYSCLK too low"));
>      > - } else {
>      > - for (int i = 0; i < 4; i++) {
>      > - if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > - result = crs[i];
>      > - break;
>      > - }
>      > - }
>      > - if (result < 0) MG_ERROR(("SYSCLK too high"));
>      > - }
>      > - MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
>      > - return result;
>      > +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
>      > + return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) |
>     (reg << 2) | (2 << 0)));
>      > }
>      >
>      > -static bool mip_driver_tm4c_init(struct mip_if *ifp) {
>      > - struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data
>     *) ifp->driver_data;
>      > +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg <<
>     2) | (2 << 0)), val);
>      > +}
>      > +
>      > +// MDC clock is generated manually; as per 802.3, it must not
>     exceed 2.5MHz
>      > +static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_ra_data *d =
>      > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
>      > s_ifp = ifp;
>      >
>      > + // Init SMI clock timing. If user told us the clock value, use it.
>      > + // TODO(): Otherwise, guess
>      > + s_smispin = d->clock / 15000000;
>      > +
>      > // Init RX descriptors
>      > for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > - s_rxdesc[i][0] = BIT(31); // Own
>      > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
>     chained
>      > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>     data buffer
>      > - s_rxdesc[i][3] =
>      > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > - // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
>      > + s_rxdesc[i][0] = MG_BIT(31); // RACT
>      > + s_rxdesc[i][1] = ETH_PKT_SIZE << 16; // RBL
>      > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; // Point to data buffer
>      > }
>      > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
>     descriptor
>      >
>      > // Init TX descriptors
>      > for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>      > - s_txdesc[i][3] =
>      > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + // TACT = 0
>      > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
>      > }
>      > -
>      > - EMAC->EMACDMABUSMOD |= BIT(0); // Software reset
>      > - while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1); //
>     Wait until done
>      > -
>      > - // Set MDC clock divider. If user told us the value, use it.
>     Otherwise, guess
>      > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>     d->mdc_cr;
>      > - EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
>      > -
>      > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
>     not use
>      > - // hardware checksum. Therefore, descriptor size is 4, not 8
>      > - // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23)
>     | BIT(25);
>      > - EMAC->EMACIM = BIT(3) | BIT(9); // Mask timestamp & PMT IT
>      > - EMAC->EMACFLOWCTL = BIT(7); // Disable zero-quanta pause
>      > - // EMAC->EMACFRAMEFLTR = BIT(31); // Receive all
>      > - // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
>      > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15)); // Reset internal
>     PHY (EPHY)
>      > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12)); // Set
>     autonegotiation
>      > - EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
>     descriptors
>      > - EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
>     descriptors
>      > - EMAC->EMACDMAIM = BIT(6) | BIT(16); // RIE, NIE
>      > - EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
>     Duplex, Fast
>      > - EMAC->EMACDMAOPMODE =
>      > - BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
>      > - EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>      > - EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
>      > - ((uint32_t) ifp->mac[2] << 16) |
>      > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>      > - // NOTE(scaprile) There are 3 additional slots for filtering,
>     disabled by
>      > - // default. This also applies to the STM32 driver (at least for
>     F7)
>      > -
>      > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
>      > + s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
>     descriptor
>      > +
>      > + EDMAC->EDMR = MG_BIT(0); // Software reset, wait 64 PCLKA
>     clocks (27.2.1)
>      > + uint32_t sckdivcr = RASYSC[8]; // get divisors from SCKDIVCR
>     (8.2.2)
>      > + uint32_t ick = 1 << ((sckdivcr >> 24) & 7); // sys_clock div
>      > + uint32_t pcka = 1 << ((sckdivcr >> 12) & 7); // pclka div
>      > + raspin((64U * pcka) / (3U * ick));
>      > + EDMAC->EDMR = MG_BIT(6); // Initialize, little-endian (27.2.1)
>      > +
>      > + MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
>      > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
>      > + mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
>      > +
>      > + // Select RMII mode,
>      > + ETHERC->ECMR = MG_BIT(2) | MG_BIT(1); // 100M, Full-duplex, CRC
>      > + // ETHERC->ECMR |= MG_BIT(0); // Receive all
>      > + ETHERC->RFLR = 1518; // Set max rx length
>      > +
>      > + EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
>      > + EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
>      > + // MAC address filtering (bytes in reversed order)
>      > + ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
>      > + ((uint32_t) ifp->mac[1] << 16U) |
>      > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
>      > + ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
>      > +
>      > + EDMAC->TFTR = 0; // Store and forward (27.2.10)
>      > + EDMAC->FDR = 0x070f; // (27.2.11)
>      > + EDMAC->RMCR = MG_BIT(0); // (27.2.12)
>      > + ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5); // TE RE
>      > + EDMAC->EESIPR = MG_BIT(18); // Enable Rx IRQ
>      > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
>      > + EDMAC->EDTRR = MG_BIT(0); // Transmit Descriptors have changed
>      > return true;
>      > }
>      >
>      > -static uint32_t s_txno;
>      > -static size_t mip_driver_tm4c_tx(const void *buf, size_t len,
>     struct mip_if *ifp) {
>      > - if (len > sizeof(s_txbuf[s_txno])) {
>      > +// Transmit frame
>      > +static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
>      > + struct mg_tcpip_if *ifp) {
>      > + static int s_txno; // Current descriptor index
>      > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>      > MG_ERROR(("Frame too big, %ld", (long) len));
>      > - len = 0; // fail
>      > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
>      > + len = (size_t) -1; // fail
>      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>      > + ifp->nerr++;
>      > MG_ERROR(("No descriptors available"));
>      > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>      > - // EMAC->EMACDMARIS);
>      > - len = 0; // fail
>      > + len = 0; // retry later
>      > } else {
>      > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>      > - s_txdesc[s_txno][0] =
>      > - BIT(20) | BIT(28) | BIT(29) | BIT(30); // Chain,FS,LS,IC
>      > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno][1] = len << 16; // Set data len
>      > + s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28; // (27.3.1.1) mark
>     valid
>      > + EDMAC->EDTRR = MG_BIT(0); // Transmit request
>      > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > }
>      > - EMAC->EMACDMARIS = BIT(2) | BIT(5); // Clear any prior TU/UNF
>      > - EMAC->EMACTXPOLLD = 0; // and resume
>      > return len;
>      > - (void) ifp;
>      > }
>      >
>      > -static bool mip_driver_tm4c_up(struct mip_if *ifp) {
>      > - uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
>      > - (void) ifp;
>      > - return (bmsr & BIT(2)) ? 1 : 0;
>      > +static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_ra_data *d =
>      > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
>      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + // tmp = reg with flags set to the most likely situation: 100M
>     full-duplex
>      > + // if(link is slow or half) set flags otherwise
>      > + // reg = tmp
>      > + uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1); // 100M
>     Full-duplex
>      > + if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2); // 10M
>      > + if (full_duplex == false) ecmr &= ~MG_BIT(1); // Half-duplex
>      > + ETHERC->ECMR = ecmr; // IRQ handler does not fiddle with these
>     registers
>      > + MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
>      > + ecmr & MG_BIT(1) ? "full" : "half"));
>      > + }
>      > + return up;
>      > }
>      >
>      > -void EMAC0_IRQHandler(void);
>      > +void EDMAC_IRQHandler(void);
>      > static uint32_t s_rxno;
>      > -void EMAC0_IRQHandler(void) {
>      > - qp_mark(QP_IRQTRIGGERED, 0);
>      > - if (EMAC->EMACDMARIS & BIT(6)) { // Frame received, loop
>      > - EMAC->EMACDMARIS = BIT(16) | BIT(6); // Clear flag
>      > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
>      > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
>     BIT(9))) &&
>      > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
>      > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
>      > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>      > - // EMAC->EMACDMARIS);
>      > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > - }
>      > - s_rxdesc[s_rxno][0] = BIT(31);
>      > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > +void EDMAC_IRQHandler(void) {
>      > + struct mg_tcpip_driver_ra_data *d =
>      > + (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
>      > + EDMAC->EESR = MG_BIT(18); // Ack IRQ in EDMAC 1st
>      > + ICU_IELSR[d->irqno] &= ~MG_BIT(16); // Ack IRQ in ICU last
>      > + // Frame received, loop
>      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > + uint32_t r = s_rxdesc[s_rxno][0];
>      > + if (r & MG_BIT(31)) break; // exit when done
>      > + // skip partial/errored frames (27.3.1.2)
>      > + if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
>      > + size_t len = s_rxdesc[s_rxno][1] & 0xffff;
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); // CRC already
>     stripped
>      > }
>      > + s_rxdesc[s_rxno][0] |= MG_BIT(31);
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > }
>      > - EMAC->EMACDMARIS = BIT(7); // Clear possible RU while processing
>      > - EMAC->EMACRXPOLLD = 0; // and resume RX
>      > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
>      > + // If b0 == 0, descriptors were exhausted and probably frames
>     were dropped,
>      > + // (27.2.9 RMFCR counts them)
>      > }
>      >
>      > -struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init,
>     mip_driver_tm4c_tx,
>      > - mip_driver_rx, mip_driver_tm4c_up};
>      > +struct mg_tcpip_driver mg_tcpip_driver_ra =
>     {mg_tcpip_driver_ra_init,
>      > + mg_tcpip_driver_ra_tx, NULL,
>      > + mg_tcpip_driver_ra_up};
>      > +
>      > #endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "mip/driver_w5500.c"
>      > +#line 1 "src/drivers/same54.c"
>      > #endif
>      >
>      >
>      > -#if MG_ENABLE_MIP
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
>     MG_ENABLE_DRIVER_SAME54
>      >
>      > -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
>      > +#include <sam.h>
>      >
>      > -static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t
>     addr, bool wr,
>      > - void *buf, size_t len) {
>      > - uint8_t *p = (uint8_t *) buf;
>      > - uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
>      > - (uint8_t) ((block << 3) | (wr ? 4 : 0))};
>      > - s->begin(s->spi);
>      > - for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
>      > - for (size_t i = 0; i < len; i++) {
>      > - uint8_t r = s->txn(s->spi, p[i]);
>      > - if (!wr) p[i] = r;
>      > - }
>      > - s->end(s->spi);
>      > +#define ETH_PKT_SIZE 1536 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 2 // Descriptor size (words)
>      > +
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > +static uint8_t s_txno; // Current TX descriptor
>      > +static uint8_t s_rxno; // Current RX descriptor
>      > +
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>      > +
>      > +#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
>      > +#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
>      > +#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
>      > +
>      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
>      > + GMAC_MAN_OP(2) | // Setting the read operation
>      > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
>      > + GMAC_MAN_REGA(reg); // Setting the register
>      > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
>      > + return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk; // Getting the
>     read value
>      > }
>      >
>      > -// clang-format off
>      > -static void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t
>     addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf,
>     len); }
>      > -static void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t
>     addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
>      > -static void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t
>     addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8),
>     (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
>      > -static void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t
>     addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf,
>     len); }
>      > -static uint8_t w5500_r1(struct mip_spi *s, uint8_t block,
>     uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
>     return r; }
>      > -static uint16_t w5500_r2(struct mip_spi *s, uint8_t block,
>     uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
>     buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
>      > -// clang-format on
>      > +#if 0
>      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) | //
>     Setting the write operation
>      > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
>      > + GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val); // Setting the register
>      > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting
>     until the write op is complete
>      > +}
>      > +#endif
>      >
>      > -static size_t w5500_rx(void *buf, size_t buflen, struct mip_if
>     *ifp) {
>      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>      > - uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
>     recv len
>      > - while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
>     it is stable
>      > - // printf("RSR: %d\n", (int) n);
>      > - if (n > 0) {
>      > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
>      > - n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
>      > - if (n <= len + 2 && n > 1) {
>      > - r = (uint16_t) (n - 2);
>      > - w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
>      > +int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
>      > + if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
>      > + return d->mdc_cr;
>      > + } else {
>      > + // get MCLK from GCLK_GENERATOR 0
>      > + uint32_t div = 512;
>      > + uint32_t mclk;
>      > + if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
>      > + div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
>      > + if (div == 0) div = 1;
>      > }
>      > - w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
>     read pointer
>      > - w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
>      > - // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
>      > + switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
>      > + case GCLK_GENCTRL_SRC_XOSC0_Val:
>      > + mclk = 32000000UL; /* 32MHz */
>      > + break;
>      > + case GCLK_GENCTRL_SRC_XOSC1_Val:
>      > + mclk = 32000000UL; /* 32MHz */
>      > + break;
>      > + case GCLK_GENCTRL_SRC_OSCULP32K_Val:
>      > + mclk = 32000UL;
>      > + break;
>      > + case GCLK_GENCTRL_SRC_XOSC32K_Val:
>      > + mclk = 32000UL;
>      > + break;
>      > + case GCLK_GENCTRL_SRC_DFLL_Val:
>      > + mclk = 48000000UL; /* 48MHz */
>      > + break;
>      > + case GCLK_GENCTRL_SRC_DPLL0_Val:
>      > + mclk = 200000000UL; /* 200MHz */
>      > + break;
>      > + case GCLK_GENCTRL_SRC_DPLL1_Val:
>      > + mclk = 200000000UL; /* 200MHz */
>      > + break;
>      > + default:
>      > + mclk = 200000000UL; /* 200MHz */
>      > + }
>      > +
>      > + mclk /= div;
>      > + uint8_t crs[] = {0, 1, 2, 3, 4, 5}; // GMAC->NCFGR::CLK values
>      > + uint8_t dividers[] = {8, 16, 32, 48, 64, 96}; // Respective CLK
>     dividers
>      > + for (int i = 0; i < 6; i++) {
>      > + if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > + return crs[i];
>      > + }
>      > + }
>      > +
>      > + return 5;
>      > }
>      > - return r;
>      > }
>      >
>      > -static size_t w5500_tx(const void *buf, size_t buflen, struct
>     mip_if *ifp) {
>      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>      > - uint16_t n = 0, len = (uint16_t) buflen;
>      > - while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
>      > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
>      > - w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
>      > - w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
>     write pointer
>      > - w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
>      > - for (int i = 0; i < 40; i++) {
>      > - uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
>      > - if (ir == 0) continue;
>      > - // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
>     (int) n, ptr);
>      > - w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
>      > - if (ir & 8) len = 0; // Timeout. Report error
>      > - if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
>      > +static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_same54_data *d =
>      > + (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
>      > + s_ifp = ifp;
>      > +
>      > + MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
>      > + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
>      > + GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d)); //
>     Set MDC divider
>      > + GMAC_REGS->GMAC_NCR = 0; // Disable RX & TX
>      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk; // Enable MDC & MDIO
>      > +
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init TX descriptors
>      > + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; // Point to data buffer
>      > + s_txdesc[i][1] = MG_BIT(31); // OWN bit
>      > }
>      > - return len;
>      > + s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30); // Last tx
>     descriptor - wrap
>      > +
>      > + GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18) // DMA recv buf 1536
>      > + | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
>      > + GMAC_DCFGR_TXPBMS(1); // See #2487
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init RX descriptors
>      > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; // Address of the data
>     buffer
>      > + s_rxdesc[i][1] = 0; // Clear status
>      > + }
>      > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1); // Last rx
>     descriptor - wrap
>      > +
>      > + GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc; // about the
>     descriptor addresses
>      > + GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc; // Let the
>     controller know
>      > +
>      > + GMAC_REGS->SA[0].GMAC_SAB =
>      > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
>      > + GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5],
>     ifp->mac[4]);
>      > +
>      > + GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk; // Disable MII, use RMII
>      > + GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk |
>     GMAC_NCFGR_MTIHEN_Msk |
>      > + GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
>      > + GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
>      > + GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
>      > + GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
>      > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
>      > + GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
>      > + GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
>      > + GMAC_REGS->GMAC_IDR = ~0U; // Disable interrupts, then enable
>     required
>      > + GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
>      > + GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
>      > + GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
>      > + GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
>      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
>      > + NVIC_EnableIRQ(GMAC_IRQn);
>      > +
>      > + return true;
>      > }
>      >
>      > -static bool w5500_init(struct mip_if *ifp) {
>      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>      > - s->end(s->spi);
>      > - w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
>      > - w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
>      > - w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
>      > - // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
>      > - w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
>      > - w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
>      > - w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
>      > - w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
>      > - return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
>      > +static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t
>     len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // Frame is too big
>      > + } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No free descriptors"));
>      > + len = 0; // All descriptors are busy, fail
>      > + } else {
>      > + uint32_t status = len | MG_BIT(15); // Frame length, last chunk
>      > + if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30); // wrap
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno][1] = status;
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > + }
>      > + __DSB(); // Ensure descriptors have been written
>      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk; // Enable transmission
>      > + return len;
>      > }
>      >
>      > -static bool w5500_up(struct mip_if *ifp) {
>      > - struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
>      > - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
>      > - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
>      > +static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
>      > + uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
>      > + bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
>      > +
>      > + // If PHY is ready, update NCFGR accordingly
>      > + if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
>      > + uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
>      > + bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
>      > + bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
>      > + GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
>      > + ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
>      > + GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
>      > + }
>      > +
>      > + return up;
>      > +}
>      > +
>      > +void GMAC_Handler(void);
>      > +void GMAC_Handler(void) {
>      > + uint32_t isr = GMAC_REGS->GMAC_ISR;
>      > + uint32_t rsr = GMAC_REGS->GMAC_RSR;
>      > + uint32_t tsr = GMAC_REGS->GMAC_TSR;
>      > + if (isr & GMAC_ISR_RCOMP_Msk) {
>      > + if (rsr & GMAC_ISR_RCOMP_Msk) {
>      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>      > + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
>      > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>      > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // Disown
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > + }
>      > + }
>      > + }
>      > +
>      > + if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
>     GMAC_TSR_TXCOMP_Msk |
>      > + GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
>      > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
>      > + // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
>      > + if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |=
>     MG_BIT(31);
>      > + }
>      > +
>      > + GMAC_REGS->GMAC_RSR = rsr;
>      > + GMAC_REGS->GMAC_TSR = tsr;
>      > }
>      >
>      > -struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx,
>     w5500_rx, w5500_up};
>      > +struct mg_tcpip_driver mg_tcpip_driver_same54 = {
>      > + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
>      > + mg_tcpip_driver_same54_up};
>      > #endif
>      >
>      > #ifdef MG_ENABLE_LINES
>      > -#line 1 "mip/mip.c"
>      > +#line 1 "src/drivers/stm32f.c"
>      > #endif
>      >
>      >
>      > -#if MG_ENABLE_MIP
>      > -
>      > -#define MIP_ETHEMERAL_PORT 49152
>      > -#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
>      > -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
>      > -
>      > -#ifndef MIP_QSIZE
>      > -#define MIP_QSIZE (16 * 1024) // Queue size
>      > -#endif
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
>      > + MG_ENABLE_DRIVER_STM32F
>      > +struct stm32f_eth {
>      > + volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
>     MACMIIDR, MACFCR,
>      > + MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
>     MACDBGR, MACSR,
>      > + MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
>     MACA3HR,
>      > + MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
>      > + RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
>      > + RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
>      > + RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
>     PTPTSLUR,
>      > + PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
>     RESERVED9[564],
>      > + DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
>     DMAIER,
>      > + DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
>      > + DMACHRBAR;
>      > +};
>      > +#undef ETH
>      > +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
>      >
>      > -#ifndef MIP_TCP_KEEPALIVE_MS
>      > -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
>      > -#endif
>      > +#define ETH_PKT_SIZE 1540 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 4 // Descriptor size (words)
>      >
>      > -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
>      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>     ethernet buffers
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>     ethernet buffers
>      > +static uint8_t s_txno; // Current TX descriptor
>      > +static uint8_t s_rxno; // Current RX descriptor
>      >
>      > -struct connstate {
>      > - uint32_t seq, ack; // TCP seq/ack counters
>      > - uint64_t timer; // TCP keep-alive / ACK timer
>      > - uint8_t mac[6]; // Peer MAC address
>      > - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
>      > -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
>     send keepalive
>      > -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
>     soon
>      > - uint8_t tmiss; // Number of keep-alive misses
>      > - struct mg_iobuf raw; // For TLS only. Incoming raw data
>      > -};
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      >
>      > -#pragma pack(push, 1)
>      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > + ETH->MACMIIAR &= (7 << 2);
>      > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
>      > + ETH->MACMIIAR |= MG_BIT(0);
>      > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
>      > + return ETH->MACMIIDR & 0xffff;
>      > +}
>      >
>      > -struct lcp {
>      > - uint8_t addr, ctrl, proto[2], code, id, len[2];
>      > -};
>      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + ETH->MACMIIDR = val;
>      > + ETH->MACMIIAR &= (7 << 2);
>      > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
>     6) | MG_BIT(1);
>      > + ETH->MACMIIAR |= MG_BIT(0);
>      > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
>      > +}
>      >
>      > -struct eth {
>      > - uint8_t dst[6]; // Destination MAC address
>      > - uint8_t src[6]; // Source MAC address
>      > - uint16_t type; // Ethernet type
>      > -};
>      > +static uint32_t get_hclk(void) {
>      > + struct rcc {
>      > + volatile uint32_t CR, PLLCFGR, CFGR;
>      > + } *rcc = (struct rcc *) 0x40023800;
>      > + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
>     8MHz */;
>      >
>      > -struct ip {
>      > - uint8_t ver; // Version
>      > - uint8_t tos; // Unused
>      > - uint16_t len; // Length
>      > - uint16_t id; // Unused
>      > - uint16_t frag; // Fragmentation
>      > - uint8_t ttl; // Time to live
>      > - uint8_t proto; // Upper level protocol
>      > - uint16_t csum; // Checksum
>      > - uint32_t src; // Source IP
>      > - uint32_t dst; // Destination IP
>      > -};
>      > + if (rcc->CFGR & (1 << 2)) {
>      > + clk = hse;
>      > + } else if (rcc->CFGR & (1 << 3)) {
>      > + uint32_t vco, m, n, p;
>      > + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
>      > + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
>      > + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
>      > + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
>      > + vco = (uint32_t) ((uint64_t) clk * n / m);
>      > + clk = vco / p;
>      > + } else {
>      > + clk = hsi;
>      > + }
>      > + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
>      > + if (hpre < 8) return clk;
>      >
>      > -struct ip6 {
>      > - uint8_t ver; // Version
>      > - uint8_t opts[3]; // Options
>      > - uint16_t len; // Length
>      > - uint8_t proto; // Upper level protocol
>      > - uint8_t ttl; // Time to live
>      > - uint8_t src[16]; // Source IP
>      > - uint8_t dst[16]; // Destination IP
>      > -};
>      > + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>      > + return ((uint32_t) clk) >> ahbptab[hpre - 8];
>      > +}
>      >
>      > -struct icmp {
>      > - uint8_t type;
>      > - uint8_t code;
>      > - uint16_t csum;
>      > -};
>      > +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
>     as per 802.3,
>      > +// it must not exceed 2.5MHz As the AHB clock can be (and
>     usually is) derived
>      > +// from the HSI (internal RC), and it can go above specs, the
>     datasheets
>      > +// specify a range of frequencies and activate one of a series
>     of dividers to
>      > +// keep the MDC clock safely below 2.5MHz. We guess a divider
>     setting based on
>      > +// HCLK with a +5% drift. If the user uses a different clock
>     from our
>      > +// defaults, needs to set the macros on top Valid for
>     STM32F74xxx/75xxx
>      > +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
>     drift)
>      > +static int guess_mdc_cr(void) {
>      > + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
>      > + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
>     dividers
>      > + uint32_t hclk = get_hclk(); // Guess system HCLK
>      > + int result = -1; // Invalid CR value
>      > + if (hclk < 25000000) {
>      > + MG_ERROR(("HCLK too low"));
>      > + } else {
>      > + for (int i = 0; i < 6; i++) {
>      > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > + result = crs[i];
>      > + break;
>      > + }
>      > + }
>      > + if (result < 0) MG_ERROR(("HCLK too high"));
>      > + }
>      > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>      > + return result;
>      > +}
>      >
>      > -struct arp {
>      > - uint16_t fmt; // Format of hardware address
>      > - uint16_t pro; // Format of protocol address
>      > - uint8_t hlen; // Length of hardware address
>      > - uint8_t plen; // Length of protocol address
>      > - uint16_t op; // Operation
>      > - uint8_t sha[6]; // Sender hardware address
>      > - uint32_t spa; // Sender protocol address
>      > - uint8_t tha[6]; // Target hardware address
>      > - uint32_t tpa; // Target protocol address
>      > -};
>      > +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_stm32f_data *d =
>      > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
>      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>      > + s_ifp = ifp;
>      >
>      > -struct tcp {
>      > - uint16_t sport; // Source port
>      > - uint16_t dport; // Destination port
>      > - uint32_t seq; // Sequence number
>      > - uint32_t ack; // Acknowledgement number
>      > - uint8_t off; // Data offset
>      > - uint8_t flags; // TCP flags
>      > -#define TH_FIN 0x01
>      > -#define TH_SYN 0x02
>      > -#define TH_RST 0x04
>      > -#define TH_PUSH 0x08
>      > -#define TH_ACK 0x10
>      > -#define TH_URG 0x20
>      > -#define TH_ECE 0x40
>      > -#define TH_CWR 0x80
>      > - uint16_t win; // Window
>      > - uint16_t csum; // Checksum
>      > - uint16_t urp; // Urgent pointer
>      > -};
>      > + // Init RX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_rxdesc[i][0] = MG_BIT(31); // Own
>      > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
>     address chained
>      > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>     data buffer
>      > + s_rxdesc[i][3] =
>      > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + }
>      >
>      > -struct udp {
>      > - uint16_t sport; // Source port
>      > - uint16_t dport; // Destination port
>      > - uint16_t len; // UDP length
>      > - uint16_t csum; // UDP checksum
>      > -};
>      > + // Init TX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>      > + s_txdesc[i][3] =
>      > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + }
>      >
>      > -struct dhcp {
>      > - uint8_t op, htype, hlen, hops;
>      > - uint32_t xid;
>      > - uint16_t secs, flags;
>      > - uint32_t ciaddr, yiaddr, siaddr, giaddr;
>      > - uint8_t hwaddr[208];
>      > - uint32_t magic;
>      > - uint8_t options[32];
>      > -};
>      > + ETH->DMABMR |= MG_BIT(0); // Software reset
>      > + while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until
>     done
>      >
>      > -#pragma pack(pop)
>      > + // Set MDC clock divider. If user told us the value, use it.
>     Otherwise, guess
>      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>     d->mdc_cr;
>      > + ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
>      >
>      > -struct pkt {
>      > - struct mg_str raw; // Raw packet data
>      > - struct mg_str pay; // Payload data
>      > - struct eth *eth;
>      > - struct llc *llc;
>      > - struct arp *arp;
>      > - struct ip *ip;
>      > - struct ip6 *ip6;
>      > - struct icmp *icmp;
>      > - struct tcp *tcp;
>      > - struct udp *udp;
>      > - struct dhcp *dhcp;
>      > -};
>      > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
>     not use
>      > + // hardware checksum. Therefore, descriptor size is 4, not 8
>      > + // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
>     MG_BIT(23) |
>      > + // MG_BIT(25);
>      > + ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
>      > + ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause
>      > + // ETH->MACFFR = MG_BIT(31); // Receive all
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
>      > + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
>      > + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
>      > + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE
>      > + ETH->MACCR =
>      > + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE,
>     Duplex, Fast
>      > + ETH->DMAOMR =
>      > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
>     TSF, RSF
>      >
>      > -static void q_copyin(struct queue *q, const uint8_t *buf, size_t
>     len,
>      > - size_t head) {
>      > - size_t left = q->len - head;
>      > - memcpy(&q->buf[head], buf, left < len ? left : len);
>      > - if (left < len) memcpy(q->buf, &buf[left], len - left);
>      > + // MAC address filtering
>      > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>      > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>      > + ((uint32_t) ifp->mac[2] << 16) |
>      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>      > + return true;
>      > }
>      >
>      > -static void q_copyout(struct queue *q, uint8_t *buf, size_t len,
>     size_t tail) {
>      > - size_t left = q->len - tail;
>      > - memcpy(buf, &q->buf[tail], left < len ? left : len);
>      > - if (left < len) memcpy(&buf[left], q->buf, len - left);
>      > +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t
>     len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // Frame is too big
>      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No free descriptors"));
>      > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>     ETH->DMASR);
>      > + len = 0; // All descriptors are busy, fail
>      > + } else {
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>      > + s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); //
>     Chain,FS,LS
>      > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
>     take over
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > + }
>      > + MG_DSB(); // ensure descriptors have been written
>      > + ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS
>      > + ETH->DMATPDR = 0; // and resume
>      > + return len;
>      > }
>      >
>      > -static bool q_write(struct queue *q, const void *buf, size_t len) {
>      > - bool success = false;
>      > - size_t left = (q->len - q->head + q->tail - 1) % q->len;
>      > - if (len + sizeof(size_t) <= left) {
>      > - q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
>      > - q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) %
>     q->len);
>      > - q->head = (q->head + sizeof(len) + len) % q->len;
>      > - success = true;
>      > +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_stm32f_data *d =
>      > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
>      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + // tmp = reg with flags set to the most likely situation: 100M
>     full-duplex
>      > + // if(link is slow or half) set flags otherwise
>      > + // reg = tmp
>      > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M,
>     Full-duplex
>      > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
>      > + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex
>      > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
>     register
>      > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
>      > + maccr & MG_BIT(11) ? "full" : "half"));
>      > }
>      > - return success;
>      > + return up;
>      > }
>      >
>      > -#ifdef MIP_QPROFILE
>      > -static inline size_t q_space(struct queue *q) {
>      > - return q->tail > q->head ? q->tail - q->head : q->tail +
>     (q->len - q->head);
>      > +#ifdef __riscv
>      > +__attribute__((interrupt())) // For RISCV CH32V307, which share
>     the same MAC
>      > +#endif
>      > +void ETH_IRQHandler(void);
>      > +void ETH_IRQHandler(void) {
>      > + if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop
>      > + ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag
>      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
>      > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
>      > + (MG_BIT(8) | MG_BIT(9))) &&
>      > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
>     frames
>      > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
>      > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>      > + // ETH->DMASR);
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > + }
>      > + s_rxdesc[s_rxno][0] = MG_BIT(31);
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > + }
>      > + }
>      > + // Cleanup flags
>      > + ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary
>      > + | MG_BIT(7); // Clear possible RBUS while processing
>      > + ETH->DMARPDR = 0; // and resume RX
>      > }
>      > +
>      > +struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
>      > + mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
>      > + mg_tcpip_driver_stm32f_up};
>      > #endif
>      >
>      > -static inline size_t q_avail(struct queue *q) {
>      > - size_t n = 0;
>      > - if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n),
>     q->tail);
>      > - return n;
>      > -}
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/stm32h.c"
>      > +#endif
>      >
>      > -static size_t q_read(struct queue *q, void *buf) {
>      > - size_t n = q_avail(q);
>      > - if (n > 0) {
>      > - q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
>      > - q->tail = (q->tail + sizeof(n) + n) % q->len;
>      > - }
>      > - return n;
>      > -}
>      >
>      > -static struct mg_str mkstr(void *buf, size_t len) {
>      > - struct mg_str str = {(char *) buf, len};
>      > - return str;
>      > -}
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
>      > + MG_ENABLE_DRIVER_STM32H
>      > +struct stm32h_eth {
>      > + volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
>      > + RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR,
>     MACIVIR,
>      > + RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7],
>     MACISR,
>      > + MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2],
>     MACLCSR,
>      > + MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR,
>     RESERVED10,
>      > + MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR,
>     MACMDIODR,
>      > + RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR,
>     MACA1HR,
>      > + MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248],
>     MMCCR,
>      > + MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR,
>     MMCTMCGPR,
>      > + RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
>      > + RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
>      > + MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
>      > + RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
>      > + RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
>      > + MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR,
>     MACSSIR,
>      > + MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25,
>     MACTSSR,
>      > + RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR,
>     RESERVED28,
>      > + MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
>      > + RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR,
>     MACPPSIR,
>      > + MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R,
>     MACSPI2R, MACLMIR,
>      > + RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55],
>     MTLTQOMR,
>      > + MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR,
>     MTLRQMPOCR, MTLRQDR,
>      > + RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR,
>     RESERVED37[60], DMACCR,
>      > + DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
>      > + DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
>      > + DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42,
>     DMACCARDR,
>      > + RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR,
>     RESERVED45[2],
>      > + DMACMFCR;
>      > +};
>      > +#undef ETH
>      > +#define ETH \
>      > + ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL
>     + 0x8000UL))
>      >
>      > -static void mkpay(struct pkt *pkt, void *p) {
>      > - pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] -
>     (char *) p));
>      > +#define ETH_PKT_SIZE 1540 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 4 // Descriptor size (words)
>      > +
>      > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX
>     descriptors
>      > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX
>     descriptors
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>     ethernet buffers
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>     ethernet buffers
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +
>      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > + ETH->MACMDIOAR &= (0xF << 8);
>      > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
>     16) | 3 << 2;
>      > + ETH->MACMDIOAR |= MG_BIT(0);
>      > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
>      > + return (uint16_t) ETH->MACMDIODR;
>      > }
>      >
>      > -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
>      > - const uint8_t *p = (const uint8_t *) buf;
>      > - for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] :
>     (uint32_t) (p[i] << 8);
>      > - return sum;
>      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + ETH->MACMDIODR = val;
>      > + ETH->MACMDIOAR &= (0xF << 8);
>      > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
>     16) | 1 << 2;
>      > + ETH->MACMDIOAR |= MG_BIT(0);
>      > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
>      > }
>      >
>      > -static uint16_t csumfin(uint32_t sum) {
>      > - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
>      > - return mg_htons(~sum & 0xffff);
>      > +static uint32_t get_hclk(void) {
>      > + struct rcc {
>      > + volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1,
>     D1CFGR,
>      > + D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR,
>     PLL1FRACR,
>      > + PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR,
>     D2CCIP1R,
>      > + D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5,
>     BDCR, CSR,
>      > + RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR,
>     APB1LRSTR,
>      > + APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR,
>     RESERVED11[9],
>      > + RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR,
>     APB1HENR,
>      > + APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
>      > + AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR,
>     APB4LPENR,
>      > + RESERVED13[4];
>      > + } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
>      > + uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /*
>     8MHz */,
>      > + csi = 4000000 /* 4MHz */;
>      > + unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
>      > +
>      > + if (sel == 1) {
>      > + clk = csi;
>      > + } else if (sel == 2) {
>      > + clk = hse;
>      > + } else if (sel == 3) {
>      > + uint32_t vco, m, n, p;
>      > + unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
>      > + m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
>      > + n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
>      > + ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional
>     mode
>      > + p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
>      > + if (src == 1) {
>      > + clk = csi;
>      > + } else if (src == 2) {
>      > + clk = hse;
>      > + } else {
>      > + clk = hsi;
>      > + clk >>= ((rcc->CR & 3) >> 3);
>      > + }
>      > + vco = (uint32_t) ((uint64_t) clk * n / m);
>      > + clk = vco / p;
>      > + } else {
>      > + clk = hsi;
>      > + clk >>= ((rcc->CR & 3) >> 3);
>      > + }
>      > + const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>      > + uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
>      > + if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
>      > + MG_DEBUG(("D1 CLK: %u", clk));
>      > + uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
>      > + if (hpre < 8) return clk;
>      > + return ((uint32_t) clk) >> cptab[hpre - 8];
>      > +}
>      > +
>      > +// Guess CR from AHB1 clock. MDC clock is generated from the ETH
>     peripheral
>      > +// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB
>     clock can
>      > +// be derived from HSI or CSI (internal RC) clocks, and those
>     can go above
>      > +// specs, the datasheets specify a range of frequencies and
>     activate one of a
>      > +// series of dividers to keep the MDC clock safely below 2.5MHz.
>     We guess a
>      > +// divider setting based on HCLK with some drift. If the user
>     uses a different
>      > +// clock from our defaults, needs to set the macros on top.
>     Valid for
>      > +// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock
>     has a 7.5 %
>      > +// worst case drift @ max temp)
>      > +static int guess_mdc_cr(void) {
>      > + const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR
>     values
>      > + const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective
>     HCLK dividers
>      > + uint32_t hclk = get_hclk(); // Guess system HCLK
>      > + int result = -1; // Invalid CR value
>      > + for (int i = 0; i < 6; i++) {
>      > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > + result = crs[i];
>      > + break;
>      > + }
>      > + }
>      > + if (result < 0) MG_ERROR(("HCLK too high"));
>      > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>      > + return result;
>      > }
>      >
>      > -static uint16_t ipcsum(const void *buf, size_t len) {
>      > - uint32_t sum = csumup(0, buf, len);
>      > - return csumfin(sum);
>      > -}
>      > +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_stm32h_data *d =
>      > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
>      > + s_ifp = ifp;
>      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>      > + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
>      >
>      > -// ARP cache is organised as a doubly linked list. A successful
>     cache lookup
>      > -// moves an entry to the head of the list. New entries are added
>     by replacing
>      > -// the last entry in the list with a new IP/MAC.
>      > -// ARP cache format: | prev | next | Entry0 | Entry1 | .... |
>     EntryN |
>      > -// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
>      > -// prev and next are 1-byte offsets in the cache, so cache size
>     is max 256 bytes
>      > -// ARP entry size is 12 bytes
>      > -static void arp_cache_init(uint8_t *p, int n, int size) {
>      > - for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i
>     - 1) * size);
>      > - for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i
>     + 1) * size);
>      > - p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
>      > - p[1] = p[3 + (n - 1) * size] = 2;
>      > -}
>      > + // Init RX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>     data buffer
>      > + s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN,
>     IOC, BUF1V
>      > + }
>      >
>      > -#if 0
>      > -static inline void arp_cache_dump(const uint8_t *p) {
>      > - MG_INFO(("ARP cache:"));
>      > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
>     + 1]) {
>      > - MG_INFO((" %I -> %A", 4, &p[j + 2], &p[j + 6]));
>      > + // Init TX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>      > }
>      > -}
>      > -#endif
>      >
>      > -static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF,
>     0xFF, 0xFF};
>      > + ETH->DMAMR |= MG_BIT(0); // Software reset
>      > + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done
>      >
>      > -static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
>      > - uint8_t *p = ifp->arp_cache;
>      > - if (ip == 0) return NULL;
>      > - // use broadcast MAC for local and global broadcast IP
>      > - if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
>      > - return (uint8_t *) bcastmac;
>      > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
>     + 1]) {
>      > - if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
>      > - p[1] = j, p[0] = p[j]; // Found entry! Point list head to us
>      > - // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
>      > - return p + j + 6; // And return MAC address
>      > - }
>      > - }
>      > - return NULL;
>      > -}
>      > + // Set MDC clock divider. If user told us the value, use it.
>     Otherwise, guess
>      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>     d->mdc_cr;
>      > + ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
>      > +
>      > + // NOTE(scaprile): We do not use timing facilities so the DMA
>     engine does not
>      > + // re-write buffer address
>      > + ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset
>     value)
>      > + ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this
>     actually needed
>      > + ETH->MACIER = 0; // Do not enable additional irq sources (reset
>     value)
>      > + ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause
>      > + // ETH->MACPFR = MG_BIT(31); // Receive all
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + mg_phy_init(&phy, phy_addr, phy_conf);
>      > + ETH->DMACRDLAR =
>      > + (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address
>      > + ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length
>      > + ETH->DMACRDTPR =
>      > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
>      > + 1]; // last valid descriptor address
>      > + ETH->DMACTDLAR =
>      > + (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address
>      > + ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length
>      > + ETH->DMACTDTPR =
>      > + (uint32_t) (uintptr_t) s_txdesc; // first available descriptor
>     address
>      > + ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table)
>     (reset value)
>      > + ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE
>      > + ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
>      > + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved
>      > + ETH->MTLTQOMR |= MG_BIT(1); // TSF
>      > + ETH->MTLRQOMR |= MG_BIT(5); // RSF
>      > + ETH->DMACTCR |= MG_BIT(0); // ST
>      > + ETH->DMACRCR |= MG_BIT(0); // SR
>      >
>      > -static void arp_cache_add(struct mip_if *ifp, uint32_t ip,
>     uint8_t mac[6]) {
>      > - uint8_t *p = ifp->arp_cache;
>      > - if (ip == 0 || ip == ~0U) return; // Bad IP
>      > - if (arp_cache_find(ifp, ip) != NULL) return; // Already exists,
>     do nothing
>      > - memcpy(p + p[0] + 2, &ip, sizeof(ip)); // Replace last entry:
>     IP address
>      > - memcpy(p + p[0] + 6, mac, 6); // And MAC address
>      > - p[1] = p[0], p[0] = p[p[1]]; // Point list head to us
>      > - MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
>      > + // MAC address filtering
>      > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>      > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>      > + ((uint32_t) ifp->mac[2] << 16) |
>      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>      > + return true;
>      > }
>      >
>      > -static size_t ether_output(struct mip_if *ifp, size_t len) {
>      > - // size_t min = 64; // Pad short frames to 64 bytes (minimum
>     Ethernet size)
>      > - // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len
>     = min;
>      > - // mg_hexdump(ifp->tx.ptr, len);
>      > - return ifp->driver->tx(ifp->tx.ptr, len, ifp);
>      > +static uint32_t s_txno;
>      > +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t
>     len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // Frame is too big
>      > + } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
>      > + s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X",
>     s_txdesc[i][3]));
>      > + len = 0; // All descriptors are busy, fail
>      > + } else {
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno][2] = (uint32_t) len; // Set data len
>      > + s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD
>      > + s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA
>     take over
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > + }
>      > + ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS
>      > + ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; //
>     and resume
>      > + return len;
>      > + (void) ifp;
>      > }
>      >
>      > -static void arp_ask(struct mip_if *ifp, uint32_t ip) {
>      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>      > - struct arp *arp = (struct arp *) (eth + 1);
>      > - memset(eth->dst, 255, sizeof(eth->dst));
>      > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
>      > - eth->type = mg_htons(0x806);
>      > - memset(arp, 0, sizeof(*arp));
>      > - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
>      > - arp->plen = 4;
>      > - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
>      > - memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
>      > - ether_output(ifp, PDIFF(eth, arp + 1));
>      > +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_stm32h_data *d =
>      > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
>      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + // tmp = reg with flags set to the most likely situation: 100M
>     full-duplex
>      > + // if(link is slow or half) set flags otherwise
>      > + // reg = tmp
>      > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M,
>     Full-duplex
>      > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
>      > + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex
>      > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
>     register
>      > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
>      > + maccr & MG_BIT(13) ? "full" : "half"));
>      > + }
>      > + return up;
>      > }
>      >
>      > -static void onstatechange(struct mip_if *ifp) {
>      > - if (ifp->state == MIP_STATE_READY) {
>      > - MG_INFO(("READY, IP: %I", 4, &ifp->ip));
>      > - MG_INFO((" GW: %I", 4, &ifp->gw));
>      > - if (ifp->lease_expire > ifp->now) {
>      > - MG_INFO(
>      > - (" Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
>      > +void ETH_IRQHandler(void);
>      > +static uint32_t s_rxno;
>      > +void ETH_IRQHandler(void) {
>      > + if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop
>      > + ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag
>      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > + if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done
>      > + if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
>      > + (MG_BIT(28) | MG_BIT(29))) &&
>      > + !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored
>     frames
>      > + uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
>      > + // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len,
>     s_rxdesc[s_rxno][3],
>      > + // ETH->DMACSR));
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > + }
>      > + s_rxdesc[s_rxno][3] =
>      > + MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > }
>      > - arp_ask(ifp, ifp->gw);
>      > - } else if (ifp->state == MIP_STATE_UP) {
>      > - MG_ERROR(("Link up"));
>      > - } else if (ifp->state == MIP_STATE_DOWN) {
>      > - MG_ERROR(("Link down"));
>      > }
>      > + ETH->DMACSR =
>      > + MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
>      > + ETH->DMACRDTPR =
>      > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and
>     resume RX
>      > }
>      >
>      > -static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto,
>     uint32_t ip_src,
>      > - uint32_t ip_dst, size_t plen) {
>      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>      > - struct ip *ip = (struct ip *) (eth + 1);
>      > - uint8_t *mac = arp_cache_find(ifp, ip_dst); // Dst IP in ARP
>     cache ?
>      > - if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
>      > - arp_ask(ifp, ip_dst); // Same net, lookup
>      > - if (!mac) mac = arp_cache_find(ifp, ifp->gw); // Use gateway MAC
>      > - if (!mac) arp_ask(ifp, ifp->gw); // Not found? lookup
>      > - if (mac) memcpy(eth->dst, mac, sizeof(eth->dst)); // Found? Use it
>      > - if (!mac) memset(eth->dst, 255, sizeof(eth->dst)); // No? Use
>     broadcast
>      > - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // TODO(cpq): ARP
>     lookup
>      > - eth->type = mg_htons(0x800);
>      > - memset(ip, 0, sizeof(*ip));
>      > - ip->ver = 0x45; // Version 4, header length 5 words
>      > - ip->frag = 0x40; // Don't fragment
>      > - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
>      > - ip->ttl = 64;
>      > - ip->proto = proto;
>      > - ip->src = ip_src;
>      > - ip->dst = ip_dst;
>      > - ip->csum = ipcsum(ip, sizeof(*ip));
>      > - return ip;
>      > -}
>      > +struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
>      > + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
>      > + mg_tcpip_driver_stm32h_up};
>      > +#endif
>      >
>      > -static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t
>     sport,
>      > - uint32_t ip_dst, uint16_t dport, const void *buf,
>      > - size_t len) {
>      > - struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len +
>     sizeof(struct udp));
>      > - struct udp *udp = (struct udp *) (ip + 1);
>      > - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
>      > - udp->sport = sport;
>      > - udp->dport = dport;
>      > - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
>      > - udp->csum = 0;
>      > - uint32_t cs = csumup(0, udp, sizeof(*udp));
>      > - cs = csumup(cs, buf, len);
>      > - cs = csumup(cs, &ip->src, sizeof(ip->src));
>      > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>      > - cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
>      > - udp->csum = csumfin(cs);
>      > - memmove(udp + 1, buf, len);
>      > - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
>      > - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
>     sizeof(*udp) + len);
>      > -}
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/tm4c.c"
>      > +#endif
>      >
>      > -static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
>      > - uint8_t *opts, size_t optslen) {
>      > - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>      > - dhcp.magic = mg_htonl(0x63825363);
>      > - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
>      > - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
>      > - memcpy(&dhcp.options, opts, optslen);
>      > - tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp,
>     sizeof(dhcp));
>      > -}
>      >
>      > -static void tx_dhcp_request(struct mip_if *ifp, uint32_t src,
>     uint32_t dst) {
>      > - uint8_t opts[] = {
>      > - 53, 1, 3, // Type: DHCP request
>      > - 55, 2, 1, 3, // GW and mask
>      > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
>      > - 54, 4, 0, 0, 0, 0, // DHCP server ID
>      > - 50, 4, 0, 0, 0, 0, // Requested IP
>      > - 255 // End of options
>      > - };
>      > - memcpy(opts + 14, &dst, sizeof(dst));
>      > - memcpy(opts + 20, &src, sizeof(src));
>      > - tx_dhcp(ifp, src, dst, opts, sizeof(opts));
>      > -}
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>     MG_ENABLE_DRIVER_TM4C
>      > +struct tm4c_emac {
>      > + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
>     EMACHASHTBLL,
>      > + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
>     EMACSTATUS,
>      > + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
>     EMACADDR0H,
>      > + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
>     EMACADDR3H,
>      > + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
>      > + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
>      > + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
>     RESERVED6[4],
>      > + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
>     EMACRXCNTCRCERR,
>      > + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
>      > + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
>     EMACSUBSECINC,
>      > + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
>      > + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
>      > + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
>      > + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
>     EMACTXDLADDR,
>      > + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
>      > + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
>     EMACHOSRXBA,
>      > + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
>      > + EMACEPHYIM, EMACEPHYIMSC;
>      > +};
>      > +#undef EMAC
>      > +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>      >
>      > -static void tx_dhcp_discover(struct mip_if *ifp) {
>      > - uint8_t opts[] = {
>      > - 53, 1, 1, // Type: DHCP discover
>      > - 55, 2, 1, 3, // Parameters: ip, mask
>      > - 255 // End of options
>      > - };
>      > - tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
>      > - MG_DEBUG(("DHCP discover sent"));
>      > -}
>      > +#define ETH_PKT_SIZE 1540 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 4 // Descriptor size (words)
>      >
>      > -static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
>      > - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
>      > - // ARP request. Make a response, then send
>      > - MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4,
>     &pkt->arp->spa, 4,
>      > - &pkt->arp->tpa));
>      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>      > - struct arp *arp = (struct arp *) (eth + 1);
>      > - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
>      > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
>      > - eth->type = mg_htons(0x806);
>      > - *arp = *pkt->arp;
>      > - arp->op = mg_htons(2);
>      > - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
>      > - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
>      > - arp->tpa = pkt->arp->spa;
>      > - arp->spa = ifp->ip;
>      > - MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
>      > - ether_output(ifp, PDIFF(eth, arp + 1));
>      > - } else if (pkt->arp->op == mg_htons(2)) {
>      > - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
>     0) return;
>      > - // MG_INFO(("ARP RESPONSE"));
>      > - arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
>      > - }
>      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>     ethernet buffers
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>     ethernet buffers
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +enum {
>      > + EPHY_ADDR = 0,
>      > + EPHYBMCR = 0,
>      > + EPHYBMSR = 1,
>      > + EPHYSTS = 16
>      > +}; // PHY constants
>      > +
>      > +static inline void tm4cspin(volatile uint32_t count) {
>      > + while (count--) (void) 0;
>      > }
>      >
>      > -static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
>      > - // MG_DEBUG(("ICMP %d", (int) len));
>      > - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
>     ifp->ip) {
>      > - size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
>     sizeof(struct icmp);
>      > - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
>      > - if (plen > space) plen = space;
>      > - struct ip *ip =
>      > - tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
>      > - struct icmp *icmp = (struct icmp *) (ip + 1);
>      > - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
>      > - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX
>      > - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
>      > - ether_output(ifp, hlen + plen);
>      > - }
>      > +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
>      > + EMAC->EMACMIIADDR &= (0xf << 2);
>      > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>     << 6);
>      > + EMAC->EMACMIIADDR |= MG_BIT(0);
>      > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
>      > + return EMAC->EMACMIIDATA;
>      > }
>      >
>      > -static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
>      > - uint32_t ip = 0, gw = 0, mask = 0;
>      > - uint8_t *p = pkt->dhcp->options,
>      > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
>      > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>      > - while (p + 1 < end && p[0] != 255) { // Parse options
>      > - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
>     Mask
>      > - memcpy(&mask, p + 2, sizeof(mask));
>      > - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
>     { // GW
>      > - memcpy(&gw, p + 2, sizeof(gw));
>      > - ip = pkt->dhcp->yiaddr;
>      > - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
>      > - uint32_t lease = 0;
>      > - memcpy(&lease, p + 2, sizeof(lease));
>      > - ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
>      > - }
>      > - p += p[1] + 2;
>      > - }
>      > - if (ip && mask && gw && ifp->ip == 0) {
>      > - arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *)
>     pkt->raw.ptr)->src);
>      > - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
>      > - ifp->state = MIP_STATE_READY;
>      > - onstatechange(ifp);
>      > - tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
>      > - }
>      > +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
>     val) {
>      > + EMAC->EMACMIIDATA = val;
>      > + EMAC->EMACMIIADDR &= (0xf << 2);
>      > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>     << 6) | MG_BIT(1);
>      > + EMAC->EMACMIIADDR |= MG_BIT(0);
>      > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
>      > }
>      >
>      > -// Simple DHCP server that assigns a next IP address: ifp->ip + 1
>      > -static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
>      > - uint8_t op = 0, *p = pkt->dhcp->options,
>      > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
>      > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>      > - // struct dhcp *req = pkt->dhcp;
>      > - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>      > - res.yiaddr = ifp->ip;
>      > - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
>      > - while (p + 1 < end && p[0] != 255) { // Parse options
>      > - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
>      > - op = p[2];
>      > +static uint32_t get_sysclk(void) {
>      > + struct sysctl {
>      > + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
>     PLLFREQ0,
>      > + PLLFREQ1;
>      > + } *sysctl = (struct sysctl *) 0x400FE000;
>      > + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
>     25000000 /* 25MHz */;
>      > + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
>      > + uint32_t fin, vco, mdiv, n, q, psysdiv;
>      > + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
>      > + if (pllsrc == 0) {
>      > + clk = piosc;
>      > + } else if (pllsrc == 3) {
>      > + clk = mosc;
>      > + } else {
>      > + MG_ERROR(("Unsupported clock source"));
>      > }
>      > - p += p[1] + 2;
>      > - }
>      > - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
>      > - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
>     DHCP ACK
>      > - uint8_t opts[] = {
>      > - 53, 1, msg, // Message type
>      > - 1, 4, 0, 0, 0, 0, // Subnet mask
>      > - 54, 4, 0, 0, 0, 0, // Server ID
>      > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
>      > - 51, 4, 255, 255, 255, 255, // Lease time
>      > - 255 // End of options
>      > - };
>      > - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
>      > - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
>      > - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
>      > - memcpy(&res.options, opts, sizeof(opts));
>      > - res.magic = pkt->dhcp->magic;
>      > - res.xid = pkt->dhcp->xid;
>      > - arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
>      > - tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
>     mg_htons(68),
>      > - &res, sizeof(res));
>      > + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
>      > + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
>      > + fin = clk / ((q + 1) * (n + 1));
>      > + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
>      > + 0; // mint + (mfrac / 1024); MFRAC not supported
>      > + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
>      > + vco = (uint32_t) ((uint64_t) fin * mdiv);
>      > + return vco / (psysdiv + 1);
>      > }
>      > -}
>      > -
>      > -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
>     pkt *pkt,
>      > - bool lsn) {
>      > - struct mg_connection *c = NULL;
>      > - for (c = mgr->conns; c != NULL; c = c->next) {
>      > - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
>     break;
>      > - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
>      > - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
>      > - break;
>      > + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
>      > + if (oscsrc == 0) {
>      > + clk = piosc;
>      > + } else if (oscsrc == 3) {
>      > + clk = mosc;
>      > + } else {
>      > + MG_ERROR(("Unsupported clock source"));
>      > }
>      > - return c;
>      > + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
>      > + return clk / (osysdiv + 1);
>      > }
>      >
>      > -static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
>      > - struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
>      > - if (c == NULL) {
>      > - // No UDP listener on this port. Should send ICMP, but keep
>     silent.
>      > - } else if (c != NULL) {
>      > - c->rem.port = pkt->udp->sport;
>      > - c->rem.ip = pkt->ip->src;
>      > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
>      > - mg_error(c, "max_recv_buf_size reached");
>      > - } else if (c->recv.size - c->recv.len < pkt->pay.len &&
>      > - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
>      > - mg_error(c, "oom");
>      > - } else {
>      > - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
>      > - c->recv.len += pkt->pay.len;
>      > - mg_call(c, MG_EV_READ, &pkt->pay.len);
>      > +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
>     (AHB); as per
>      > +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
>     clock can be
>      > +// derived from the PIOSC (internal RC), and it can go above
>     specs, the
>      > +// datasheets specify a range of frequencies and activate one of
>     a series of
>      > +// dividers to keep the MDC clock safely below 2.5MHz. We guess
>     a divider
>      > +// setting based on SYSCLK with a +5% drift. If the user uses a
>     different clock
>      > +// from our defaults, needs to set the macros on top Valid for
>     TM4C129x (20.7)
>      > +// (4.5% worst case drift)
>      > +// The PHY receives the main oscillator (MOSC) (20.3.1)
>      > +static int guess_mdc_cr(void) {
>      > + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
>      > + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
>      > + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
>      > + int result = -1; // Invalid CR value
>      > + if (sysclk < 25000000) {
>      > + MG_ERROR(("SYSCLK too low"));
>      > + } else {
>      > + for (int i = 0; i < 4; i++) {
>      > + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>      > + result = crs[i];
>      > + break;
>      > + }
>      > }
>      > + if (result < 0) MG_ERROR(("SYSCLK too high"));
>      > }
>      > + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
>      > + return result;
>      > }
>      >
>      > -static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip,
>     uint8_t flags,
>      > - uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
>      > - const void *buf, size_t len) {
>      > - struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct
>     tcp) + len);
>      > - struct tcp *tcp = (struct tcp *) (ip + 1);
>      > - memset(tcp, 0, sizeof(*tcp));
>      > - if (buf != NULL && len) memmove(tcp + 1, buf, len);
>      > - tcp->sport = sport;
>      > - tcp->dport = dport;
>      > - tcp->seq = seq;
>      > - tcp->ack = ack;
>      > - tcp->flags = flags;
>      > - tcp->win = mg_htons(8192);
>      > - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
>      > - uint32_t cs = 0;
>      > - uint16_t n = (uint16_t) (sizeof(*tcp) + len);
>      > - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
>     (n & 255)};
>      > - cs = csumup(cs, tcp, n);
>      > - cs = csumup(cs, &ip->src, sizeof(ip->src));
>      > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>      > - cs = csumup(cs, pseudo, sizeof(pseudo));
>      > - tcp->csum = csumfin(cs);
>      > - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
>      > -}
>      > +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_tm4c_data *d =
>      > + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
>      > + s_ifp = ifp;
>      >
>      > -static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt,
>     uint8_t flags,
>      > - uint32_t seq, const void *buf, size_t len) {
>      > - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
>      > - return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport,
>     pkt->tcp->sport, seq,
>      > - mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
>      > -}
>      > + // Init RX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_rxdesc[i][0] = MG_BIT(31); // Own
>      > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
>     address chained
>      > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>     data buffer
>      > + s_rxdesc[i][3] =
>      > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
>      > + }
>      >
>      > -static void settmout(struct mg_connection *c, uint8_t type) {
>      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS :
>     MIP_TCP_KEEPALIVE_MS;
>      > - s->timer = ifp->now + n;
>      > - s->ttype = type;
>      > - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
>      > -}
>      > + // Init TX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>      > + s_txdesc[i][3] =
>      > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>      > + }
>      >
>      > -static struct mg_connection *accept_conn(struct mg_connection *lsn,
>      > - struct pkt *pkt) {
>      > - struct mg_connection *c = mg_alloc_conn(lsn->mgr);
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>     mg_ntohl(pkt->tcp->seq);
>      > - settmout(c, MIP_TTYPE_KEEPALIVE);
>      > - c->rem.ip = pkt->ip->src;
>      > - c->rem.port = pkt->tcp->sport;
>      > - MG_DEBUG(
>      > - ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip,
>     mg_ntohs(c->rem.port)));
>      > - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
>      > - c->is_accepted = 1;
>      > - c->is_hexdumping = lsn->is_hexdumping;
>      > - c->pfn = lsn->pfn;
>      > - c->loc = lsn->loc;
>      > - c->pfn_data = lsn->pfn_data;
>      > - c->fn = lsn->fn;
>      > - c->fn_data = lsn->fn_data;
>      > - mg_call(c, MG_EV_OPEN, NULL);
>      > - mg_call(c, MG_EV_ACCEPT, NULL);
>      > - return c;
>      > + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset
>      > + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); //
>     Wait until done
>      > +
>      > + // Set MDC clock divider. If user told us the value, use it.
>     Otherwise, guess
>      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>     d->mdc_cr;
>      > + EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
>      > +
>      > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
>     not use
>      > + // hardware checksum. Therefore, descriptor size is 4, not 8
>      > + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
>     MG_BIT(23) | MG_BIT(25);
>      > + EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
>      > + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause
>      > + // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all
>      > + // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
>      > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset
>     internal PHY (EPHY)
>      > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set
>     autonegotiation
>      > + EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
>     descriptors
>      > + EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
>     descriptors
>      > + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE
>      > + EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) |
>     MG_BIT(14); // RE, TE, Duplex, Fast
>      > + EMAC->EMACDMAOPMODE =
>      > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
>     TSF, RSF
>      > + EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>      > + EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
>      > + ((uint32_t) ifp->mac[2] << 16) |
>      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>      > + // NOTE(scaprile) There are 3 additional slots for filtering,
>     disabled by
>      > + // default. This also applies to the STM32 driver (at least for
>     F7)
>      > + return true;
>      > }
>      >
>      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
>      > - if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len -
>     max_headers_len;
>      > - if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port,
>     c->rem.port,
>      > - mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
>      > - s->seq += (uint32_t) len;
>      > - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
>      > +static uint32_t s_txno;
>      > +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // fail
>      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No descriptors available"));
>      > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>      > + // EMAC->EMACDMARIS);
>      > + len = 0; // fail
>      > } else {
>      > - return MG_IO_ERR;
>      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>      > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>      > + s_txdesc[s_txno][0] =
>      > + MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); //
>     Chain,FS,LS,IC
>      > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
>     take over
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > }
>      > - return (long) len;
>      > + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior
>     TU/UNF
>      > + EMAC->EMACTXPOLLD = 0; // and resume
>      > + return len;
>      > + (void) ifp;
>      > }
>      >
>      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - if (s->raw.len == 0) return MG_IO_WAIT;
>      > - if (len > s->raw.len) len = s->raw.len;
>      > - memcpy(buf, s->raw.buf, len);
>      > - mg_iobuf_del(&s->raw, 0, len);
>      > - MG_DEBUG(("%lu", len));
>      > - return (long) len;
>      > +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
>      > + uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
>      > + bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
>      > + // tmp = reg with flags set to the most likely situation: 100M
>     full-duplex
>      > + // if(link is slow or half) set flags otherwise
>      > + // reg = tmp
>      > + uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); //
>     100M, Full-duplex
>      > + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M
>      > + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex
>      > + EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with
>     this register
>      > + MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 :
>     10,
>      > + emaccfg & MG_BIT(11) ? "full" : "half"));
>      > + }
>      > + return up;
>      > }
>      >
>      > -static void read_conn(struct mg_connection *c, struct pkt *pkt) {
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
>      > - uint32_t seq = mg_ntohl(pkt->tcp->seq);
>      > - s->raw.align = c->recv.align;
>      > - if (pkt->tcp->flags & TH_FIN) {
>      > - s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq =
>     mg_htonl(pkt->tcp->ack);
>      > - c->is_closing = 1;
>      > - } else if (pkt->pay.len == 0) {
>      > - // TODO(cpq): handle this peer's ACK
>      > - } else if (seq != s->ack) {
>      > - // TODO(cpq): peer sent us SEQ which we don't expect.
>     Retransmit rather
>      > - // than close this connection
>      > - mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
>      > - } else if (io->size - io->len < pkt->pay.len &&
>      > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>      > - mg_error(c, "oom");
>      > - } else {
>      > - // Copy TCP payload into the IO buffer. If the connection is
>     plain text, we
>      > - // copy to c->recv. If the connection is TLS, this data is
>     encrypted,
>      > - // therefore we copy that encrypted data to the s->raw iobuffer
>     instead,
>      > - // and then call mg_tls_recv() to decrypt it. NOTE:
>     mg_tls_recv() will
>      > - // call back mg_io_recv() which grabs raw data from s->raw
>      > - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
>      > - io->len += pkt->pay.len;
>      > -
>      > - MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
>     s->ack));
>      > - // Advance ACK counter
>      > - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
>      > -#if 0
>      > - // Send ACK immediately
>      > - MG_DEBUG((" imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
>      > - tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK,
>     c->loc.port,
>      > - c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
>      > -#else
>      > - // if not already running, setup a timer to send an ACK later
>      > - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
>      > -#endif
>      > -
>      > - if (c->is_tls) {
>      > - // TLS connection. Make room for decrypted data in c->recv
>      > - io = &c->recv;
>      > - if (io->size - io->len < pkt->pay.len &&
>      > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>      > - mg_error(c, "oom");
>      > - } else {
>      > - // Decrypt data directly into c->recv
>      > - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
>      > - if (n == MG_IO_ERR) {
>      > - mg_error(c, "TLS recv error");
>      > - } else if (n > 0) {
>      > - // Decrypted successfully - trigger MG_EV_READ
>      > - io->len += (size_t) n;
>      > - mg_call(c, MG_EV_READ, &n);
>      > - }
>      > +void EMAC0_IRQHandler(void);
>      > +static uint32_t s_rxno;
>      > +void EMAC0_IRQHandler(void) {
>      > + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop
>      > + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag
>      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>     not forever
>      > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
>      > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
>     (MG_BIT(8) | MG_BIT(9))) &&
>      > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
>     frames
>      > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
>      > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>      > + // EMAC->EMACDMARIS);
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>      > }
>      > - } else {
>      > - // Plain text connection, data is already in c->recv, trigger
>     MG_EV_READ
>      > - mg_call(c, MG_EV_READ, &pkt->pay.len);
>      > + s_rxdesc[s_rxno][0] = MG_BIT(31);
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > }
>      > }
>      > + EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while
>     processing
>      > + EMAC->EMACRXPOLLD = 0; // and resume RX
>      > }
>      >
>      > -static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
>      > - struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>      > - struct connstate *s = c == NULL ? NULL : (struct connstate *)
>     (c + 1);
>      > -#if 0
>      > - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
>     pkt->pay.len));
>      > +struct mg_tcpip_driver mg_tcpip_driver_tm4c =
>     {mg_tcpip_driver_tm4c_init,
>      > + mg_tcpip_driver_tm4c_tx, NULL,
>      > + mg_tcpip_driver_tm4c_up};
>      > #endif
>      > - if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN
>     | TH_ACK)) {
>      > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>     mg_ntohl(pkt->tcp->seq) + 1;
>      > - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
>      > - c->is_connecting = 0; // Client connected
>      > - settmout(c, MIP_TTYPE_KEEPALIVE);
>      > - mg_call(c, MG_EV_CONNECT, NULL); // Let user know
>      > - } else if (c != NULL && c->is_connecting) {
>      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > - } else if (c != NULL && pkt->tcp->flags & TH_RST) {
>      > - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>      > - } else if (c != NULL) {
>      > -#if 0
>      > - MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
>      > - 4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
>      > - 4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
>      > - mg_hexdump(pkt->pay.buf, pkt->pay.len);
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/w5500.c"
>      > #endif
>      > - s->tmiss = 0; // Reset missed keep-alive counter
>      > - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
>      > - settmout(c, MIP_TTYPE_KEEPALIVE); // unless a former ACK
>     timeout is pending
>      > - read_conn(c, pkt); // Override timer with ACK timeout if needed
>      > - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
>      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > - } else if (pkt->tcp->flags & TH_RST) {
>      > - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>      > - // ignore RST if not connected
>      > - } else if (pkt->tcp->flags & TH_SYN) {
>      > - // Use peer's source port as ISN, in order to recognise the
>     handshake
>      > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
>      > - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
>      > - } else if (pkt->tcp->flags & TH_FIN) {
>      > - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
>     + 1U) {
>      > - accept_conn(c, pkt);
>      > - } else if (!c->is_accepted ) { // no peer
>      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>      > - } else {
>      > - // MG_DEBUG(("dropped silently.."));
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
>     MG_ENABLE_DRIVER_W5500
>      > +
>      > +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
>      > +
>      > +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr,
>      > + bool wr, void *buf, size_t len) {
>      > + size_t i;
>      > + uint8_t *p = (uint8_t *) buf;
>      > + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
>      > + (uint8_t) ((block << 3) | (wr ? 4 : 0))};
>      > + s->begin(s->spi);
>      > + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
>      > + for (i = 0; i < len; i++) {
>      > + uint8_t r = s->txn(s->spi, p[i]);
>      > + if (!wr) p[i] = r;
>      > }
>      > + s->end(s->spi);
>      > }
>      >
>      > -static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
>      > - // MG_DEBUG(("IP %d", (int) pkt->pay.len));
>      > - if (pkt->ip->proto == 1) {
>      > - pkt->icmp = (struct icmp *) (pkt->ip + 1);
>      > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>      > - mkpay(pkt, pkt->icmp + 1);
>      > - rx_icmp(ifp, pkt);
>      > - } else if (pkt->ip->proto == 17) {
>      > - pkt->udp = (struct udp *) (pkt->ip + 1);
>      > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
>      > - mkpay(pkt, pkt->udp + 1);
>      > - if (pkt->udp->dport == mg_htons(68)) {
>      > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>      > - mkpay(pkt, pkt->dhcp + 1);
>      > - rx_dhcp_client(ifp, pkt);
>      > - } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
>     mg_htons(67)) {
>      > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>      > - mkpay(pkt, pkt->dhcp + 1);
>      > - rx_dhcp_server(ifp, pkt);
>      > - } else {
>      > - rx_udp(ifp, pkt);
>      > +// clang-format off
>      > +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
>     true, buf, len); }
>      > +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
>      > +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >>
>     8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf,
>     sizeof(buf)); }
>      > +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
>     false, buf, len); }
>      > +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
>     return r; }
>      > +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block,
>     uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
>     buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
>      > +// clang-format on
>      > +
>      > +static size_t w5500_rx(void *buf, size_t buflen, struct
>     mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>      > + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
>     recv len
>      > + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
>     it is stable
>      > + // printf("RSR: %d\n", (int) n);
>      > + if (n > 0) {
>      > + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
>      > + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
>      > + if (n <= len + 2 && n > 1) {
>      > + r = (uint16_t) (n - 2);
>      > + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
>      > }
>      > - } else if (pkt->ip->proto == 6) {
>      > - pkt->tcp = (struct tcp *) (pkt->ip + 1);
>      > - if (pkt->pay.len < sizeof(*pkt->tcp)) return;
>      > - mkpay(pkt, pkt->tcp + 1);
>      > - uint16_t iplen = mg_ntohs(pkt->ip->len);
>      > - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
>      >> 4) * 4U));
>      > - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
>      > - rx_tcp(ifp, pkt);
>      > + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
>     read pointer
>      > + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
>      > + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
>      > }
>      > + return r;
>      > }
>      >
>      > -static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
>      > - // MG_DEBUG(("IP %d", (int) len));
>      > - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
>      > - pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
>      > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>      > - mkpay(pkt, pkt->icmp + 1);
>      > - rx_icmp(ifp, pkt);
>      > - } else if (pkt->ip6->proto == 17) {
>      > - pkt->udp = (struct udp *) (pkt->ip6 + 1);
>      > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
>      > - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
>      > - // mg_htons(udp->dport)));
>      > - mkpay(pkt, pkt->udp + 1);
>      > +static size_t w5500_tx(const void *buf, size_t buflen,
>      > + struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>      > + uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
>      > + while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
>      > + ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
>      > + w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
>      > + w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
>     write pointer
>      > + w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
>      > + for (i = 0; i < 40; i++) {
>      > + uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
>      > + if (ir == 0) continue;
>      > + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
>     (int) n, ptr);
>      > + w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
>      > + if (ir & 8) len = 0; // Timeout. Report error
>      > + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
>      > }
>      > + return len;
>      > }
>      >
>      > -static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
>      > - const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
>      > - struct pkt pkt;
>      > - memset(&pkt, 0, sizeof(pkt));
>      > - pkt.raw.ptr = (char *) buf;
>      > - pkt.raw.len = len;
>      > - pkt.eth = (struct eth *) buf;
>      > - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
>      > - if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
>      > - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
>      > - // Not for us. Drop silently
>      > - } else if (pkt.eth->type == mg_htons(0x806)) {
>      > - pkt.arp = (struct arp *) (pkt.eth + 1);
>      > - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
>     // Truncated
>      > - rx_arp(ifp, &pkt);
>      > - } else if (pkt.eth->type == mg_htons(0x86dd)) {
>      > - pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
>      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
>     // Truncated
>      > - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
>      > - mkpay(&pkt, pkt.ip6 + 1);
>      > - rx_ip6(ifp, &pkt);
>      > - } else if (pkt.eth->type == mg_htons(0x800)) {
>      > - pkt.ip = (struct ip *) (pkt.eth + 1);
>      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>     // Truncated
>      > - // Truncate frame to what IP header tells us
>      > - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
>     pkt.raw.len) {
>      > - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
>      > - }
>      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>     // Truncated
>      > - if ((pkt.ip->ver >> 4) != 4) return; // Not IP
>      > - mkpay(&pkt, pkt.ip + 1);
>      > - rx_ip(ifp, &pkt);
>      > - } else {
>      > - MG_DEBUG((" Unknown eth type %x", mg_htons(pkt.eth->type)));
>      > +static bool w5500_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>      > + s->end(s->spi);
>      > + w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
>      > + w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
>      > + w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
>      > + // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
>      > + w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
>      > + w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
>      > + w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
>      > + w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
>      > + return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
>      > +}
>      > +
>      > +static bool w5500_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *)
>     ifp->driver_data;
>      > + uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
>      > + return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
>      > +}
>      > +
>      > +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init,
>     w5500_tx, w5500_rx,
>      > + w5500_up};
>      > +#endif
>      > +
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/xmc.c"
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
>     MG_ENABLE_DRIVER_XMC
>      > +
>      > +struct ETH_GLOBAL_TypeDef {
>      > + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER,
>     HASH_TABLE_HIGH,
>      > + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL,
>     VLAN_TAG, VERSION,
>      > + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS,
>     RESERVED[2],
>      > + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH,
>     MAC_ADDRESS0_LOW,
>      > + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH,
>     MAC_ADDRESS2_LOW,
>      > + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
>      > + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT,
>     MMC_RECEIVE_INTERRUPT_MASK,
>      > + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
>      > + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
>      > + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5,
>     RX_STATISTICS_2[30],
>      > + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
>      > + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
>      > + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
>      > + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
>      > + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
>      > + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
>      > + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
>      > + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
>      > + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
>      > + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
>      > + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
>      > + CURRENT_HOST_RECEIVE_DESCRIPTOR,
>     CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
>      > + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
>      > +};
>      > +
>      > +#undef ETH0
>      > +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
>      > +
>      > +#define ETH_PKT_SIZE 1536 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 4 // Descriptor size (words)
>      > +
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > +static uint8_t s_txno; // Current TX descriptor
>      > +static uint8_t s_rxno; // Current RX descriptor
>      > +
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>      > +
>      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
>      > + ((uint32_t)addr << 11) |
>      > + ((uint32_t)reg << 6) | 1;
>      > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
>      > + return (uint16_t)(ETH0->GMII_DATA & 0xffff);
>      > +}
>      > +
>      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + ETH0->GMII_DATA = val;
>      > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
>      > + ((uint32_t)addr << 11) |
>      > + ((uint32_t)reg << 6) | 3;
>      > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
>      > +}
>      > +
>      > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data
>     *d) {
>      > + if (d->mdc_cr == -1) {
>      > + // assume ETH clock is 60MHz by default
>      > + // then according to 13.2.8.1, we need to set value 3
>      > + return 3;
>      > }
>      > +
>      > + return d->mdc_cr;
>      > }
>      >
>      > -static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
>      > - if (ifp == NULL || ifp->driver == NULL) return;
>      > - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
>     1000, uptime_ms);
>      > - ifp->now = uptime_ms;
>      > +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_xmc_data *d =
>      > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
>      > + s_ifp = ifp;
>      >
>      > - // Handle physical interface up/down status
>      > - if (expired_1000ms && ifp->driver->up) {
>      > - bool up = ifp->driver->up(ifp);
>      > - bool current = ifp->state != MIP_STATE_DOWN;
>      > - if (up != current) {
>      > - ifp->state = up == false ? MIP_STATE_DOWN
>      > - : ifp->enable_dhcp_client ? MIP_STATE_UP
>      > - : MIP_STATE_READY;
>      > - if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
>      > - onstatechange(ifp);
>      > + // reset MAC
>      > + ETH0->BUS_MODE |= 1;
>      > + while (ETH0->BUS_MODE & 1) (void) 0;
>      > +
>      > + // set clock rate
>      > + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
>      > +
>      > + // init phy
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
>      > +
>      > + // configure MAC: DO, DM, FES, TC
>      > + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14)
>     | MG_BIT(24);
>      > +
>      > + // set the MAC address
>      > + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
>      > + ETH0->MAC_ADDRESS0_LOW =
>      > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
>      > +
>      > + // Configure the receive filter
>      > + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
>      > + // Disable flow control
>      > + ETH0->FLOW_CONTROL = 0;
>      > + // Enable store and forward mode
>      > + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
>      > +
>      > + // Configure DMA bus mode (AAL, USP, RPBL, PBL)
>      > + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8);
>      > +
>      > + // init RX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
>      > + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
>      > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
>      > + if (i == ETH_DESC_CNT - 1) {
>      > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
>      > + } else {
>      > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
>      > }
>      > }
>      > - if (ifp->state == MIP_STATE_DOWN) return;
>      > - // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
>      > + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
>     &s_rxdesc[0][0];
>      >
>      > - if (ifp->ip == 0 && expired_1000ms) {
>      > - tx_dhcp_discover(ifp); // If IP not configured, send DHCP
>      > - } else if (ifp->enable_dhcp_client == false && expired_1000ms
>     && ifp->gw &&
>      > - arp_cache_find(ifp, ifp->gw) == NULL) {
>      > - arp_ask(ifp, ifp->gw); // If GW's MAC address in not in ARP cache
>      > + // init TX descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
>      > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
>      > + if (i == ETH_DESC_CNT - 1) {
>      > + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
>      > + } else {
>      > + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
>      > + }
>      > }
>      > + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
>     &s_txdesc[0][0];
>      >
>      > - // Read data from the network
>      > - size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len,
>     ifp);
>      > - mip_rx(ifp, (void *) ifp->rx.ptr, len);
>      > - qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
>      > + // Clear interrupts
>      > + ETH0->STATUS = 0xFFFFFFFF;
>      >
>      > - // Process timeouts
>      > - for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c =
>     c->next) {
>      > - if (c->is_udp || c->is_listening) continue;
>      > - if (c->is_connecting || c->is_resolving) continue;
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - if (uptime_ms > s->timer) {
>      > - if (s->ttype == MIP_TTYPE_ACK) {
>      > - MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
>      > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
>      > - mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
>      > - } else {
>      > - if (s->tmiss++ > 2) {
>      > - mg_error(c, "keepalive");
>      > - } else {
>      > - MG_DEBUG(("%lu keepalive", c->id));
>      > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
>      > - mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
>      > - }
>      > - }
>      > - settmout(c, MIP_TTYPE_KEEPALIVE);
>      > - }
>      > - }
>      > -#ifdef MIP_QPROFILE
>      > - qp_log();
>      > -#endif
>      > + // Disable MAC interrupts
>      > + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
>      > + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
>      > + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
>      > + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
>      > +
>      > + //Enable interrupts (NIE, RIE, TIE)
>      > + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
>      > +
>      > + // Enable MAC transmission and reception (TE, RE)
>      > + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
>      > + // Enable DMA transmission and reception (ST, SR)
>      > + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
>      > + return true;
>      > }
>      >
>      > -// This function executes in interrupt context, thus it should
>     copy data
>      > -// somewhere fast. Note that newlib's malloc is not thread safe,
>     thus use
>      > -// our lock-free queue with preallocated buffer to copy data and
>     return asap
>      > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
>      > - if (q_write(&ifp->queue, buf, len)) {
>      > - qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
>      > +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // Frame is too big
>      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No free descriptors"));
>      > + len = 0; // All descriptors are busy, fail
>      > } else {
>      > - ifp->dropped++;
>      > - qp_mark(QP_FRAMEDROPPED, ifp->dropped);
>      > - MG_ERROR(("dropped %d", (int) len));
>      > + memcpy(s_txbuf[s_txno], buf, len);
>      > + s_txdesc[s_txno][1] = len;
>      > + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
>      > + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) |
>     MG_BIT(20);
>      > + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control
>     to DMA
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > }
>      > -}
>      >
>      > -size_t mip_qread(void *buf, struct mip_if *ifp) {
>      > - size_t len = q_read(&ifp->queue, buf);
>      > - qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
>      > + // Resume processing
>      > + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
>      > + ETH0->TRANSMIT_POLL_DEMAND = 0;
>      > return len;
>      > }
>      >
>      > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
>      > - return mip_qread((void *) ifp->rx.ptr, ifp);
>      > - (void) len, (void) buf;
>      > -}
>      > +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_xmc_data *d =
>      > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ?
>     10 : 100,
>      > + full_duplex ? "full" : "half"));
>      > + }
>      > + return up;
>      > +}
>      > +
>      > +void ETH0_IRQHandler(void);
>      > +void ETH0_IRQHandler(void) {
>      > + uint32_t irq_status = ETH0->STATUS;
>      > +
>      > + // check if a frame was received
>      > + if (irq_status & MG_BIT(6)) {
>      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>      > + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
>      > + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>      > + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to
>     DMA
>      > + // Resume processing
>      > + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
>      > + ETH0->RECEIVE_POLL_DEMAND = 0;
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > + }
>      > + }
>      > + ETH0->STATUS = MG_BIT(6);
>      > + }
>      >
>      > -void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
>      > - if (ifp->driver->init && !ifp->driver->init(ifp)) {
>      > - MG_ERROR(("driver init failed"));
>      > - } else {
>      > - size_t maxpktsize = 1540;
>      > - ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len =
>     maxpktsize;
>      > - ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len =
>     maxpktsize;
>      > - if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1,
>     ifp->queue.len);
>      > - ifp->timer_1000ms = mg_millis();
>      > - arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
>      > - mgr->priv = ifp;
>      > - ifp->mgr = mgr;
>      > - mgr->extraconnsize = sizeof(struct connstate);
>      > - if (ifp->ip == 0) ifp->enable_dhcp_client = true;
>      > -#ifdef MIP_QPROFILE
>      > - qp_init();
>      > -#endif
>      > + // clear Successful transmission interrupt
>      > + if (irq_status & 1) {
>      > + ETH0->STATUS = 1;
>      > }
>      > -}
>      >
>      > -void mip_free(struct mip_if *ifp) {
>      > - free((char *) ifp->rx.ptr);
>      > - free((char *) ifp->tx.ptr);
>      > + // clear normal interrupt
>      > + if (irq_status & MG_BIT(16)) {
>      > + ETH0->STATUS = MG_BIT(16);
>      > + }
>      > }
>      >
>      > -int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d,
>     bool udp) {
>      > - (void) m, (void) fn, (void) d, (void) udp;
>      > - MG_ERROR(("Not implemented"));
>      > - return -1;
>      > -}
>      > +struct mg_tcpip_driver mg_tcpip_driver_xmc = {
>      > + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
>      > + mg_tcpip_driver_xmc_up};
>      > +#endif
>      >
>      > -#if 0
>      > -static uint16_t mkeport(void) {
>      > - uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
>      > - mg_random(&a, sizeof(a));
>      > - c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
>      > - return c;
>      > -}
>      > +#ifdef MG_ENABLE_LINES
>      > +#line 1 "src/drivers/xmc7.c"
>      > #endif
>      >
>      > -void mg_connect_resolved(struct mg_connection *c) {
>      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>      > - c->is_resolving = 0;
>      > - if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport =
>     MIP_ETHEMERAL_PORT;
>      > - c->loc.ip = ifp->ip;
>      > - c->loc.port = mg_htons(ifp->eport++);
>      > - MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip,
>     mg_ntohs(c->loc.port),
>      > - 4, &c->rem.ip, mg_ntohs(c->rem.port)));
>      > - mg_call(c, MG_EV_RESOLVE, NULL);
>      > - if (c->is_udp) {
>      > - mg_call(c, MG_EV_CONNECT, NULL);
>      > - } else {
>      > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
>      > - tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn,
>     0, NULL, 0);
>      > - c->is_connecting = 1;
>      > - }
>      > -}
>      >
>      > -bool mg_open_listener(struct mg_connection *c, const char *url) {
>      > - c->loc.port = mg_htons(mg_url_port(url));
>      > - return true;
>      > -}
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
>     MG_ENABLE_DRIVER_XMC7
>      > +
>      > +struct ETH_Type {
>      > + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
>      > + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
>      > + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
>      > + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT,
>     PAUSE_TIME,
>      > + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU,
>     JUMBO_MAX_LENGTH,
>      > + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
>      > + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM,
>     HASH_TOP,
>      > + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
>      > + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
>      > + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
>      > + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
>      > + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
>      > + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
>      > + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
>      > + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
>      > + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
>      > + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
>      > + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
>      > + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
>      > + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
>      > + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
>      > + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65,
>     FRAMES_RXED_128,
>      > + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024,
>     FRAMES_RXED_1519,
>      > + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
>      > + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS,
>     RX_RESOURCE_ERRORS,
>      > + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
>      > + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC,
>     TSU_TIMER_MSB_SEC,
>      > + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC,
>     TSU_TIMER_SEC,
>      > + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
>      > + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
>      > + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
>      > + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
>      > + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS,
>     RESERVED6[8],
>      > + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3,
>     RESERVED7,
>      > + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
>      > + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4,
>     DESIGNCFG_DEBUG5,
>      > + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8,
>     DESIGNCFG_DEBUG9,
>      > + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
>      > + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
>      > + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS,
>     RESERVED11,
>      > + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
>      > + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
>      > + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
>      > + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3,
>     RESERVED16[3],
>      > + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A,
>     CBS_IDLESLOPE_Q_B,
>      > + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL,
>     UPPER_RX_Q_BASE_ADDR,
>      > + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
>      > + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
>      > +};
>      >
>      > -static void write_conn(struct mg_connection *c) {
>      > - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
>      > - : mg_io_send(c, c->send.buf, c->send.len);
>      > - if (len > 0) {
>      > - mg_iobuf_del(&c->send, 0, (size_t) len);
>      > - mg_call(c, MG_EV_WRITE, &len);
>      > - }
>      > +#define ETH0 ((struct ETH_Type *) 0x40490000)
>      > +
>      > +#define ETH_PKT_SIZE 1536 // Max frame size
>      > +#define ETH_DESC_CNT 4 // Descriptors count
>      > +#define ETH_DS 2 // Descriptor size (words)
>      > +
>      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>      > +static uint8_t s_txno; // Current TX descriptor
>      > +static uint8_t s_rxno; // Current RX descriptor
>      > +
>      > +static struct mg_tcpip_if *s_ifp; // MIP interface
>      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>      > +
>      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>      > + // WRITE1, READ OPERATION, PHY, REG, WRITE10
>      > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf)
>     << 24) |
>      > + ((reg & 0x1f) << 18) | MG_BIT(17);
>      > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
>      > + return ETH0->PHY_MANAGEMENT & 0xffff;
>      > }
>      >
>      > -static void close_conn(struct mg_connection *c) {
>      > - struct connstate *s = (struct connstate *) (c + 1);
>      > - mg_iobuf_free(&s->raw); // For TLS connections, release raw data
>      > - if (c->is_udp == false && c->is_listening == false) { // For
>     TCP conns,
>      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv; // send
>     TCP FIN
>      > - tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
>      > - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>      > - }
>      > - mg_close_conn(c);
>      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>     val) {
>      > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf)
>     << 24) |
>      > + ((reg & 0x1f) << 18) | MG_BIT(17) | val;
>      > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
>      > }
>      >
>      > -static bool can_write(struct mg_connection *c) {
>      > - return c->is_connecting == 0 && c->is_resolving == 0 &&
>     c->send.len > 0 &&
>      > - c->is_tls_hs == 0;
>      > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data
>     *d) {
>      > + // see ETH0 -> NETWORK_CONFIG register
>      > + (void) d;
>      > + return 3;
>      > }
>      >
>      > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>      > - struct mg_connection *c, *tmp;
>      > - uint64_t now = mg_millis();
>      > - mip_poll((struct mip_if *) mgr->priv, now);
>      > - mg_timer_poll(&mgr->timers, now);
>      > - for (c = mgr->conns; c != NULL; c = tmp) {
>      > - tmp = c->next;
>      > - mg_call(c, MG_EV_POLL, &now);
>      > - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
>      > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>      > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>      > - if (c->is_tls_hs) mg_tls_handshake(c);
>      > - if (can_write(c)) write_conn(c);
>      > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>      > - if (c->is_closing) close_conn(c);
>      > +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_xmc7_data *d =
>      > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
>      > + s_ifp = ifp;
>      > +
>      > + // enable controller, set RGMII mode
>      > + ETH0->CTL = MG_BIT(31) | 2;
>      > +
>      > + uint32_t cr = get_clock_rate(d);
>      > + // set NSP change, ignore RX FCS, data bus width, clock rate
>      > + // frame length 1536, full duplex, speed
>      > + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
>      > + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
>      > + MG_BIT(1) | MG_BIT(0);
>      > +
>      > + // config DMA settings: Force TX burst, Discard on Error, set
>     RX buffer size
>      > + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
>      > + ETH0->DMA_CONFIG =
>      > + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8)
>     | 4;
>      > +
>      > + // initialize descriptors
>      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>      > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
>      > + if (i == ETH_DESC_CNT - 1) {
>      > + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor
>      > + }
>      > +
>      > + s_txdesc[i][0] = (uint32_t) s_txbuf[i];
>      > + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor
>      > + if (i == ETH_DESC_CNT - 1) {
>      > + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor
>      > + }
>      > }
>      > - (void) ms;
>      > + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
>      > + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
>      > +
>      > + // disable other queues
>      > + ETH0->TRANSMIT_Q2_PTR = 1;
>      > + ETH0->TRANSMIT_Q1_PTR = 1;
>      > + ETH0->RECEIVE_Q2_PTR = 1;
>      > + ETH0->RECEIVE_Q1_PTR = 1;
>      > +
>      > + // enable interrupts (TX and RX complete)
>      > + ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
>      > +
>      > + // set MAC address
>      > + ETH0->SPEC_ADD1_BOTTOM =
>      > + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 |
>     ifp->mac[0];
>      > + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
>      > +
>      > + // enable MDIO, TX, RX
>      > + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
>      > +
>      > + // start transmission
>      > + ETH0->NETWORK_CONTROL |= MG_BIT(9);
>      > +
>      > + // init phy
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
>      > +
>      > + (void) d;
>      > + return true;
>      > }
>      >
>      > -bool mg_send(struct mg_connection *c, const void *buf, size_t
>     len) {
>      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>      > - bool res = false;
>      > - if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
>      > - mg_error(c, "net down");
>      > - } else if (c->is_udp) {
>      > - tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf,
>     len);
>      > - res = true;
>      > +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
>      > + struct mg_tcpip_if *ifp) {
>      > + if (len > sizeof(s_txbuf[s_txno])) {
>      > + MG_ERROR(("Frame too big, %ld", (long) len));
>      > + len = 0; // Frame is too big
>      > + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
>      > + ifp->nerr++;
>      > + MG_ERROR(("No free descriptors"));
>      > + len = 0; // All descriptors are busy, fail
>      > } else {
>      > - res = mg_iobuf_add(&c->send, c->send.len, buf, len);
>      > - }
>      > - return res;
>      > -}
>      > + memcpy(s_txbuf[s_txno], buf, len);
>      > + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30)
>     : 0) |
>      > + MG_BIT(15) | len; // Last buffer and length
>      >
>      > -#ifdef MIP_QPROFILE
>      > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
>      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>      > + }
>      >
>      > -#pragma pack(push, 1)
>      > -struct qpentry {
>      > - uint32_t timestamp;
>      > - uint16_t type;
>      > - uint16_t len;
>      > -};
>      > -#pragma pack(pop)
>      > + MG_DSB();
>      > + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
>      > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
>      >
>      > -static struct queue qp;
>      > + return len;
>      > +}
>      >
>      > -// This is called from IRQ and main contexts; two producers,
>     single consumer
>      > -// TODO(scaprile): avoid concurrency issues (2 queues ?)
>      > -void qp_mark(unsigned int type, int len) {
>      > - static bool ovf = false;
>      > - static uint16_t irq_ctr = 0, drop_ctr = 0;
>      > - struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
>      > - .type = (uint16_t) type,
>      > - .len = (uint16_t) len};
>      > - if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
>      > - if (ovf) {
>      > - e.type = (uint16_t) QP_QUEUEOVF;
>      > - e.len = drop_ctr;
>      > +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
>      > + struct mg_tcpip_driver_xmc7_data *d =
>      > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
>      > + uint8_t speed = MG_PHY_SPEED_10M;
>      > + bool up = false, full_duplex = false;
>      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>     just went up
>      > + if (speed == MG_PHY_SPEED_1000M) {
>      > + ETH0->NETWORK_CONFIG |= MG_BIT(10);
>      > + }
>      > + MG_DEBUG(("Link is %uM %s-duplex",
>      > + speed == MG_PHY_SPEED_10M ? 10 :
>      > + (speed == MG_PHY_SPEED_100M ? 100 : 1000),
>      > + full_duplex ? "full" : "half"));
>      > }
>      > - ovf = !q_write(&qp, &e, sizeof(e));
>      > + (void) d;
>      > + return up;
>      > }
>      >
>      > -void qp_log(void) {
>      > - struct qpentry e;
>      > - const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP",
>     "OVFL"};
>      > - for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
>      > - MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
>      > +void ETH_IRQHandler(void) {
>      > + uint32_t irq_status = ETH0->INT_STATUS;
>      > + if (irq_status & MG_BIT(1)) {
>      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>      > + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
>      > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
>      > + //MG_INFO(("Receive complete: %ld bytes", len));
>      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>      > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control
>     to DMA
>      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>      > + }
>      > + }
>      > }
>      > -}
>      >
>      > -void qp_init(void) {
>      > - qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
>      > - qp.buf = calloc(1, qp.len); // THERE IS NO FREE
>      > + ETH0->INT_STATUS = irq_status;
>      > }
>      > -#endif // MIP_QPROFILE
>      >
>      > -#endif // MG_ENABLE_MIP
>      > +struct mg_tcpip_driver mg_tcpip_driver_xmc7 =
>     {mg_tcpip_driver_xmc7_init,
>      > + mg_tcpip_driver_xmc7_tx, NULL,
>      > + mg_tcpip_driver_xmc7_up};
>      > +#endif
>      > diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
>      > index 350aad9c..3a439a6e 100644
>      > --- a/mongoose/mongoose.h
>      > +++ b/mongoose/mongoose.h
>      > @@ -1,5 +1,5 @@
>      > // Copyright (c) 2004-2013 Sergey Lyubka
>      > -// Copyright (c) 2013-2022 Cesanta Software Limited
>      > +// Copyright (c) 2013-2024 Cesanta Software Limited
>      > // All rights reserved
>      > //
>      > // This software is dual-licensed: you can redistribute it and/or
>     modify
>      > @@ -15,58 +15,48 @@
>      > // Alternatively, you can license this software under a commercial
>      > // license, as set out in https://www.mongoose.ws/licensing/
>     <https://www.mongoose.ws/licensing/>
>      > //
>      > -// SPDX-License-Identifier: GPL-2.0-only
>      > +// SPDX-License-Identifier: GPL-2.0-only or commercial
>      >
>      > #ifndef MONGOOSE_H
>      > #define MONGOOSE_H
>      >
>      > -#define MG_VERSION "7.8"
>      > +#define MG_VERSION "7.14"
>      >
>      > #ifdef __cplusplus
>      > extern "C" {
>      > #endif
>      >
>      >
>      > -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
>      > -#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
>      > -#define MG_ARCH_WIN32 2 // Windows
>      > -#define MG_ARCH_ESP32 3 // ESP32
>      > -#define MG_ARCH_ESP8266 4 // ESP8266
>      > -#define MG_ARCH_FREERTOS 5 // FreeRTOS
>      > -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
>      > -#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
>      > -#define MG_ARCH_NEWLIB 8 // Bare metal ARM
>      > -#define MG_ARCH_RTX 9 // Keil MDK RTX
>      > -#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
>      > -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
>      > +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h
>      > +#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
>      > +#define MG_ARCH_WIN32 2 // Windows
>      > +#define MG_ARCH_ESP32 3 // ESP32
>      > +#define MG_ARCH_ESP8266 4 // ESP8266
>      > +#define MG_ARCH_FREERTOS 5 // FreeRTOS
>      > +#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
>      > +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
>      > +#define MG_ARCH_NEWLIB 8 // Bare metal ARM
>      > +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
>      > +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
>      > +#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
>      > +#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
>      > +#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5,
>     FreeRTOS)
>      > +#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
>      >
>      > #if !defined(MG_ARCH)
>      > #if defined(__unix__) || defined(__APPLE__)
>      > #define MG_ARCH MG_ARCH_UNIX
>      > #elif defined(_WIN32)
>      > #define MG_ARCH MG_ARCH_WIN32
>      > -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
>      > -#define MG_ARCH MG_ARCH_ESP8266
>      > -#elif defined(__ZEPHYR__)
>      > -#define MG_ARCH MG_ARCH_ZEPHYR
>      > -#elif defined(ESP_PLATFORM)
>      > -#define MG_ARCH MG_ARCH_ESP32
>      > -#elif defined(FREERTOS_IP_H)
>      > -#define MG_ARCH MG_ARCH_FREERTOS
>      > -#define MG_ENABLE_FREERTOS_TCP 1
>      > -#elif defined(AZURE_RTOS_THREADX)
>      > -#define MG_ARCH MG_ARCH_AZURERTOS
>      > -#elif defined(PICO_TARGET_NAME)
>      > -#define MG_ARCH MG_ARCH_RP2040
>      > #endif
>      > #endif // !defined(MG_ARCH)
>      >
>      > #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
>      > -#include "mongoose_custom.h" // keep this include
>      > +#include "mongoose_config.h" // keep this include
>      > #endif
>      >
>      > #if !defined(MG_ARCH)
>      > -#error "MG_ARCH is not specified and we couldn't guess it. Set
>     -D MG_ARCH=..."
>      > +#error "MG_ARCH is not specified and we couldn't guess it.
>     Define MG_ARCH=... in your compiler"
>      > #endif
>      >
>      > // http://esr.ibiblio.org/?p=5095 <http://esr.ibiblio.org/?p=5095>
>      > @@ -130,7 +120,8 @@ extern "C" {
>      > #include <sys/types.h>
>      > #include <time.h>
>      >
>      > -#include <esp_timer.h>
>      > +#include <esp_ota_ops.h> // Use angle brackets to avoid
>      > +#include <esp_timer.h> // amalgamation ditching them
>      >
>      > #define MG_PATH_MAX 128
>      >
>      > @@ -166,15 +157,24 @@ extern "C" {
>      > #if MG_ARCH == MG_ARCH_FREERTOS
>      >
>      > #include <ctype.h>
>      > -// #include <errno.h> // Cannot include errno - might conflict
>     with lwip!
>      > +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
>      > +#include <errno.h>
>      > +#endif
>      > #include <stdarg.h>
>      > #include <stdbool.h>
>      > #include <stddef.h>
>      > #include <stdint.h>
>      > #include <stdio.h>
>      > -#include <stdlib.h> // rand(), strtol(), atoi()
>      > +#include <stdlib.h> // rand(), strtol(), atoi()
>      > #include <string.h>
>      > +#if defined(__ARMCC_VERSION)
>      > +#define mode_t size_t
>      > +#include <alloca.h>
>      > +#include <time.h>
>      > +#elif defined(__CCRH__)
>      > +#else
>      > #include <sys/stat.h>
>      > +#endif
>      >
>      > #include <FreeRTOS.h>
>      > #include <task.h>
>      > @@ -186,7 +186,7 @@ extern "C" {
>      > #define calloc(a, b) mg_calloc(a, b)
>      > #define free(a) vPortFree(a)
>      > #define malloc(a) pvPortMalloc(a)
>      > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
>      > +#define strdup(s) mg_mprintf("%s", s)
>      >
>      > // Re-route calloc/free to the FreeRTOS's functions, don't use
>     stdlib
>      > static inline void *mg_calloc(size_t cnt, size_t size) {
>      > @@ -240,9 +240,34 @@ static inline int mg_mkdir(const char *path,
>     mode_t mode) {
>      > #include <pico/stdlib.h>
>      > int mkdir(const char *, mode_t);
>      > #endif
>      > -
>      > -
>      > -#if MG_ARCH == MG_ARCH_RTX
>      > +
>      >
>      > +
>      >
>      > +#if MG_ARCH == MG_ARCH_RTTHREAD
>      >
>      > +
>      >
>      > +#include <rtthread.h>
>      >
>      > +#include <ctype.h>
>      >
>      > +#include <errno.h>
>      >
>      > +#include <fcntl.h>
>      >
>      > +#include <sys/socket.h>
>      >
>      > +#include <sys/select.h>
>      >
>      > +#include <stdarg.h>
>      >
>      > +#include <stdbool.h>
>      >
>      > +#include <stdint.h>
>      >
>      > +#include <stdio.h>
>      >
>      > +#include <stdlib.h>
>      >
>      > +#include <string.h>
>      >
>      > +#include <sys/types.h>
>      >
>      > +#include <time.h>
>      >
>      > +
>      >
>      > +#ifndef MG_IO_SIZE
>      >
>      > +#define MG_IO_SIZE 1460
>      >
>      > +#endif
>      >
>      > +
>      >
>      > +#endif // MG_ARCH == MG_ARCH_RTTHREAD
>      >
>      > +
>      > +
>      > +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
>      > + MG_ARCH == MG_ARCH_CMSIS_RTOS2
>      >
>      > #include <ctype.h>
>      > #include <errno.h>
>      > @@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
>      > #include <stdint.h>
>      > #include <stdio.h>
>      > #include <stdlib.h>
>      > +#include <alloca.h>
>      > #include <string.h>
>      > #include <time.h>
>      > +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
>      > +#include "cmsis_os.h" // keep this include
>      > +// https://developer.arm.com/documentation/ka003821/latest
>     <https://developer.arm.com/documentation/ka003821/latest>
>      > +extern uint32_t rt_time_get(void);
>      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
>      > +#include "cmsis_os2.h" // keep this include
>      > +#endif
>      > +
>      > +#define strdup(s) mg_mprintf("%s", s)
>      > +
>      > +#if defined(__ARMCC_VERSION)
>      > +#define mode_t size_t
>      > +#define mkdir(a, b) mg_mkdir(a, b)
>      > +static inline int mg_mkdir(const char *path, mode_t mode) {
>      > + (void) path, (void) mode;
>      > + return -1;
>      > +}
>      > +#endif
>      >
>      > -#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
>     !MG_ENABLE_LWIP)
>      > +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH ==
>     MG_ARCH_CMSIS_RTOS2) && \
>      > + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
>     !MG_ENABLE_LWIP) && \
>      > + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
>      > #define MG_ENABLE_RL 1
>      > +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
>      > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
>      > +#endif
>      > #endif
>      >
>      > #endif
>      > @@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
>      > #define MG_INVALID_SOCKET INVALID_SOCKET
>      > #define MG_SOCKET_TYPE SOCKET
>      > typedef unsigned long nfds_t;
>      > -#define MG_SOCKET_ERRNO WSAGetLastError()
>      > #if defined(_MSC_VER)
>      > #pragma comment(lib, "ws2_32.lib")
>      > #ifndef alloca
>      > @@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
>      > #endif
>      > #endif
>      > #define poll(a, b, c) WSAPoll((a), (b), (c))
>      > -#ifndef SO_EXCLUSIVEADDRUSE
>      > -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
>      > -#endif
>      > #define closesocket(x) closesocket(x)
>      >
>      > typedef int socklen_t;
>      > @@ -423,16 +468,24 @@ typedef int socklen_t;
>      > #define MG_PATH_MAX FILENAME_MAX
>      > #endif
>      >
>      > -#ifndef EINPROGRESS
>      > -#define EINPROGRESS WSAEINPROGRESS
>      > -#endif
>      > -#ifndef EWOULDBLOCK
>      > -#define EWOULDBLOCK WSAEWOULDBLOCK
>      > +#ifndef SO_EXCLUSIVEADDRUSE
>      > +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
>      > #endif
>      >
>      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError()
>     : 0)
>      > +
>      > +#define MG_SOCK_PENDING(errcode) \
>      > + (((errcode) < 0) && \
>      > + (WSAGetLastError() == WSAEINTR || WSAGetLastError() ==
>     WSAEINPROGRESS || \
>      > + WSAGetLastError() == WSAEWOULDBLOCK))
>      > +
>      > +#define MG_SOCK_RESET(errcode) \
>      > + (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
>      > +
>      > #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
>      > -#define sleep(x) Sleep(x)
>      > +#define sleep(x) Sleep((x) *1000)
>      > #define mkdir(a, b) _mkdir(a)
>      > +#define timegm(x) _mkgmtime(x)
>      >
>      > #ifndef S_ISDIR
>      > #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
>      > @@ -442,6 +495,10 @@ typedef int socklen_t;
>      > #define MG_ENABLE_DIRLIST 1
>      > #endif
>      >
>      > +#ifndef SIGPIPE
>      > +#define SIGPIPE 0
>      > +#endif
>      > +
>      > #endif
>      >
>      >
>      > @@ -451,8 +508,9 @@ typedef int socklen_t;
>      >
>      > #include <ctype.h>
>      > #include <errno.h>
>      > -#include <fcntl.h>
>      > #include <zephyr/net/socket.h>
>      > +#include <zephyr/posix/fcntl.h>
>      > +#include <zephyr/posix/sys/select.h>
>      > #include <stdarg.h>
>      > #include <stdbool.h>
>      > #include <stdint.h>
>      > @@ -464,11 +522,18 @@ typedef int socklen_t;
>      >
>      > #define MG_PUTCHAR(x) printk("%c", x)
>      > #ifndef strdup
>      > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
>      > +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
>      > #endif
>      > #define strerror(x) zsock_gai_strerror(x)
>      > +
>      > +#ifndef FD_CLOEXEC
>      > #define FD_CLOEXEC 0
>      > +#endif
>      > +
>      > +#ifndef F_SETFD
>      > #define F_SETFD 0
>      > +#endif
>      > +
>      > #define MG_ENABLE_SSI 0
>      >
>      > int rand(void);
>      > @@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
>      >
>      > #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
>      >
>      > -#include <ctype.h>
>      > -#include <errno.h>
>      > #include <limits.h>
>      > -#include <stdarg.h>
>      > -#include <stdbool.h>
>      > -#include <stddef.h>
>      > -#include <stdio.h>
>      > -#include <stdlib.h>
>      > -#include <string.h>
>      > -#include <sys/stat.h>
>      > -#include <time.h>
>      > -
>      > -#include <FreeRTOS.h>
>      > #include <list.h>
>      > -#include <task.h>
>      >
>      > #include <FreeRTOS_IP.h>
>      > #include <FreeRTOS_Sockets.h>
>      > +#include <FreeRTOS_errno_TCP.h> // contents to be moved and file
>     removed, some day
>      >
>      > #define MG_SOCKET_TYPE Socket_t
>      > #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
>      > @@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
>      > #define SO_ERROR 0
>      > #define SOL_SOCKET 0
>      > #define SO_REUSEADDR 0
>      > +
>      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
>      > +
>      > +#define MG_SOCK_PENDING(errcode) \
>      > + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
>      > + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \
>      > + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
>      > + (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
>      > +
>      > +#define MG_SOCK_RESET(errcode) ((errcode) ==
>     -pdFREERTOS_ERRNO_ENOTCONN)
>      > +
>      > +// actually only if optional timeout is enabled
>      > +#define MG_SOCK_INTR(fd) (fd == NULL)
>      > +
>      > #define sockaddr_in freertos_sockaddr
>      > #define sockaddr freertos_sockaddr
>      > #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
>      > @@ -543,8 +610,17 @@ static inline int
>     mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
>      >
>      >
>      > #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
>      > -#if defined(__GNUC__)
>      > +
>      > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
>      > #include <sys/stat.h>
>      > +#endif
>      > +
>      > +struct timeval;
>      > +
>      > +#include <lwip/sockets.h>
>      > +
>      > +#if !LWIP_TIMEVAL_PRIVATE
>      > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang
>     sets both
>      > #include <sys/time.h>
>      > #else
>      > struct timeval {
>      > @@ -552,8 +628,7 @@ struct timeval {
>      > long tv_usec;
>      > };
>      > #endif
>      > -
>      > -#include <lwip/sockets.h>
>      > +#endif
>      >
>      > #if LWIP_SOCKET != 1
>      > // Sockets support disabled in LWIP by default
>      > @@ -565,16 +640,25 @@ struct timeval {
>      > #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
>      > #include <rl_net.h>
>      >
>      > -#define MG_ENABLE_CUSTOM_MILLIS 1
>      > #define closesocket(x) closesocket(x)
>      > -#define mkdir(a, b) (-1)
>      > -#define EWOULDBLOCK BSD_EWOULDBLOCK
>      > -#define EAGAIN BSD_EWOULDBLOCK
>      > -#define EINPROGRESS BSD_EWOULDBLOCK
>      > -#define EINTR BSD_EWOULDBLOCK
>      > -#define ECONNRESET BSD_ECONNRESET
>      > -#define EPIPE BSD_ECONNRESET
>      > +
>      > #define TCP_NODELAY SO_KEEPALIVE
>      > +
>      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
>      > +
>      > +#define MG_SOCK_PENDING(errcode) \
>      > + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
>      > + (errcode) == BSD_EINPROGRESS)
>      > +
>      > +#define MG_SOCK_RESET(errcode) \
>      > + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
>      > +
>      > +// In blocking mode, which is enabled by default, accept() waits
>     for a
>      > +// connection request. In non blocking mode, you must call accept()
>      > +// again if the error code BSD_EWOULDBLOCK is returned.
>      > +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
>      > +
>      > +#define socklen_t int
>      > #endif
>      >
>      >
>      > @@ -582,8 +666,12 @@ struct timeval {
>      > #define MG_ENABLE_LOG 1
>      > #endif
>      >
>      > -#ifndef MG_ENABLE_MIP
>      > -#define MG_ENABLE_MIP 0 // Mongoose built-in network stack
>      > +#ifndef MG_ENABLE_CUSTOM_LOG
>      > +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
>      > +#endif
>      > +
>      > +#ifndef MG_ENABLE_TCPIP
>      > +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack
>      > #endif
>      >
>      > #ifndef MG_ENABLE_LWIP
>      > @@ -599,7 +687,7 @@ struct timeval {
>      > #endif
>      >
>      > #ifndef MG_ENABLE_SOCKET
>      > -#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
>      > +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
>      > #endif
>      >
>      > #ifndef MG_ENABLE_POLL
>      > @@ -614,18 +702,6 @@ struct timeval {
>      > #define MG_ENABLE_FATFS 0
>      > #endif
>      >
>      > -#ifndef MG_ENABLE_MBEDTLS
>      > -#define MG_ENABLE_MBEDTLS 0
>      > -#endif
>      > -
>      > -#ifndef MG_ENABLE_OPENSSL
>      > -#define MG_ENABLE_OPENSSL 0
>      > -#endif
>      > -
>      > -#ifndef MG_ENABLE_CUSTOM_TLS
>      > -#define MG_ENABLE_CUSTOM_TLS 0
>      > -#endif
>      > -
>      > #ifndef MG_ENABLE_SSI
>      > #define MG_ENABLE_SSI 0
>      > #endif
>      > @@ -634,6 +710,10 @@ struct timeval {
>      > #define MG_ENABLE_IPV6 0
>      > #endif
>      >
>      > +#ifndef MG_IPV6_V6ONLY
>      > +#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4
>     address
>      > +#endif
>      > +
>      > #ifndef MG_ENABLE_MD5
>      > #define MG_ENABLE_MD5 1
>      > #endif
>      > @@ -659,12 +739,16 @@ struct timeval {
>      > #define MG_ENABLE_PACKED_FS 0
>      > #endif
>      >
>      > +#ifndef MG_ENABLE_ASSERT
>      > +#define MG_ENABLE_ASSERT 0
>      > +#endif
>      > +
>      > #ifndef MG_IO_SIZE
>      > #define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer
>     growth
>      > #endif
>      >
>      > #ifndef MG_MAX_RECV_SIZE
>      > -#define MG_MAX_RECV_SIZE (3 * 1024 * 1024) // Maximum recv IO
>     buffer size
>      > +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv
>     IO buffer size
>      > #endif
>      >
>      > #ifndef MG_DATA_SIZE
>      > @@ -688,18 +772,18 @@ struct timeval {
>      > #endif
>      >
>      > #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
>      > -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
>      > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
>      > #endif
>      >
>      > #ifndef MG_DIRSEP
>      > #define MG_DIRSEP '/'
>      > #endif
>      >
>      > -#ifndef MG_ENABLE_FILE
>      > +#ifndef MG_ENABLE_POSIX_FS
>      > #if defined(FOPEN_MAX)
>      > -#define MG_ENABLE_FILE 1
>      > +#define MG_ENABLE_POSIX_FS 1
>      > #else
>      > -#define MG_ENABLE_FILE 0
>      > +#define MG_ENABLE_POSIX_FS 0
>      > #endif
>      > #endif
>      >
>      > @@ -732,60 +816,112 @@ struct timeval {
>      > #define MG_EPOLL_MOD(c, wr)
>      > #endif
>      >
>      > +#ifndef MG_ENABLE_PROFILE
>      > +#define MG_ENABLE_PROFILE 0
>      > +#endif
>      >
>      > +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also
>     initialize
>      > +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver
>     for
>      > +#endif // Mongoose built-in network stack
>      >
>      > +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
>      > +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>     (DHCP)
>      > +#endif
>      >
>      > -struct mg_str {
>      > - const char *ptr; // Pointer to string data
>      > - size_t len; // String len
>      > -};
>      > +#ifndef MG_TCPIP_MASK
>      > +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>     (DHCP)
>      > +#endif
>      > +
>      > +#ifndef MG_TCPIP_GW
>      > +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>     (DHCP)
>      > +#endif
>      > +
>      > +#ifndef MG_SET_MAC_ADDRESS
>      > +#define MG_SET_MAC_ADDRESS(mac)
>      > +#endif
>      >
>      > -#define MG_NULL_STR \
>      > - { NULL, 0 }
>      > +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
>      > +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
>      > +#endif
>      >
>      > -#define MG_C_STR(a) \
>      > - { (a), sizeof(a) - 1 }
>      > +
>      > +
>      > +
>      > +// Describes an arbitrary chunk of memory
>      > +struct mg_str {
>      > + char *buf; // String data
>      > + size_t len; // String length
>      > +};
>      >
>      > // Using macro to avoid shadowing C++ struct constructor, see #1298
>      > #define mg_str(s) mg_str_s(s)
>      >
>      > struct mg_str mg_str(const char *s);
>      > struct mg_str mg_str_n(const char *s, size_t n);
>      > -int mg_lower(const char *s);
>      > -int mg_ncasecmp(const char *s1, const char *s2, size_t len);
>      > int mg_casecmp(const char *s1, const char *s2);
>      > -int mg_vcmp(const struct mg_str *s1, const char *s2);
>      > -int mg_vcasecmp(const struct mg_str *str1, const char *str2);
>      > int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
>      > -struct mg_str mg_strstrip(struct mg_str s);
>      > -struct mg_str mg_strdup(const struct mg_str s);
>      > -const char *mg_strstr(const struct mg_str haystack, const struct
>     mg_str needle);
>      > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
>     str2);
>      > bool mg_match(struct mg_str str, struct mg_str pattern, struct
>     mg_str *caps);
>      > -bool mg_globmatch(const char *pattern, size_t plen, const char
>     *s, size_t n);
>      > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
>     mg_str *v);
>      > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
>     *v, char delim);
>      > -char *mg_hex(const void *buf, size_t len, char *dst);
>      > -void mg_unhex(const char *buf, size_t len, unsigned char *to);
>      > -unsigned long mg_unhexn(const char *s, size_t len);
>      > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
>      > -int64_t mg_to64(struct mg_str str);
>      > -uint64_t mg_tou64(struct mg_str str);
>      > -char *mg_remove_double_dots(char *s);
>      > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
>     *b, char delim);
>      > +
>      > +bool mg_str_to_num(struct mg_str, int base, void *val, size_t
>     val_len);
>      > +
>      >
>      >
>      >
>      > +// Single producer, single consumer non-blocking queue
>      > +
>      > +struct mg_queue {
>      > + char *buf;
>      > + size_t size;
>      > + volatile size_t tail;
>      > + volatile size_t head;
>      > +};
>      > +
>      > +void mg_queue_init(struct mg_queue *, char *, size_t); // Init
>     queue
>      > +size_t mg_queue_book(struct mg_queue *, char **buf, size_t); //
>     Reserve space
>      > +void mg_queue_add(struct mg_queue *, size_t); // Add new message
>      > +size_t mg_queue_next(struct mg_queue *, char **); // Get oldest
>     message
>      > +void mg_queue_del(struct mg_queue *, size_t); // Delete oldest
>     message
>      >
>      >
>      > -typedef void (*mg_pfn_t)(char, void *); // Custom putchar
>      > +
>      > +
>      > +typedef void (*mg_pfn_t)(char, void *); // Output function
>      > typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M
>     printer
>      > -void mg_pfn_iobuf(char ch, void *param); // iobuf printer
>      >
>      > size_t mg_vxprintf(void (*)(char, void *), void *, const char
>     *fmt, va_list *);
>      > size_t mg_xprintf(void (*fn)(char, void *), void *, const char
>     *fmt, ...);
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +// Convenience wrappers around mg_xprintf
>      > size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>     va_list *ap);
>      > size_t mg_snprintf(char *, size_t, const char *fmt, ...);
>      > char *mg_vmprintf(const char *fmt, va_list *ap);
>      > char *mg_mprintf(const char *fmt, ...);
>      > +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt,
>     va_list *);
>      > +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
>      > +
>      > +// %M print helper functions
>      > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
>     *ap);
>      > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
>     va_list *ap);
>      > +
>      > +// Various output functions
>      > +void mg_pfn_iobuf(char ch, void *param); // param: struct
>     mg_iobuf *
>      > +void mg_pfn_stdout(char c, void *param); // param: ignored
>      > +
>      > +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m",
>     MG_ESC("hi"))
>      > +#define MG_ESC(str) mg_print_esc, 0, (str)
>      >
>      >
>      >
>      > @@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
>      >
>      >
>      > enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG,
>     MG_LL_VERBOSE };
>      > +extern int mg_log_level; // Current log level, one of MG_LL_*
>      > +
>      > void mg_log(const char *fmt, ...);
>      > -bool mg_log_prefix(int ll, const char *file, int line, const
>     char *fname);
>      > -void mg_log_set(int log_level);
>      > +void mg_log_prefix(int ll, const char *file, int line, const
>     char *fname);
>      > +// bool mg_log2(int ll, const char *file, int line, const char
>     *fmt, ...);
>      > void mg_hexdump(const void *buf, size_t len);
>      > void mg_log_set_fn(mg_pfn_t fn, void *param);
>      >
>      > +#define mg_log_set(level_) mg_log_level = (level_)
>      > +
>      > #if MG_ENABLE_LOG
>      > -#define MG_LOG(level, args) \
>      > - do { \
>      > - if (mg_log_prefix((level), __FILE__, __LINE__, __func__))
>     mg_log args; \
>      > +#define MG_LOG(level, args) \
>      > + do { \
>      > + if ((level) <= mg_log_level) { \
>      > + mg_log_prefix((level), __FILE__, __LINE__, __func__); \
>      > + mg_log args; \
>      > + } \
>      > } while (0)
>      > #else
>      > #define MG_LOG(level, args) \
>      > @@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2,
>     MG_FS_DIR = 4 };
>      > // stat(), write(), read() calls.
>      > struct mg_fs {
>      > int (*st)(const char *path, size_t *size, time_t *mtime); // stat
>     file
>      > - void (*ls)(const char *path, void (*fn)(const char *, void *),
>     void *);
>      > + void (*ls)(const char *path, void (*fn)(const char *, void *),
>      > + void *); // List directory entries: call fn(file_name, fn_data)
>      > + // for each directory entry
>      > void *(*op)(const char *path, int flags); // Open file
>      > void (*cl)(void *fd); // Close file
>      > size_t (*rd)(void *fd, void *buf, size_t len); // Read file
>      > @@ -877,28 +1022,84 @@ struct mg_fd {
>      >
>      > struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>     flags);
>      > void mg_fs_close(struct mg_fd *fd);
>      > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
>     *size);
>      > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
>     size_t len);
>      > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
>      > bool mg_file_write(struct mg_fs *fs, const char *path, const void
>     *, size_t);
>      > bool mg_file_printf(struct mg_fs *fs, const char *path, const
>     char *fmt, ...);
>      >
>      > +// Packed API
>      > +const char *mg_unpack(const char *path, size_t *size, time_t
>     *mtime);
>      > +const char *mg_unlist(size_t no); // Get no'th packed filename
>      > +struct mg_str mg_unpacked(const char *path); // Packed file as
>     mg_str
>      > +
>      >
>      >
>      >
>      >
>      >
>      > +
>      > +#if MG_ENABLE_ASSERT
>      > +#include <assert.h>
>      > +#elif !defined(assert)
>      > +#define assert(x)
>      > +#endif
>      > +
>      > +void mg_bzero(volatile unsigned char *buf, size_t len);
>      > void mg_random(void *buf, size_t len);
>      > char *mg_random_str(char *buf, size_t len);
>      > uint16_t mg_ntohs(uint16_t net);
>      > uint32_t mg_ntohl(uint32_t net);
>      > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
>      > -uint64_t mg_millis(void);
>      > +uint64_t mg_millis(void); // Return milliseconds since boot
>      > +uint64_t mg_now(void); // Return milliseconds since Epoch
>      > +bool mg_path_is_sane(const struct mg_str path);
>      >
>      > #define mg_htons(x) mg_ntohs(x)
>      > #define mg_htonl(x) mg_ntohl(x)
>      >
>      > -#define MG_U32(a, b, c, d) \
>      > - (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
>      > - ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
>      > +#define MG_U32(a, b, c, d) \
>      > + (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) <<
>     16) | \
>      > + ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
>      > +
>      > +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
>      > +
>      > +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n",
>     MG_IPADDR_PARTS(&ip))
>      > +#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
>      > +#define MG_IPADDR_PARTS(ADDR) \
>      > + MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
>      > +
>      > +#define MG_REG(x) ((volatile uint32_t *) (x))[0]
>      > +#define MG_BIT(x) (((uint32_t) 1U) << (x))
>      > +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) &
>     ~(CLRMASK)) | (SETMASK)
>      > +
>      > +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) /
>     (a)) * (a)))
>      > +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
>      > +
>      > +#if defined(__GNUC__)
>      > +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
>      > +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
>      > +#elif defined(__CCRH__)
>      > +#define MG_RH850_DISABLE_IRQ() __DI()
>      > +#define MG_RH850_ENABLE_IRQ() __EI()
>      > +#else
>      > +#define MG_ARM_DISABLE_IRQ()
>      > +#define MG_ARM_ENABLE_IRQ()
>      > +#endif
>      > +
>      > +#if defined(__CC_ARM)
>      > +#define MG_DSB() __dsb(0xf)
>      > +#elif defined(__ARMCC_VERSION)
>      > +#define MG_DSB() __builtin_arm_dsb(0xf)
>      > +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
>      > +#define MG_DSB() asm("DSB 0xf")
>      > +#elif defined(__ICCARM__)
>      > +#define MG_DSB() __iar_builtin_DSB()
>      > +#else
>      > +#define MG_DSB()
>      > +#endif
>      > +
>      > +struct mg_addr;
>      > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
>      >
>      > // Linked list management macros
>      > #define LIST_ADD_HEAD(type_, head_, elem_) \
>      > @@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
>      > size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *,
>     size_t);
>      > size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
>      >
>      > -int mg_base64_update(unsigned char p, char *to, int len);
>      > -int mg_base64_final(char *to, int len);
>      > -int mg_base64_encode(const unsigned char *p, int n, char *to);
>      > -int mg_base64_decode(const char *src, int n, char *dst);
>      > +
>      > +size_t mg_base64_update(unsigned char input_byte, char *buf,
>     size_t len);
>      > +size_t mg_base64_final(char *buf, size_t len);
>      > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
>     *buf, size_t);
>      > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
>     size_t);
>      >
>      >
>      >
>      > @@ -976,35 +1178,793 @@ typedef struct {
>      > void mg_sha1_init(mg_sha1_ctx *);
>      > void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data,
>     size_t len);
>      > void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
>      > +// https://github.com/B-Con/crypto-algorithms
>     <https://github.com/B-Con/crypto-algorithms>
>      > +// Author: Brad Conte (brad AT bradconte.com
>     <http://bradconte.com>)
>      > +// Disclaimer: This code is presented "as is" without any
>     guarantees.
>      > +// Details: Defines the API for the corresponding SHA1
>     implementation.
>      > +// Copyright: public domain
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +typedef struct {
>      > + uint32_t state[8];
>      > + uint64_t bits;
>      > + uint32_t len;
>      > + unsigned char buffer[64];
>      > +} mg_sha256_ctx;
>      > +
>      > +void mg_sha256_init(mg_sha256_ctx *);
>      > +void mg_sha256_update(mg_sha256_ctx *, const unsigned char
>     *data, size_t len);
>      > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
>      > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
>     uint8_t *data,
>      > + size_t datasz);
>      > +#ifndef TLS_X15519_H
>      > +#define TLS_X15519_H
>      > +
>      > +
>      > +
>      > +#define X25519_BYTES 32
>      > +extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
>      > +
>      > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
>     scalar[X25519_BYTES],
>      > + const uint8_t x1[X25519_BYTES], int clamp);
>      > +
>      > +
>      > +#endif /* TLS_X15519_H */
>      >
>     +/******************************************************************************
>      > + *
>      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>     THE GOOD OF ALL
>      > + *
>      > + * This is a simple and straightforward implementation of
>     AES-GCM authenticated
>      > + * encryption. The focus of this work was correctness &
>     accuracy. It is written
>      > + * in straight 'C' without any particular focus upon
>     optimization or speed. It
>      > + * should be endian (memory byte order) neutral since the few
>     places that care
>      > + * are handled explicitly.
>      > + *
>      > + * This implementation of AES-GCM was created by Steven M.
>     Gibson of GRC.com.
>      > + *
>      > + * It is intended for general purpose use, but was written in
>     support of GRC's
>      > + * reference implementation of the SQRL (Secure Quick Reliable
>     Login) client.
>      > + *
>      > + * See:
>     http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
>     <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
>      > + *
>     http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
>     <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/> \
>      > + * gcm/gcm-revised-spec.pdf
>      > + *
>      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>     WARRANTY MADE
>      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>     YOUR OWN RISK.
>      > + *
>      > +
>     *******************************************************************************/
>      > +#ifndef TLS_AES128_H
>      > +#define TLS_AES128_H
>      > +
>      > +typedef unsigned char uchar; // add some convienent shorter types
>      > +typedef unsigned int uint;
>      > +
>      >
>     +/******************************************************************************
>      > + * AES_CONTEXT : cipher context / holds inter-call data
>      > +
>     ******************************************************************************/
>      > +typedef struct {
>      > + int mode; // 1 for Encryption, 0 for Decryption
>      > + int rounds; // keysize-based rounds count
>      > + uint32_t *rk; // pointer to current round key
>      > + uint32_t buf[68]; // key expansion buffer
>      > +} aes_context;
>      > +
>      > +
>      > +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure
>      > +
>      >
>     +/******************************************************************************
>      > + * GCM_CONTEXT : MUST be called once before ANY use of this library
>      > +
>     ******************************************************************************/
>      > +int mg_gcm_initialize(void);
>      > +
>      > +//
>      > +// aes-gcm.h
>      > +// MKo
>      > +//
>      > +// Created by Markus Kosmal on 20/11/14.
>      > +//
>      > +//
>      > +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned
>     char *input,
>      > + size_t input_length, const unsigned char *key,
>      > + const size_t key_len, const unsigned char *iv,
>      > + const size_t iv_len, unsigned char *aead,
>      > + size_t aead_len, unsigned char *tag,
>      > + const size_t tag_len);
>      > +
>      > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
>     char *input,
>      > + size_t input_length, const unsigned char *key,
>      > + const size_t key_len, const unsigned char *iv,
>      > + const size_t iv_len);
>      > +
>      > +#endif /* TLS_AES128_H */
>      > +
>      > +// End of aes128 PD
>      > +
>      > +
>      > +
>      > +#define MG_UECC_SUPPORTS_secp256r1 1
>      > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
>     2-clause license. */
>      > +
>      > +#ifndef _UECC_H_
>      > +#define _UECC_H_
>      > +
>      > +/* Platform selection options.
>      > +If MG_UECC_PLATFORM is not defined, the code will try to guess
>     it based on
>      > +compiler macros. Possible values for MG_UECC_PLATFORM are
>     defined below: */
>      > +#define mg_uecc_arch_other 0
>      > +#define mg_uecc_x86 1
>      > +#define mg_uecc_x86_64 2
>      > +#define mg_uecc_arm 3
>      > +#define mg_uecc_arm_thumb 4
>      > +#define mg_uecc_arm_thumb2 5
>      > +#define mg_uecc_arm64 6
>      > +#define mg_uecc_avr 7
>      > +
>      > +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate
>     for your platform
>      > +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly
>     defined then it will
>      > +be automatically set based on your platform. */
>      > +
>      > +/* Optimization level; trade speed for code size.
>      > + Larger values produce code that is faster but larger.
>      > + Currently supported values are 0 - 4; 0 is unusably slow for most
>      > + applications. Optimization level 4 currently only has an effect
>     ARM platforms
>      > + where more than one curve is enabled. */
>      > +#ifndef MG_UECC_OPTIMIZATION_LEVEL
>      > +#define MG_UECC_OPTIMIZATION_LEVEL 2
>      > +#endif
>      > +
>      > +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this
>     will cause a
>      > +specific function to be used for (scalar) squaring instead of
>     the generic
>      > +multiplication function. This can make things faster somewhat
>     faster, but
>      > +increases the code size. */
>      > +#ifndef MG_UECC_SQUARE_FUNC
>      > +#define MG_UECC_SQUARE_FUNC 0
>      > +#endif
>      > +
>      > +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as
>     nonzero), this will
>      > +switch to native little-endian format for *all* arrays passed in
>     and out of the
>      > +public API. This includes public and private keys, shared
>     secrets, signatures
>      > +and message hashes. Using this switch reduces the amount of call
>     stack memory
>      > +used by uECC, since less intermediate translations are required.
>     Note that this
>      > +will *only* work on native little-endian processors and it will
>     treat the
>      > +uint8_t arrays passed into the public API as word arrays,
>     therefore requiring
>      > +the provided byte arrays to be word aligned on architectures
>     that do not support
>      > +unaligned accesses. IMPORTANT: Keys and signatures generated with
>      > +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys
>     and signatures
>      > +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties
>     must use the same
>      > +endianness. */
>      > +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>      > +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
>      > +#endif
>      > +
>      > +/* Curve support selection. Set to 0 to remove that curve. */
>      > +#ifndef MG_UECC_SUPPORTS_secp160r1
>      > +#define MG_UECC_SUPPORTS_secp160r1 0
>      > +#endif
>      > +#ifndef MG_UECC_SUPPORTS_secp192r1
>      > +#define MG_UECC_SUPPORTS_secp192r1 0
>      > +#endif
>      > +#ifndef MG_UECC_SUPPORTS_secp224r1
>      > +#define MG_UECC_SUPPORTS_secp224r1 0
>      > +#endif
>      > +#ifndef MG_UECC_SUPPORTS_secp256r1
>      > +#define MG_UECC_SUPPORTS_secp256r1 1
>      > +#endif
>      > +#ifndef MG_UECC_SUPPORTS_secp256k1
>      > +#define MG_UECC_SUPPORTS_secp256k1 0
>      > +#endif
>      > +
>      > +/* Specifies whether compressed point format is supported.
>      > + Set to 0 to disable point compression/decompression functions. */
>      > +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
>      > +#endif
>      > +
>      > +struct MG_UECC_Curve_t;
>      > +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
>      > +
>      > +#ifdef __cplusplus
>      > +extern "C" {
>      > +#endif
>      > +
>      > +#if MG_UECC_SUPPORTS_secp160r1
>      > +MG_UECC_Curve mg_uecc_secp160r1(void);
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp192r1
>      > +MG_UECC_Curve mg_uecc_secp192r1(void);
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp224r1
>      > +MG_UECC_Curve mg_uecc_secp224r1(void);
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp256r1
>      > +MG_UECC_Curve mg_uecc_secp256r1(void);
>      > +#endif
>      > +#if MG_UECC_SUPPORTS_secp256k1
>      > +MG_UECC_Curve mg_uecc_secp256k1(void);
>      > +#endif
>      > +
>      > +/* MG_UECC_RNG_Function type
>      > +The RNG function should fill 'size' random bytes into 'dest'. It
>     should return 1
>      > +if 'dest' was filled with random data, or 0 if the random data
>     could not be
>      > +generated. The filled-in values should be either truly random,
>     or from a
>      > +cryptographically-secure PRNG.
>      > +
>      > +A correctly functioning RNG function must be set (using
>     mg_uecc_set_rng())
>      > +before calling mg_uecc_make_key() or mg_uecc_sign().
>      > +
>      > +Setting a correctly functioning RNG function improves the
>     resistance to
>      > +side-channel attacks for mg_uecc_shared_secret() and
>      > +mg_uecc_sign_deterministic().
>      > +
>      > +A correct RNG function is set by default when building for
>     Windows, Linux, or OS
>      > +X. If you are building on another POSIX-compliant system that
>     supports
>      > +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use
>     the predefined
>      > +RNG. For embedded platforms there is no predefined RNG function;
>     you must
>      > +provide your own.
>      > +*/
>      > +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
>      > +
>      > +/* mg_uecc_set_rng() function.
>      > +Set the function that will be used to generate random bytes. The
>     RNG function
>      > +should return 1 if the random data was generated, or 0 if the
>     random data could
>      > +not be generated.
>      > +
>      > +On platforms where there is no predefined RNG function (eg
>     embedded platforms),
>      > +this must be called before mg_uecc_make_key() or mg_uecc_sign()
>     are used.
>      > +
>      > +Inputs:
>      > + rng_function - The function that will be used to generate
>     random bytes.
>      > +*/
>      > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
>      > +
>      > +/* mg_uecc_get_rng() function.
>      > +
>      > +Returns the function that will be used to generate random bytes.
>      > +*/
>      > +MG_UECC_RNG_Function mg_uecc_get_rng(void);
>      > +
>      > +/* mg_uecc_curve_private_key_size() function.
>      > +
>      > +Returns the size of a private key for the curve in bytes.
>      > +*/
>      > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_curve_public_key_size() function.
>      > +
>      > +Returns the size of a public key for the curve in bytes.
>      > +*/
>      > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_make_key() function.
>      > +Create a public/private key pair.
>      > +
>      > +Outputs:
>      > + public_key - Will be filled in with the public key. Must be at
>     least 2 *
>      > +the curve size (in bytes) long. For example, if the curve is
>     secp256r1,
>      > +public_key must be 64 bytes long. private_key - Will be filled
>     in with the
>      > +private key. Must be as long as the curve order; this is
>     typically the same as
>      > +the curve size, except for secp160r1. For example, if the curve
>     is secp256r1,
>      > +private_key must be 32 bytes long.
>      > +
>      > + For secp160r1, private_key must be 21 bytes long! Note that
>      > +the first byte will almost always be 0 (there is about a 1 in
>     2^80 chance of it
>      > +being non-zero).
>      > +
>      > +Returns 1 if the key pair was generated successfully, 0 if an
>     error occurred.
>      > +*/
>      > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
>      > + MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_shared_secret() function.
>      > +Compute a shared secret given your secret key and someone else's
>     public key. If
>      > +the public key is not from a trusted source and has not been
>     previously
>      > +verified, you should verify it first using
>     mg_uecc_valid_public_key(). Note: It
>      > +is recommended that you hash the result of
>     mg_uecc_shared_secret() before using
>      > +it for symmetric encryption or HMAC.
>      > +
>      > +Inputs:
>      > + public_key - The public key of the remote party.
>      > + private_key - Your private key.
>      > +
>      > +Outputs:
>      > + secret - Will be filled in with the shared secret value. Must
>     be the same
>      > +size as the curve size; for example, if the curve is secp256r1,
>     secret must be
>      > +32 bytes long.
>      > +
>      > +Returns 1 if the shared secret was generated successfully, 0 if
>     an error
>      > +occurred.
>      > +*/
>      > +int mg_uecc_shared_secret(const uint8_t *public_key, const
>     uint8_t *private_key,
>      > + uint8_t *secret, MG_UECC_Curve curve);
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +/* mg_uecc_compress() function.
>      > +Compress a public key.
>      > +
>      > +Inputs:
>      > + public_key - The public key to compress.
>      > +
>      > +Outputs:
>      > + compressed - Will be filled in with the compressed public key.
>     Must be at
>      > +least (curve size + 1) bytes long; for example, if the curve is
>     secp256r1,
>      > + compressed must be 33 bytes long.
>      > +*/
>      > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
>     *compressed,
>      > + MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_decompress() function.
>      > +Decompress a compressed public key.
>      > +
>      > +Inputs:
>      > + compressed - The compressed public key.
>      > +
>      > +Outputs:
>      > + public_key - Will be filled in with the decompressed public key.
>      > +*/
>      > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
>     *public_key,
>      > + MG_UECC_Curve curve);
>      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>      > +
>      > +/* mg_uecc_valid_public_key() function.
>      > +Check to see if a public key is valid.
>      > +
>      > +Note that you are not required to check for a valid public key
>     before using any
>      > +other uECC functions. However, you may wish to avoid spending
>     CPU time computing
>      > +a shared secret or verifying a signature using an invalid public
>     key.
>      > +
>      > +Inputs:
>      > + public_key - The public key to check.
>      > +
>      > +Returns 1 if the public key is valid, 0 if it is invalid.
>      > +*/
>      > +int mg_uecc_valid_public_key(const uint8_t *public_key,
>     MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_compute_public_key() function.
>      > +Compute the corresponding public key for a private key.
>      > +
>      > +Inputs:
>      > + private_key - The private key to compute the public key for
>      > +
>      > +Outputs:
>      > + public_key - Will be filled in with the corresponding public key
>      > +
>      > +Returns 1 if the key was computed successfully, 0 if an error
>     occurred.
>      > +*/
>      > +int mg_uecc_compute_public_key(const uint8_t *private_key,
>     uint8_t *public_key,
>      > + MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_sign() function.
>      > +Generate an ECDSA signature for a given hash value.
>      > +
>      > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
>     recommended) and
>      > +pass it in to this function along with your private key.
>      > +
>      > +Inputs:
>      > + private_key - Your private key.
>      > + message_hash - The hash of the message to sign.
>      > + hash_size - The size of message_hash in bytes.
>      > +
>      > +Outputs:
>      > + signature - Will be filled in with the signature value. Must be
>     at least 2 *
>      > +curve size long. For example, if the curve is secp256r1,
>     signature must be 64
>      > +bytes long.
>      > +
>      > +Returns 1 if the signature generated successfully, 0 if an error
>     occurred.
>      > +*/
>      > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
>     *message_hash,
>      > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
>      > +
>      > +/* MG_UECC_HashContext structure.
>      > +This is used to pass in an arbitrary hash function to
>      > +mg_uecc_sign_deterministic(). The structure will be used for
>     multiple hash
>      > +computations; each time a new hash is computed, init_hash() will
>     be called,
>      > +followed by one or more calls to update_hash(), and finally a
>     call to
>      > +finish_hash() to produce the resulting hash.
>      > +
>      > +The intention is that you will create a structure that includes
>      > +MG_UECC_HashContext followed by any hash-specific data. For
>     example:
>      > +
>      > +typedef struct SHA256_HashContext {
>      > + MG_UECC_HashContext uECC;
>      > + SHA256_CTX ctx;
>      > +} SHA256_HashContext;
>      > +
>      > +void init_SHA256(MG_UECC_HashContext *base) {
>      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>      > + SHA256_Init(&context->ctx);
>      > +}
>      > +
>      > +void update_SHA256(MG_UECC_HashContext *base,
>      > + const uint8_t *message,
>      > + unsigned message_size) {
>      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>      > + SHA256_Update(&context->ctx, message, message_size);
>      > +}
>      > +
>      > +void finish_SHA256(MG_UECC_HashContext *base, uint8_t
>     *hash_result) {
>      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>      > + SHA256_Final(hash_result, &context->ctx);
>      > +}
>      > +
>      > +... when signing ...
>      > +{
>      > + uint8_t tmp[32 + 32 + 64];
>      > + SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256,
>     &finish_SHA256, 64,
>      > +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash,
>     &ctx.uECC, signature);
>      > +}
>      > +*/
>      > +typedef struct MG_UECC_HashContext {
>      > + void (*init_hash)(const struct MG_UECC_HashContext *context);
>      > + void (*update_hash)(const struct MG_UECC_HashContext *context,
>      > + const uint8_t *message, unsigned message_size);
>      > + void (*finish_hash)(const struct MG_UECC_HashContext *context,
>      > + uint8_t *hash_result);
>      > + unsigned
>      > + block_size; /* Hash function block size in bytes, eg 64 for
>     SHA-256. */
>      > + unsigned
>      > + result_size; /* Hash function result size in bytes, eg 32 for
>     SHA-256. */
>      > + uint8_t *tmp; /* Must point to a buffer of at least (2 *
>     result_size +
>      > + block_size) bytes. */
>      > +} MG_UECC_HashContext;
>      > +
>      > +/* mg_uecc_sign_deterministic() function.
>      > +Generate an ECDSA signature for a given hash value, using a
>     deterministic
>      > +algorithm (see RFC 6979). You do not need to set the RNG using
>     mg_uecc_set_rng()
>      > +before calling this function; however, if the RNG is defined it
>     will improve
>      > +resistance to side-channel attacks.
>      > +
>      > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
>     recommended) and
>      > +pass it to this function along with your private key and a hash
>     context. Note
>      > +that the message_hash does not need to be computed with the same
>     hash function
>      > +used by hash_context.
>      > +
>      > +Inputs:
>      > + private_key - Your private key.
>      > + message_hash - The hash of the message to sign.
>      > + hash_size - The size of message_hash in bytes.
>      > + hash_context - A hash context to use.
>      > +
>      > +Outputs:
>      > + signature - Will be filled in with the signature value.
>      > +
>      > +Returns 1 if the signature generated successfully, 0 if an error
>     occurred.
>      > +*/
>      > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
>      > + const uint8_t *message_hash, unsigned hash_size,
>      > + const MG_UECC_HashContext *hash_context,
>      > + uint8_t *signature, MG_UECC_Curve curve);
>      > +
>      > +/* mg_uecc_verify() function.
>      > +Verify an ECDSA signature.
>      > +
>      > +Usage: Compute the hash of the signed data using the same hash
>     as the signer and
>      > +pass it to this function along with the signer's public key and
>     the signature
>      > +values (r and s).
>      > +
>      > +Inputs:
>      > + public_key - The signer's public key.
>      > + message_hash - The hash of the signed data.
>      > + hash_size - The size of message_hash in bytes.
>      > + signature - The signature value.
>      > +
>      > +Returns 1 if the signature is valid, 0 if it is invalid.
>      > +*/
>      > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
>     *message_hash,
>      > + unsigned hash_size, const uint8_t *signature,
>      > + MG_UECC_Curve curve);
>      > +
>      > +#ifdef __cplusplus
>      > +} /* end of extern "C" */
>      > +#endif
>      > +
>      > +#endif /* _UECC_H_ */
>      > +
>      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>     2-clause license. */
>      > +
>      > +#ifndef _UECC_VLI_H_
>      > +#define _UECC_VLI_H_
>      > +
>      > +//
>      > +//
>      > +
>      > +/* Functions for raw large-integer manipulation. These are only
>     available
>      > + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
>      > +#ifndef MG_UECC_ENABLE_VLI_API
>      > +#define MG_UECC_ENABLE_VLI_API 0
>      > +#endif
>      > +
>      > +#ifdef __cplusplus
>      > +extern "C" {
>      > +#endif
>      > +
>      > +#if MG_UECC_ENABLE_VLI_API
>      > +
>      > +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
>      > +
>      > +/* Constant-time comparison to zero - secure way to compare long
>     integers */
>      > +/* Returns 1 if vli == 0, 0 otherwise. */
>      > +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
>      > + wordcount_t num_words);
>      > +
>      > +/* Returns nonzero if bit 'bit' of vli is set. */
>      > +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli,
>     bitcount_t bit);
>      > +
>      > +/* Counts the number of bits required to represent vli. */
>      > +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
>      > + const wordcount_t max_words);
>      > +
>      > +/* Sets dest = src. */
>      > +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t
>     *src,
>      > + wordcount_t num_words);
>      > +
>      > +/* Constant-time comparison function - secure way to compare
>     long integers */
>      > +/* Returns one if left == right, zero otherwise */
>      > +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words);
>      > +
>      > +/* Constant-time comparison function - secure way to compare
>     long integers */
>      > +/* Returns sign of left - right, in constant time. */
>      > +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, wordcount_t num_words);
>      > +
>      > +/* Computes vli = vli >> 1. */
>      > +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t
>     num_words);
>      > +
>      > +/* Computes result = left + right, returning carry. Can modify
>     in place. */
>      > +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = left - right, returning borrow. Can modify
>     in place. */
>      > +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = left * right. Result must be 2 * num_words
>     long. */
>      > +void mg_uecc_vli_mult(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, wordcount_t num_words);
>      > +
>      > +/* Computes result = left^2. Result must be 2 * num_words long. */
>      > +void mg_uecc_vli_square(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = (left + right) % mod.
>      > + Assumes that left < mod and right < mod, and that result does
>     not overlap
>      > + mod. */
>      > +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = (left - right) % mod.
>      > + Assumes that left < mod and right < mod, and that result does
>     not overlap
>      > + mod. */
>      > +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = product % mod, where product is 2N words long.
>      > + Currently only designed to work for mod == curve->p or curve_n. */
>      > +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t
>     *product,
>      > + const mg_uecc_word_t *mod, wordcount_t num_words);
>      > +
>      > +/* Calculates result = product (mod curve->p), where product is
>     up to
>      > + 2 * curve->num_words long. */
>      > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
>     mg_uecc_word_t *product,
>      > + MG_UECC_Curve curve);
>      > +
>      > +/* Computes result = (left * right) % mod.
>      > + Currently only designed to work for mod == curve->p or curve_n. */
>      > +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>      > + wordcount_t num_words);
>      > +
>      > +/* Computes result = (left * right) % curve->p. */
>      > +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *right, MG_UECC_Curve curve);
>      > +
>      > +/* Computes result = left^2 % mod.
>      > + Currently only designed to work for mod == curve->p or curve_n. */
>      > +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const
>     mg_uecc_word_t *left,
>      > + const mg_uecc_word_t *mod, wordcount_t num_words);
>      > +
>      > +/* Computes result = left^2 % curve->p. */
>      > +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
>      > + const mg_uecc_word_t *left,
>      > + MG_UECC_Curve curve);
>      > +
>      > +/* Computes result = (1 / input) % mod.*/
>      > +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const
>     mg_uecc_word_t *input,
>      > + const mg_uecc_word_t *mod, wordcount_t num_words);
>      > +
>      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>      > +/* Calculates a = sqrt(a) (mod curve->p) */
>      > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
>      > +#endif
>      > +
>      > +/* Converts an integer in uECC native format to big-endian
>     bytes. */
>      > +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
>      > + const mg_uecc_word_t *native);
>      > +/* Converts big-endian bytes to an integer in uECC native
>     format. */
>      > +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const
>     uint8_t *bytes,
>      > + int num_bytes);
>      > +
>      > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
>      > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
>      > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
>      > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
>      > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
>      > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
>      > +
>      > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
>      > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
>      > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
>      > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
>      > +
>      > +int mg_uecc_valid_point(const mg_uecc_word_t *point,
>     MG_UECC_Curve curve);
>      > +
>      > +/* Multiplies a point by a scalar. Points are represented by the
>     X coordinate
>      > + followed by the Y coordinate in the same array, both
>     coordinates are
>      > + curve->num_words long. Note that scalar must be
>     curve->num_n_words long (NOT
>      > + curve->num_words). */
>      > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
>     mg_uecc_word_t *point,
>      > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
>      > +
>      > +/* Generates a random integer in the range 0 < random < top.
>      > + Both random and top have num_words words. */
>      > +int mg_uecc_generate_random_int(mg_uecc_word_t *random,
>      > + const mg_uecc_word_t *top,
>      > + wordcount_t num_words);
>      > +
>      > +#endif /* MG_UECC_ENABLE_VLI_API */
>      > +
>      > +#ifdef __cplusplus
>      > +} /* end of extern "C" */
>      > +#endif
>      > +
>      > +#endif /* _UECC_VLI_H_ */
>      > +
>      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>     2-clause license. */
>      > +
>      > +#ifndef _UECC_TYPES_H_
>      > +#define _UECC_TYPES_H_
>      > +
>      > +#ifndef MG_UECC_PLATFORM
>      > +#if defined(__AVR__) && __AVR__
>      > +#define MG_UECC_PLATFORM mg_uecc_avr
>      > +#elif defined(__thumb2__) || \
>      > + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
>      > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
>      > +#elif defined(__thumb__)
>      > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb
>      > +#elif defined(__arm__) || defined(_M_ARM)
>      > +#define MG_UECC_PLATFORM mg_uecc_arm
>      > +#elif defined(__aarch64__)
>      > +#define MG_UECC_PLATFORM mg_uecc_arm64
>      > +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
>      > + defined(__I86__)
>      > +#define MG_UECC_PLATFORM mg_uecc_x86
>      > +#elif defined(__amd64__) || defined(_M_X64)
>      > +#define MG_UECC_PLATFORM mg_uecc_x86_64
>      > +#else
>      > +#define MG_UECC_PLATFORM mg_uecc_arch_other
>      > +#endif
>      > +#endif
>      > +
>      > +#ifndef MG_UECC_ARM_USE_UMAAL
>      > +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
>      > +#define MG_UECC_ARM_USE_UMAAL 1
>      > +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >=
>     6) && \
>      > + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
>      > +#define MG_UECC_ARM_USE_UMAAL 1
>      > +#else
>      > +#define MG_UECC_ARM_USE_UMAAL 0
>      > +#endif
>      > +#endif
>      > +
>      > +#ifndef MG_UECC_WORD_SIZE
>      > +#if MG_UECC_PLATFORM == mg_uecc_avr
>      > +#define MG_UECC_WORD_SIZE 1
>      > +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM ==
>     mg_uecc_arm64)
>      > +#define MG_UECC_WORD_SIZE 8
>      > +#else
>      > +#define MG_UECC_WORD_SIZE 4
>      > +#endif
>      > +#endif
>      > +
>      > +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
>      > + (MG_UECC_WORD_SIZE != 8)
>      > +#error "Unsupported value for MG_UECC_WORD_SIZE"
>      > +#endif
>      > +
>      > +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
>      > +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
>      > +#undef MG_UECC_WORD_SIZE
>      > +#define MG_UECC_WORD_SIZE 1
>      > +#endif
>      > +
>      > +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \
>      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
>      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
>      > + (MG_UECC_WORD_SIZE != 4))
>      > +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
>      > +#undef MG_UECC_WORD_SIZE
>      > +#define MG_UECC_WORD_SIZE 4
>      > +#endif
>      > +
>      > +typedef int8_t wordcount_t;
>      > +typedef int16_t bitcount_t;
>      > +typedef int8_t cmpresult_t;
>      > +
>      > +#if (MG_UECC_WORD_SIZE == 1)
>      > +
>      > +typedef uint8_t mg_uecc_word_t;
>      > +typedef uint16_t mg_uecc_dword_t;
>      > +
>      > +#define HIGH_BIT_SET 0x80
>      > +#define MG_UECC_WORD_BITS 8
>      > +#define MG_UECC_WORD_BITS_SHIFT 3
>      > +#define MG_UECC_WORD_BITS_MASK 0x07
>      > +
>      > +#elif (MG_UECC_WORD_SIZE == 4)
>      > +
>      > +typedef uint32_t mg_uecc_word_t;
>      > +typedef uint64_t mg_uecc_dword_t;
>      > +
>      > +#define HIGH_BIT_SET 0x80000000
>      > +#define MG_UECC_WORD_BITS 32
>      > +#define MG_UECC_WORD_BITS_SHIFT 5
>      > +#define MG_UECC_WORD_BITS_MASK 0x01F
>      > +
>      > +#elif (MG_UECC_WORD_SIZE == 8)
>      > +
>      > +typedef uint64_t mg_uecc_word_t;
>      > +
>      > +#define HIGH_BIT_SET 0x8000000000000000U
>      > +#define MG_UECC_WORD_BITS 64
>      > +#define MG_UECC_WORD_BITS_SHIFT 6
>      > +#define MG_UECC_WORD_BITS_MASK 0x03F
>      > +
>      > +#endif /* MG_UECC_WORD_SIZE */
>      > +
>      > +#endif /* _UECC_TYPES_H_ */
>      > +// End of uecc BSD-2
>      >
>      >
>      > struct mg_connection;
>      > typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
>      > - void *ev_data, void *fn_data);
>      > + void *ev_data);
>      > void mg_call(struct mg_connection *c, int ev, void *ev_data);
>      > void mg_error(struct mg_connection *c, const char *fmt, ...);
>      >
>      > enum {
>      > - MG_EV_ERROR, // Error char *error_message
>      > - MG_EV_OPEN, // Connection created NULL
>      > - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
>      > - MG_EV_RESOLVE, // Host name is resolved NULL
>      > - MG_EV_CONNECT, // Connection established NULL
>      > - MG_EV_ACCEPT, // Connection accepted NULL
>      > - MG_EV_TLS_HS, // TLS handshake succeeded NULL
>      > - MG_EV_READ, // Data received from socket long *bytes_read
>      > - MG_EV_WRITE, // Data written to socket long *bytes_written
>      > - MG_EV_CLOSE, // Connection closed NULL
>      > - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message *
>      > - MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct
>     mg_http_message *
>      > - MG_EV_WS_OPEN, // Websocket handshake done struct
>     mg_http_message *
>      > - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
>      > - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
>      > - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
>      > - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
>      > - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
>      > - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
>      > - MG_EV_USER // Starting ID for user events
>      > + MG_EV_ERROR, // Error char *error_message
>      > + MG_EV_OPEN, // Connection created NULL
>      > + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
>      > + MG_EV_RESOLVE, // Host name is resolved NULL
>      > + MG_EV_CONNECT, // Connection established NULL
>      > + MG_EV_ACCEPT, // Connection accepted NULL
>      > + MG_EV_TLS_HS, // TLS handshake succeeded NULL
>      > + MG_EV_READ, // Data received from socket long *bytes_read
>      > + MG_EV_WRITE, // Data written to socket long *bytes_written
>      > + MG_EV_CLOSE, // Connection closed NULL
>      > + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message *
>      > + MG_EV_HTTP_MSG, // Full HTTP request/response struct
>     mg_http_message *
>      > + MG_EV_WS_OPEN, // Websocket handshake done struct
>     mg_http_message *
>      > + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
>      > + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
>      > + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
>      > + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
>      > + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
>      > + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
>      > + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
>      > + MG_EV_USER // Starting ID for user events
>      > };
>      >
>      >
>      > @@ -1021,10 +1981,10 @@ struct mg_dns {
>      > };
>      >
>      > struct mg_addr {
>      > - uint16_t port; // TCP or UDP port in network byte order
>      > - uint32_t ip; // IP address in network byte order
>      > - uint8_t ip6[16]; // IPv6 address
>      > - bool is_ip6; // True when address is IPv6 address
>      > + uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte
>     order
>      > + uint16_t port; // TCP or UDP port in network byte order
>      > + uint8_t scope_id; // IPv6 scope ID
>      > + bool is_ip6; // True when address is IPv6 address
>      > };
>      >
>      > struct mg_mgr {
>      > @@ -1036,12 +1996,14 @@ struct mg_mgr {
>      > unsigned long nextid; // Next connection ID
>      > unsigned long timerid; // Next timer ID
>      > void *userdata; // Arbitrary user data pointer
>      > + void *tls_ctx; // TLS context shared by all TLS sessions
>      > uint16_t mqtt_id; // MQTT IDs for pub/sub
>      > void *active_dns_requests; // DNS requests in progress
>      > struct mg_timer *timers; // Active timers
>      > int epoll_fd; // Used when MG_EPOLL_ENABLE=1
>      > void *priv; // Used by the MIP stack
>      > size_t extraconnsize; // Used by the MIP stack
>      > + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup()
>      > #if MG_ENABLE_FREERTOS_TCP
>      > SocketSet_t ss; // NOTE(lsm): referenced from socket struct
>      > #endif
>      > @@ -1056,6 +2018,8 @@ struct mg_connection {
>      > unsigned long id; // Auto-incrementing unique connection ID
>      > struct mg_iobuf recv; // Incoming data
>      > struct mg_iobuf send; // Outgoing data
>      > + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE
>      > + struct mg_iobuf rtls; // TLS only. Incoming encrypted data
>      > mg_event_handler_t fn; // User-specified event handler function
>      > void *fn_data; // User-specified function parameter
>      > mg_event_handler_t pfn; // Protocol-specific handler function
>      > @@ -1066,6 +2030,7 @@ struct mg_connection {
>      > unsigned is_client : 1; // Outbound (client) connection
>      > unsigned is_accepted : 1; // Accepted (server) connection
>      > unsigned is_resolving : 1; // Non-blocking DNS resolution is in
>     progress
>      > + unsigned is_arplooking : 1; // Non-blocking ARP resolution is
>     in progress
>      > unsigned is_connecting : 1; // Non-blocking connect is in progress
>      > unsigned is_tls : 1; // TLS-enabled connection
>      > unsigned is_tls_hs : 1; // TLS handshake is in progress
>      > @@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *,
>     const void *, size_t);
>      > size_t mg_printf(struct mg_connection *, const char *fmt, ...);
>      > size_t mg_vprintf(struct mg_connection *, const char *fmt,
>     va_list *ap);
>      > bool mg_aton(struct mg_str str, struct mg_addr *addr);
>      > -int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool
>     udp);
>      >
>      > // These functions are used to integrate with custom network stacks
>      > struct mg_connection *mg_alloc_conn(struct mg_mgr *);
>      > void mg_close_conn(struct mg_connection *c);
>      > bool mg_open_listener(struct mg_connection *c, const char *url);
>      > +
>      > +// Utility functions
>      > +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void
>     *buf, size_t len);
>      > +bool mg_wakeup_init(struct mg_mgr *);
>      > struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>     milliseconds,
>      > unsigned flags, void (*fn)(void *), void *arg);
>      >
>      > -// Low-level IO primives used by TLS layer
>      > -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
>      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len);
>      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
>      > -
>      >
>      >
>      >
>      > @@ -1127,7 +2090,6 @@ struct mg_http_message {
>      > struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers
>      > struct mg_str body; // Body
>      > struct mg_str head; // Request + headers
>      > - struct mg_str chunk; // Chunk for chunked encoding, or partial
>     body
>      > struct mg_str message; // Request + headers + body
>      > };
>      >
>      > @@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *,
>     const char *name, char *, size_t);
>      > int mg_url_decode(const char *s, size_t n, char *to, size_t
>     to_len, int form);
>      > size_t mg_url_encode(const char *s, size_t n, char *buf, size_t
>     len);
>      > void mg_http_creds(struct mg_http_message *, char *, size_t, char
>     *, size_t);
>      > -bool mg_http_match_uri(const struct mg_http_message *, const
>     char *glob);
>      > long mg_http_upload(struct mg_connection *c, struct
>     mg_http_message *hm,
>      > - struct mg_fs *fs, const char *path, size_t max_size);
>      > + struct mg_fs *fs, const char *dir, size_t max_size);
>      > void mg_http_bauth(struct mg_connection *, const char *user,
>     const char *pass);
>      > struct mg_str mg_http_get_header_var(struct mg_str s, struct
>     mg_str v);
>      > size_t mg_http_next_multipart(struct mg_str, size_t, struct
>     mg_http_part *);
>      > @@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct
>     mg_connection *c, const char *root,
>      > const char *fullpath);
>      >
>      >
>      > +#define MG_TLS_NONE 0 // No TLS support
>      > +#define MG_TLS_MBED 1 // mbedTLS
>      > +#define MG_TLS_OPENSSL 2 // OpenSSL
>      > +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL)
>      > +#define MG_TLS_BUILTIN 3 // Built-in
>      > +#define MG_TLS_CUSTOM 4 // Custom implementation
>      > +
>      > +#ifndef MG_TLS
>      > +#define MG_TLS MG_TLS_NONE
>      > +#endif
>      > +
>      >
>      >
>      >
>      >
>      > struct mg_tls_opts {
>      > - const char *ca; // CA certificate file. For both listeners and
>     clients
>      > - const char *crl; // Certificate Revocation List. For clients
>      > - const char *cert; // Certificate
>      > - const char *certkey; // Certificate key
>      > - const char *ciphers; // Cipher list
>      > - struct mg_str srvname; // If not empty, enables server name
>     verification
>      > - struct mg_fs *fs; // FS API for reading certificate files
>      > + struct mg_str ca; // PEM or DER
>      > + struct mg_str cert; // PEM or DER
>      > + struct mg_str key; // PEM or DER
>      > + struct mg_str name; // If not empty, enable host name verification
>      > + int skip_verification; // Skip certificate and host name
>     verification
>      > };
>      >
>      > -void mg_tls_init(struct mg_connection *, const struct
>     mg_tls_opts *);
>      > +void mg_tls_init(struct mg_connection *, const struct
>     mg_tls_opts *opts);
>      > void mg_tls_free(struct mg_connection *);
>      > long mg_tls_send(struct mg_connection *, const void *buf, size_t
>     len);
>      > long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
>      > size_t mg_tls_pending(struct mg_connection *);
>      > void mg_tls_handshake(struct mg_connection *);
>      >
>      > +// Private
>      > +void mg_tls_ctx_init(struct mg_mgr *);
>      > +void mg_tls_ctx_free(struct mg_mgr *);
>      > +
>      > +// Low-level IO primives used by TLS layer
>      > +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
>      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>     len);
>      > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
>      >
>      >
>      >
>      >
>      >
>      >
>      > -#if MG_ENABLE_MBEDTLS
>      > +
>      > +#if MG_TLS == MG_TLS_MBED
>      > #include <mbedtls/debug.h>
>      > #include <mbedtls/net_sockets.h>
>      > #include <mbedtls/ssl.h>
>      > +#include <mbedtls/ssl_ticket.h>
>      > +
>      > +struct mg_tls_ctx {
>      > + int dummy;
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + mbedtls_ssl_ticket_context tickets;
>      > +#endif
>      > +};
>      >
>      > struct mg_tls {
>      > - char *cafile; // CA certificate path
>      > mbedtls_x509_crt ca; // Parsed CA certificate
>      > mbedtls_x509_crt cert; // Parsed certificate
>      > + mbedtls_pk_context pk; // Private key context
>      > mbedtls_ssl_context ssl; // SSL/TLS context
>      > mbedtls_ssl_config conf; // SSL-TLS config
>      > - mbedtls_pk_context pk; // Private key context
>      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>      > + mbedtls_ssl_ticket_context ticket; // Session tickets context
>      > +#endif
>      > };
>      > #endif
>      >
>      >
>      > -#if MG_ENABLE_OPENSSL
>      > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>      >
>      > -#ifdef CONFIG_SSL_IMPL_WOLFSSL
>      > -#include <wolfssl/openssl/err.h>
>      > -#include <wolfssl/openssl/ssl.h>
>      > -#else
>      > #include <openssl/err.h>
>      > #include <openssl/ssl.h>
>      > -#endif
>      >
>      > struct mg_tls {
>      > + BIO_METHOD *bm;
>      > SSL_CTX *ctx;
>      > SSL *ssl;
>      > };
>      > @@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char
>     *buf, size_t len);
>      > #define MQTT_CMD_DISCONNECT 14
>      > #define MQTT_CMD_AUTH 15
>      >
>      > +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
>      > +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
>      > +#define MQTT_PROP_CONTENT_TYPE 0x03
>      > +#define MQTT_PROP_RESPONSE_TOPIC 0x08
>      > +#define MQTT_PROP_CORRELATION_DATA 0x09
>      > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
>      > +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
>      > +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
>      > +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
>      > +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
>      > +#define MQTT_PROP_AUTHENTICATION_DATA 0x16
>      > +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
>      > +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
>      > +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
>      > +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
>      > +#define MQTT_PROP_SERVER_REFERENCE 0x1C
>      > +#define MQTT_PROP_REASON_STRING 0x1F
>      > +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
>      > +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
>      > +#define MQTT_PROP_TOPIC_ALIAS 0x23
>      > +#define MQTT_PROP_MAXIMUM_QOS 0x24
>      > +#define MQTT_PROP_RETAIN_AVAILABLE 0x25
>      > +#define MQTT_PROP_USER_PROPERTY 0x26
>      > +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
>      > +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
>      > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
>      > +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
>      > +
>      > +enum {
>      > + MQTT_PROP_TYPE_BYTE,
>      > + MQTT_PROP_TYPE_STRING,
>      > + MQTT_PROP_TYPE_STRING_PAIR,
>      > + MQTT_PROP_TYPE_BINARY_DATA,
>      > + MQTT_PROP_TYPE_VARIABLE_INT,
>      > + MQTT_PROP_TYPE_INT,
>      > + MQTT_PROP_TYPE_SHORT
>      > +};
>      > +
>      > enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
>      >
>      > +struct mg_mqtt_prop {
>      > + uint8_t id; // Enumerated at MQTT5 Reference
>      > + uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types
>      > + struct mg_str key; // Non-NULL only for user property type
>      > + struct mg_str val; // Non-NULL only for UTF-8 types and user
>     properties
>      > +};
>      > +
>      > struct mg_mqtt_opts {
>      > - struct mg_str user; // Username, can be empty
>      > - struct mg_str pass; // Password, can be empty
>      > - struct mg_str client_id; // Client ID
>      > - struct mg_str will_topic; // Will topic
>      > - struct mg_str will_message; // Will message
>      > - uint8_t will_qos; // Will message quality of service
>      > - uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4.
>      > - uint16_t keepalive; // Keep-alive timer in seconds
>      > - bool will_retain; // Retain last will
>      > - bool clean; // Use clean session, 0 or 1
>      > + struct mg_str user; // Username, can be empty
>      > + struct mg_str pass; // Password, can be empty
>      > + struct mg_str client_id; // Client ID
>      > + struct mg_str topic; // message/subscription topic
>      > + struct mg_str message; // message content
>      > + uint8_t qos; // message quality of service
>      > + uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4
>      > + uint16_t keepalive; // Keep-alive timer in seconds
>      > + uint16_t retransmit_id; // For PUBLISH, init to 0
>      > + bool retain; // Retain flag
>      > + bool clean; // Clean session flag
>      > + struct mg_mqtt_prop *props; // MQTT5 props array
>      > + size_t num_props; // number of props
>      > + struct mg_mqtt_prop *will_props; // Valid only for CONNECT
>     packet (MQTT5)
>      > + size_t num_will_props; // Number of will props
>      > };
>      >
>      > struct mg_mqtt_message {
>      > - struct mg_str topic; // Parsed topic
>      > - struct mg_str data; // Parsed message
>      > - struct mg_str dgram; // Whole MQTT datagram, including headers
>      > - uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP,
>     SUBACK, PUBLISH
>      > - uint8_t cmd; // MQTT command, one of MQTT_CMD_*
>      > - uint8_t qos; // Quality of service
>      > - uint8_t ack; // Connack return code. 0 - success
>      > + struct mg_str topic; // Parsed topic for PUBLISH
>      > + struct mg_str data; // Parsed message for PUBLISH
>      > + struct mg_str dgram; // Whole MQTT packet, including headers
>      > + uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK,
>     PUBLISH
>      > + uint8_t cmd; // MQTT command, one of MQTT_CMD_*
>      > + uint8_t qos; // Quality of service
>      > + uint8_t ack; // CONNACK return code, 0 = success
>      > + size_t props_start; // Offset to the start of the properties
>     (MQTT5)
>      > + size_t props_size; // Length of the properties
>      > };
>      >
>      > struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char
>     *url,
>      > @@ -1327,15 +2364,16 @@ struct mg_connection
>     *mg_mqtt_connect(struct mg_mgr *, const char *url,
>      > struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>     char *url,
>      > mg_event_handler_t fn, void *fn_data);
>      > void mg_mqtt_login(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts);
>      > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
>      > - struct mg_str data, int qos, bool retain);
>      > -void mg_mqtt_sub(struct mg_connection *, struct mg_str topic,
>     int qos);
>      > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
>     mg_mqtt_opts *opts);
>      > +void mg_mqtt_sub(struct mg_connection *, const struct
>     mg_mqtt_opts *opts);
>      > int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct
>     mg_mqtt_message *);
>      > void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd,
>     uint8_t flags,
>      > uint32_t len);
>      > void mg_mqtt_ping(struct mg_connection *);
>      > void mg_mqtt_pong(struct mg_connection *);
>      > -void mg_mqtt_disconnect(struct mg_connection *);
>      > +void mg_mqtt_disconnect(struct mg_connection *, const struct
>     mg_mqtt_opts *);
>      > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct
>     mg_mqtt_prop *,
>      > + size_t ofs);
>      >
>      >
>      >
>      > @@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf,
>     size_t len, size_t ofs,
>      > enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2,
>     MG_JSON_NOT_FOUND = -3 };
>      > int mg_json_get(struct mg_str json, const char *path, int *toklen);
>      >
>      > +struct mg_str mg_json_get_tok(struct mg_str json, const char
>     *path);
>      > bool mg_json_get_num(struct mg_str json, const char *path, double
>     *v);
>      > bool mg_json_get_bool(struct mg_str json, const char *path, bool
>     *v);
>      > long mg_json_get_long(struct mg_str json, const char *path, long
>     dflt);
>      > @@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json,
>     const char *path);
>      > char *mg_json_get_hex(struct mg_str json, const char *path, int
>     *len);
>      > char *mg_json_get_b64(struct mg_str json, const char *path, int
>     *len);
>      >
>      > +bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
>      > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
>     *key,
>      > + struct mg_str *val);
>      > +
>      >
>      >
>      >
>      > @@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *,
>     const char *fmt, va_list *ap);
>      > void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt,
>     ...);
>      > void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt,
>     va_list *);
>      > void mg_rpc_list(struct mg_rpc_req *r);
>      > +// Copyright (c) 2023 Cesanta Software Limited
>      > +// All rights reserved
>      >
>      >
>      >
>      >
>      >
>      > -struct mip_if; // MIP network interface
>      > +#define MG_OTA_NONE 0 // No OTA support
>      > +#define MG_OTA_FLASH 1 // OTA via an internal flash
>      > +#define MG_OTA_ESP32 2 // ESP32 OTA implementation
>      > +#define MG_OTA_CUSTOM 100 // Custom implementation
>      >
>      > -struct mip_driver {
>      > - bool (*init)(struct mip_if *); // Initialise driver
>      > - size_t (*tx)(const void *, size_t, struct mip_if *); //
>     Transmit frame
>      > - size_t (*rx)(void *buf, size_t len, struct mip_if *); //
>     Receive frame (poll)
>      > - bool (*up)(struct mip_if *); // Up/down status
>      > -};
>      > +#ifndef MG_OTA
>      > +#define MG_OTA MG_OTA_NONE
>      > +#endif
>      > +
>      > +#if defined(__GNUC__) && !defined(__APPLE__)
>      > +#define MG_IRAM __attribute__((section(".iram")))
>      > +#else
>      > +#define MG_IRAM
>      > +#endif
>      >
>      > -// Receive queue - single producer, single consumer queue.
>     Interrupt-based
>      > -// drivers copy received frames to the queue in interrupt
>     context. mip_poll()
>      > -// function runs in event loop context, reads from the queue
>      > -struct queue {
>      > - uint8_t *buf;
>      > - size_t len;
>      > - volatile size_t tail, head;
>      > +// Firmware update API
>      > +bool mg_ota_begin(size_t new_firmware_size); // Start writing
>      > +bool mg_ota_write(const void *buf, size_t len); // Write chunk,
>     aligned to 1k
>      > +bool mg_ota_end(void); // Stop writing
>      > +
>      > +enum {
>      > + MG_OTA_UNAVAILABLE = 0, // No OTA information is present
>      > + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after
>     the OTA
>      > + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback
>      > + MG_OTA_COMMITTED = 3 // The firmware is good
>      > };
>      > +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
>      > +
>      > +int mg_ota_status(int firmware); // Return firmware status MG_OTA_*
>      > +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
>      > +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp,
>     UNIX UTC epoch
>      > +size_t mg_ota_size(int firmware); // Firmware size
>      > +
>      > +bool mg_ota_commit(void); // Commit current firmware
>      > +bool mg_ota_rollback(void); // Rollback to the previous firmware
>      > +MG_IRAM void mg_ota_boot(void); // Bootloader function
>      > +// Copyright (c) 2023 Cesanta Software Limited
>      > +// All rights reserved
>      >
>      > -#define MIP_ARP_ENTRIES 5 // Number of ARP cache entries.
>     Maximum 21
>      > -#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES) // ARP cache size
>      > +
>      > +
>      > +
>      > +
>      > +#define MG_DEVICE_NONE 0 // Dummy system
>      > +
>      > +#define MG_DEVICE_STM32H5 1 // STM32 H5
>      > +#define MG_DEVICE_STM32H7 2 // STM32 H7
>      > +#define MG_DEVICE_CH32V307 100 // WCH CH32V307
>      > +#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
>      > +#define MG_DEVICE_RT1020 300 // IMXRT1020
>      > +#define MG_DEVICE_RT1060 301 // IMXRT1060
>      > +#define MG_DEVICE_CUSTOM 1000 // Custom implementation
>      > +
>      > +#ifndef MG_DEVICE
>      > +#define MG_DEVICE MG_DEVICE_NONE
>      > +#endif
>      > +
>      > +// Flash information
>      > +void *mg_flash_start(void); // Return flash start address
>      > +size_t mg_flash_size(void); // Return flash size
>      > +size_t mg_flash_sector_size(void); // Return flash sector size
>      > +size_t mg_flash_write_align(void); // Return flash write align,
>     minimum 4
>      > +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
>      > +
>      > +// Write, erase, swap bank
>      > +bool mg_flash_write(void *addr, const void *buf, size_t len);
>      > +bool mg_flash_erase(void *sector);
>      > +bool mg_flash_swap_bank(void);
>      > +
>      > +// Convenience functions to store data on a flash sector with
>     wear levelling
>      > +// If `sector` is NULL, then the last sector of flash is used
>      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>     len);
>      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>     size_t len);
>      > +
>      > +void mg_device_reset(void); // Reboot device immediately
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +
>      > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
>      > +struct mg_tcpip_if; // Mongoose TCP/IP network interface
>      > +
>      > +struct mg_tcpip_driver {
>      > + bool (*init)(struct mg_tcpip_if *); // Init driver
>      > + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); //
>     Transmit frame
>      > + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); //
>     Receive frame
>      > + bool (*up)(struct mg_tcpip_if *); // Up/down status
>      > +};
>      >
>      > // Network interface
>      > -struct mip_if {
>      > - uint8_t mac[6]; // MAC address. Must be set to a valid MAC
>      > - uint32_t ip, mask, gw; // IP address, mask, default gateway
>      > - struct mg_str rx; // Output (TX) buffer
>      > - struct mg_str tx; // Input (RX) buffer
>      > - bool enable_dhcp_client; // Enable DCHP client
>      > - bool enable_dhcp_server; // Enable DCHP server
>      > - struct mip_driver *driver; // Low level driver
>      > - void *driver_data; // Driver-specific data
>      > - struct mg_mgr *mgr; // Mongoose event manager
>      > - struct queue queue; // Set queue.len for interrupt based drivers
>      > +struct mg_tcpip_if {
>      > + uint8_t mac[6]; // MAC address. Must be set to a valid MAC
>      > + uint32_t ip, mask, gw; // IP address, mask, default gateway
>      > + struct mg_str tx; // Output (TX) buffer
>      > + bool enable_dhcp_client; // Enable DCHP client
>      > + bool enable_dhcp_server; // Enable DCHP server
>      > + bool enable_get_gateway; // DCHP server sets client as gateway
>      > + bool enable_crc32_check; // Do a CRC check on RX frames and
>     strip it
>      > + bool enable_mac_check; // Do a MAC check on RX frames
>      > + struct mg_tcpip_driver *driver; // Low level driver
>      > + void *driver_data; // Driver-specific data
>      > + struct mg_mgr *mgr; // Mongoose event manager
>      > + struct mg_queue recv_queue; // Receive queue
>      > + uint16_t mtu; // Interface MTU
>      > +#define MG_TCPIP_MTU_DEFAULT 1500
>      >
>      > // Internal state, user can use it but should not change it
>      > - uint64_t now; // Current time
>      > - uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
>      > - uint64_t lease_expire; // Lease expiration time
>      > - uint8_t arp_cache[MIP_ARP_CS]; // Each entry is 12 bytes
>      > - uint16_t eport; // Next ephemeral port
>      > - uint16_t dropped; // Number of dropped frames
>      > - uint8_t state; // Current state
>      > -#define MIP_STATE_DOWN 0 // Interface is down
>      > -#define MIP_STATE_UP 1 // Interface is up
>      > -#define MIP_STATE_READY 2 // Interface is up and has IP
>      > + uint8_t gwmac[6]; // Router's MAC
>      > + uint64_t now; // Current time
>      > + uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
>      > + uint64_t lease_expire; // Lease expiration time, in ms
>      > + uint16_t eport; // Next ephemeral port
>      > + volatile uint32_t ndrop; // Number of received, but dropped frames
>      > + volatile uint32_t nrecv; // Number of received frames
>      > + volatile uint32_t nsent; // Number of transmitted frames
>      > + volatile uint32_t nerr; // Number of driver errors
>      > + uint8_t state; // Current state
>      > +#define MG_TCPIP_STATE_DOWN 0 // Interface is down
>      > +#define MG_TCPIP_STATE_UP 1 // Interface is up
>      > +#define MG_TCPIP_STATE_REQ 2 // Interface is up and has
>     requested an IP
>      > +#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP
>     assigned
>      > };
>      >
>      > -void mip_init(struct mg_mgr *, struct mip_if *);
>      > -void mip_free(struct mip_if *);
>      > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
>      > -size_t mip_qread(void *buf, struct mip_if *ifp);
>      > -// conveniency rx function for IRQ-driven drivers
>      > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
>      > -
>      > -extern struct mip_driver mip_driver_stm32;
>      > -extern struct mip_driver mip_driver_w5500;
>      > -extern struct mip_driver mip_driver_tm4c;
>      > +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
>      > +void mg_tcpip_free(struct mg_tcpip_if *);
>      > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
>     *ifp);
>      > +
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_same54;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_ra;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
>      > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
>      >
>      > // Drivers that require SPI, can use this SPI abstraction
>      > -struct mip_spi {
>      > +struct mg_tcpip_spi {
>      > void *spi; // Opaque SPI bus descriptor
>      > void (*begin)(void *); // SPI begin: slave select low
>      > void (*end)(void *); // SPI end: slave select high
>      > uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1
>     byte, read reply
>      > };
>      > +#endif
>      >
>      > -#ifdef MIP_QPROFILE
>      > -enum {
>      > - QP_IRQTRIGGERED = 0, // payload is number of interrupts so far
>      > - QP_FRAMEPUSHED, // available space in the frame queue
>      > - QP_FRAMEPOPPED, // available space in the frame queue
>      > - QP_FRAMEDONE, // available space in the frame queue
>      > - QP_FRAMEDROPPED, // number of dropped frames
>      > - QP_QUEUEOVF // profiling queue is full, payload is number of
>     frame drops
>      > +
>      > +
>      > +// Macros to record timestamped events that happens with a
>     connection.
>      > +// They are saved into a c->prof IO buffer, each event is a name
>     and a 32-bit
>      > +// timestamp in milliseconds since connection init time.
>      > +//
>      > +// Test (run in two separate terminals):
>      > +// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
>      > +// curl localhost:8000
>      > +// Output:
>      > +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile:
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE
>      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE
>      > +//
>      > +// Usage:
>      > +// Enable profiling by setting MG_ENABLE_PROFILE=1
>      > +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like
>     to measure
>      > +
>      > +#if MG_ENABLE_PROFILE
>      > +struct mg_profitem {
>      > + const char *name; // Event name
>      > + uint32_t timestamp; // Milliseconds since connection creation
>     (MG_EV_OPEN)
>      > };
>      >
>      > -void qp_mark(unsigned int type, int len);
>      > -void qp_log(void); // timestamp, type, payload
>      > -void qp_init(void);
>      > +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items
>     wih to realloc
>      > +
>      > +// Adding a profile item to the c->prof. Must be as fast as
>     possible.
>      > +// Reallocation of the c->prof iobuf is not desirable here,
>     that's why we
>      > +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
>      > +// This macro just inits and copies 8 bytes, and calls mg_millis(),
>      > +// which should be fast enough.
>      > +#define MG_PROF_ADD(c, name_) \
>      > + do { \
>      > + struct mg_iobuf *io = &c->prof; \
>      > + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \
>      > + struct mg_profitem item = {name_, (uint32_t) mg_millis() -
>     inittime}; \
>      > + mg_iobuf_add(io, io->len, &item, sizeof(item)); \
>      > + } while (0)
>      > +
>      > +// Initialising profile for a new connection. Not time sensitive
>      > +#define MG_PROF_INIT(c) \
>      > + do { \
>      > + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
>      > + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \
>      > + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \
>      > + } while (0)
>      > +
>      > +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
>      > +
>      > +// Dumping the profile. Not time sensitive
>      > +#define MG_PROF_DUMP(c) \
>      > + do { \
>      > + struct mg_iobuf *io = &c->prof; \
>      > + struct mg_profitem *p = (struct mg_profitem *) io->buf; \
>      > + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \
>      > + MG_INFO(("%lu profile:", c->id)); \
>      > + while (p < e) { \
>      > + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
>      > + p++; \
>      > + } \
>      > + } while (0)
>      > +
>      > #else
>      > -#define qp_mark(a, b)
>      > +#define MG_PROF_INIT(c)
>      > +#define MG_PROF_FREE(c)
>      > +#define MG_PROF_ADD(c, name)
>      > +#define MG_PROF_DUMP(c)
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
>     MG_ENABLE_DRIVER_CMSIS
>      > +
>      > +#include "Driver_ETH_MAC.h" // keep this include
>      > +#include "Driver_ETH_PHY.h" // keep this include
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
>     MG_ENABLE_DRIVER_IMXRT
>      > +
>      > +struct mg_tcpip_driver_imxrt_data {
>      > + // MDC clock divider. MDC clock is derived from IPS Bus clock
>     (ipg_clk),
>      > + // must not exceed 2.5MHz. Configuration for clock range
>     2.36~2.50 MHz
>      > + // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
>      > + // ipg_clk mdc_cr VALUE
>      > + // --------------------------
>      > + // -1 <-- TODO() tell driver to guess the value
>      > + // 25 MHz 4
>      > + // 33 MHz 6
>      > + // 40 MHz 7
>      > + // 50 MHz 9
>      > + // 66 MHz 13
>      > + int mdc_cr; // Valid values: -1 to 63
>      > +
>      > + uint8_t phy_addr; // PHY address
>      > +};
>      > +
>      > +#ifndef MG_TCPIP_PHY_ADDR
>      > +#define MG_TCPIP_PHY_ADDR 2
>      > +#endif
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 24
>      > +#endif
>      > +
>      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>      > + do { \
>      > + static struct mg_tcpip_driver_imxrt_data driver_data_; \
>      > + static struct mg_tcpip_if mif_; \
>      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>      > + mif_.ip = MG_TCPIP_IP; \
>      > + mif_.mask = MG_TCPIP_MASK; \
>      > + mif_.gw = MG_TCPIP_GW; \
>      > + mif_.driver = &mg_tcpip_driver_imxrt; \
>      > + mif_.driver_data = &driver_data_; \
>      > + MG_SET_MAC_ADDRESS(mif_.mac); \
>      > + mg_tcpip_init(mgr, &mif_); \
>      > + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
>      > + } while (0)
>      > +
>      > #endif
>      >
>      >
>      > -struct mip_driver_stm32_data {
>      > +
>      > +
>      > +struct mg_phy {
>      > + uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
>      > + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
>      > +};
>      > +
>      > +// PHY configuration settings, bitmask
>      > +enum {
>      > + MG_PHY_LEDS_ACTIVE_HIGH =
>      > + (1 << 0), // Set if PHY LEDs are connected to ground
>      > + MG_PHY_CLOCKS_MAC =
>      > + (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
>      > +};
>      > +
>      > +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
>      > +
>      > +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
>      > +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
>      > + uint8_t *speed);
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
>     MG_ENABLE_DRIVER_RA
>      > +
>      > +struct mg_tcpip_driver_ra_data {
>      > + // MDC clock "divider". MDC clock is software generated,
>      > + uint32_t clock; // core clock frequency in Hz
>      > + uint16_t irqno; // IRQn, R_ICU->IELSR[irqno]
>      > + uint8_t phy_addr; // PHY address
>      > +};
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
>     MG_ENABLE_DRIVER_SAME54
>      > +
>      > +struct mg_tcpip_driver_same54_data {
>      > + int mdc_cr;
>      > +};
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 5
>      > +#endif
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
>      > + MG_ENABLE_DRIVER_STM32F
>      > +
>      > +struct mg_tcpip_driver_stm32f_data {
>      > // MDC clock divider. MDC clock is derived from HCLK, must not
>     exceed 2.5MHz
>      > // HCLK range DIVIDER mdc_cr VALUE
>      > // -------------------------------------
>      > @@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
>      > // 216-310 MHz HCLK/124 5
>      > // 110, 111 Reserved
>      > int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>      > +
>      > + uint8_t phy_addr; // PHY address
>      > +};
>      > +
>      > +#ifndef MG_TCPIP_PHY_ADDR
>      > +#define MG_TCPIP_PHY_ADDR 0
>      > +#endif
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 4
>      > +#endif
>      > +
>      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>      > + do { \
>      > + static struct mg_tcpip_driver_stm32f_data driver_data_; \
>      > + static struct mg_tcpip_if mif_; \
>      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>      > + mif_.ip = MG_TCPIP_IP; \
>      > + mif_.mask = MG_TCPIP_MASK; \
>      > + mif_.gw = MG_TCPIP_GW; \
>      > + mif_.driver = &mg_tcpip_driver_stm32f; \
>      > + mif_.driver_data = &driver_data_; \
>      > + MG_SET_MAC_ADDRESS(mif_.mac); \
>      > + mg_tcpip_init(mgr, &mif_); \
>      > + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
>      > + } while (0)
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
>      > + MG_ENABLE_DRIVER_STM32H
>      > +
>      > +struct mg_tcpip_driver_stm32h_data {
>      > + // MDC clock divider. MDC clock is derived from HCLK, must not
>     exceed 2.5MHz
>      > + // HCLK range DIVIDER mdc_cr VALUE
>      > + // -------------------------------------
>      > + // -1 <-- tell driver to guess the value
>      > + // 60-100 MHz HCLK/42 0
>      > + // 100-150 MHz HCLK/62 1
>      > + // 20-35 MHz HCLK/16 2
>      > + // 35-60 MHz HCLK/26 3
>      > + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI
>      > + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI
>      > + // 110, 111 Reserved
>      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>      > +
>      > + uint8_t phy_addr; // PHY address
>      > + uint8_t phy_conf; // PHY config
>      > };
>      >
>      > +#ifndef MG_TCPIP_PHY_ADDR
>      > +#define MG_TCPIP_PHY_ADDR 0
>      > +#endif
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 4
>      > +#endif
>      > +
>      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>      > + do { \
>      > + static struct mg_tcpip_driver_stm32h_data driver_data_; \
>      > + static struct mg_tcpip_if mif_; \
>      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>      > + mif_.ip = MG_TCPIP_IP; \
>      > + mif_.mask = MG_TCPIP_MASK; \
>      > + mif_.gw = MG_TCPIP_GW; \
>      > + mif_.driver = &mg_tcpip_driver_stm32h; \
>      > + mif_.driver_data = &driver_data_; \
>      > + MG_SET_MAC_ADDRESS(mif_.mac); \
>      > + mg_tcpip_init(mgr, &mif_); \
>      > + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
>      > + } while (0)
>      > +
>      > +#endif
>      > +
>      >
>      > -struct mip_driver_tm4c_data {
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>     MG_ENABLE_DRIVER_TM4C
>      > +
>      > +struct mg_tcpip_driver_tm4c_data {
>      > // MDC clock divider. MDC clock is derived from SYSCLK, must not
>     exceed 2.5MHz
>      > // SYSCLK range DIVIDER mdc_cr VALUE
>      > // -------------------------------------
>      > @@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
>      > int mdc_cr; // Valid values: -1, 0, 1, 2, 3
>      > };
>      >
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 1
>      > +#endif
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
>     MG_ENABLE_DRIVER_W5500
>      > +
>      > +#undef MG_ENABLE_TCPIP_DRIVER_INIT
>      > +#define MG_ENABLE_TCPIP_DRIVER_INIT 0
>      > +
>      > +#endif
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
>     MG_ENABLE_DRIVER_XMC7
>      > +
>      > +struct mg_tcpip_driver_xmc7_data {
>      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>      > + uint8_t phy_addr;
>      > +};
>      > +
>      > +#ifndef MG_TCPIP_PHY_ADDR
>      > +#define MG_TCPIP_PHY_ADDR 0
>      > +#endif
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 3
>      > +#endif
>      > +
>      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>      > + do { \
>      > + static struct mg_tcpip_driver_xmc7_data driver_data_; \
>      > + static struct mg_tcpip_if mif_; \
>      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>      > + mif_.ip = MG_TCPIP_IP; \
>      > + mif_.mask = MG_TCPIP_MASK; \
>      > + mif_.gw = MG_TCPIP_GW; \
>      > + mif_.driver = &mg_tcpip_driver_xmc7; \
>      > + mif_.driver_data = &driver_data_; \
>      > + MG_SET_MAC_ADDRESS(mif_.mac); \
>      > + mg_tcpip_init(mgr, &mif_); \
>      > + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
>      > + } while (0)
>      > +
>      > +#endif
>      > +
>      > +
>      > +
>      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
>     MG_ENABLE_DRIVER_XMC
>      > +
>      > +struct mg_tcpip_driver_xmc_data {
>      > + // 13.2.8.1 Station Management Functions
>      > + // MDC clock divider (). MDC clock is derived from ETH MAC clock
>      > + // It must not exceed 2.5MHz
>      > + // ETH Clock range DIVIDER mdc_cr VALUE
>      > + // --------------------------------------------
>      > + // -1 <-- tell driver to guess the value
>      > + // 60-100 MHz ETH Clock/42 0
>      > + // 100-150 MHz ETH Clock/62 1
>      > + // 20-35 MHz ETH Clock/16 2
>      > + // 35-60 MHz ETH Clock/26 3
>      > + // 150-250 MHz ETH Clock/102 4
>      > + // 250-300 MHz ETH Clock/124 5
>      > + // 110, 111 Reserved
>      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>      > + uint8_t phy_addr;
>      > +};
>      > +
>      > +#ifndef MG_TCPIP_PHY_ADDR
>      > +#define MG_TCPIP_PHY_ADDR 0
>      > +#endif
>      > +
>      > +#ifndef MG_DRIVER_MDC_CR
>      > +#define MG_DRIVER_MDC_CR 4
>      > +#endif
>      > +
>      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>      > + do { \
>      > + static struct mg_tcpip_driver_xmc_data driver_data_; \
>      > + static struct mg_tcpip_if mif_; \
>      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>      > + mif_.ip = MG_TCPIP_IP; \
>      > + mif_.mask = MG_TCPIP_MASK; \
>      > + mif_.gw = MG_TCPIP_GW; \
>      > + mif_.driver = &mg_tcpip_driver_xmc; \
>      > + mif_.driver_data = &driver_data_; \
>      > + MG_SET_MAC_ADDRESS(mif_.mac); \
>      > + mg_tcpip_init(mgr, &mif_); \
>      > + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
>      > + } while (0)
>      > +
>      > +#endif
>      > +
>      > #ifdef __cplusplus
>      > }
>      > #endif
>
> --
> You received this message because you are subscribed to the Google
> Groups "swupdate" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to swupdate+unsubscribe@googlegroups.com
> <mailto:swupdate+unsubscribe@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com <https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com?utm_medium=email&utm_source=footer>.
Michael Glembotzki July 9, 2024, 7:36 p.m. UTC | #4
Hi Stefano,

Am Di., 9. Juli 2024 um 14:21 Uhr schrieb Stefano Babic
<stefano.babic@swupdate.org>:
>
> Hi Michael,
>
> On 09.07.24 13:31, Michael Glembotzki wrote:
> > Problem still exists with V3. The patch files I created with format
> > patch are OK, but when sending it to Patchwork something seems to break
> > with the header.
>
> It depends how you send the patch. You should still use git, "git
> send-email". This guarantees thatmails are not damaged by your mailer.

Sure, I used "git send-email" as always. Since I received a copy of
the patch files in CC, I cannot confirm that there is a problem on my
end. The patches were easy to apply with "git am" . Things are
different in patchwork. Everything is okay in the bottom view with
syntax highlighting, but the content at the top looks different. Is
there a size limit for patch files in Patchwork?

I can't recreate the cause and don't want to continue spamming with a
V4, so here are the commits in Github:
https://github.com/linkjumper/swupdate/tree/feature/migl/mongoose_update_V3

Is this suitable for the review?

Best regards
Michael



> Best regards,
> Stefano
>
> > I could post a github link, or do you have any idea
> > what the problem could be?
> >
> > Best regards
> > Michael
> > Stefano Babic schrieb am Sonntag, 7. Juli 2024 um 11:41:01 UTC+2:
> >
> >     Hi Michael,
> >
> >     I wanted to work on this, but I cannot apply clean, git am reports a
> >     malformed patch. Can you check and repost ? Thanks.
> >
> >     Best regards,
> >     Stefano
> >
> >     On 15.06.24 21:11, Michael Glembotzki wrote:
> >      > mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05
> >      >
> >      > Signed-off-by: Michael Glembotzki <Michael.G...@iris-sensing.com>
> >      > ---
> >      > mongoose/mongoose.c | 21614
> >     +++++++++++++++++++++++++++++++-----------
> >      > mongoose/mongoose.h | 2003 +++-
> >      > 2 files changed, 17594 insertions(+), 6023 deletions(-)
> >      >
> >      > diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
> >      > index 83d8fe18..3fc0e14a 100644
> >      > --- a/mongoose/mongoose.c
> >      > +++ b/mongoose/mongoose.c
> >      > @@ -1,5 +1,5 @@
> >      > // Copyright (c) 2004-2013 Sergey Lyubka
> >      > -// Copyright (c) 2013-2022 Cesanta Software Limited
> >      > +// Copyright (c) 2013-2024 Cesanta Software Limited
> >      > // All rights reserved
> >      > //
> >      > // This software is dual-licensed: you can redistribute it and/or
> >     modify
> >      > @@ -15,7 +15,7 @@
> >      > // Alternatively, you can license this software under a commercial
> >      > // license, as set out in https://www.mongoose.ws/licensing/
> >     <https://www.mongoose.ws/licensing/>
> >      > //
> >      > -// SPDX-License-Identifier: GPL-2.0-only
> >      > +// SPDX-License-Identifier: GPL-2.0-only or commercial
> >      >
> >      > #include "mongoose.h"
> >      >
> >      > @@ -24,8 +24,7 @@
> >      > #endif
> >      >
> >      >
> >      > -
> >      > -static int mg_b64idx(int c) {
> >      > +static int mg_base64_encode_single(int c) {
> >      > if (c < 26) {
> >      > return c + 'A';
> >      > } else if (c < 52) {
> >      > @@ -37,7 +36,7 @@ static int mg_b64idx(int c) {
> >      > }
> >      > }
> >      >
> >      > -static int mg_b64rev(int c) {
> >      > +static int mg_base64_decode_single(int c) {
> >      > if (c >= 'A' && c <= 'Z') {
> >      > return c - 'A';
> >      > } else if (c >= 'a' && c <= 'z') {
> >      > @@ -55,24 +54,24 @@ static int mg_b64rev(int c) {
> >      > }
> >      > }
> >      >
> >      > -int mg_base64_update(unsigned char ch, char *to, int n) {
> >      > - int rem = (n & 3) % 3;
> >      > +size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
> >      > + unsigned long rem = (n & 3) % 3;
> >      > if (rem == 0) {
> >      > - to[n] = (char) mg_b64idx(ch >> 2);
> >      > + to[n] = (char) mg_base64_encode_single(ch >> 2);
> >      > to[++n] = (char) ((ch & 3) << 4);
> >      > } else if (rem == 1) {
> >      > - to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
> >      > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
> >      > to[++n] = (char) ((ch & 15) << 2);
> >      > } else {
> >      > - to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
> >      > - to[++n] = (char) mg_b64idx(ch & 63);
> >      > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
> >      > + to[++n] = (char) mg_base64_encode_single(ch & 63);
> >      > n++;
> >      > }
> >      > return n;
> >      > }
> >      >
> >      > -int mg_base64_final(char *to, int n) {
> >      > - int saved = n;
> >      > +size_t mg_base64_final(char *to, size_t n) {
> >      > + size_t saved = n;
> >      > // printf("---[%.*s]\n", n, to);
> >      > if (n & 3) n = mg_base64_update(0, to, n);
> >      > if ((saved & 3) == 2) n--;
> >      > @@ -82,20 +81,27 @@ int mg_base64_final(char *to, int n) {
> >      > return n;
> >      > }
> >      >
> >      > -int mg_base64_encode(const unsigned char *p, int n, char *to) {
> >      > - int i, len = 0;
> >      > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
> >     *to, size_t dl) {
> >      > + size_t i, len = 0;
> >      > + if (dl > 0) to[0] = '\0';
> >      > + if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
> >      > for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
> >      > len = mg_base64_final(to, len);
> >      > return len;
> >      > }
> >      >
> >      > -int mg_base64_decode(const char *src, int n, char *dst) {
> >      > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
> >     size_t dl) {
> >      > const char *end = src == NULL ? NULL : src + n; // Cannot add to
> >     NULL
> >      > - int len = 0;
> >      > + size_t len = 0;
> >      > + if (dl < n / 4 * 3 + 1) goto fail;
> >      > while (src != NULL && src + 3 < end) {
> >      > - int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c =
> >     mg_b64rev(src[2]),
> >      > - d = mg_b64rev(src[3]);
> >      > - if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0)
> >     return 0;
> >      > + int a = mg_base64_decode_single(src[0]),
> >      > + b = mg_base64_decode_single(src[1]),
> >      > + c = mg_base64_decode_single(src[2]),
> >      > + d = mg_base64_decode_single(src[3]);
> >      > + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
> >      > + goto fail;
> >      > + }
> >      > dst[len++] = (char) ((a << 2) | (b >> 4));
> >      > if (src[2] != '=') {
> >      > dst[len++] = (char) ((b << 4) | (c >> 2));
> >      > @@ -105,1942 +111,2915 @@ int mg_base64_decode(const char *src,
> >     int n, char *dst) {
> >      > }
> >      > dst[len] = '\0';
> >      > return len;
> >      > +fail:
> >      > + if (dl > 0) dst[0] = '\0';
> >      > + return 0;
> >      > }
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/dns.c"
> >      > +#line 1 "src/device_ch32v307.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      > +#if MG_DEVICE == MG_DEVICE_CH32V307
> >      > +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
> >     <https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html>
> >      >
> >      > +#define FLASH_BASE 0x40022000
> >      > +#define FLASH_ACTLR (FLASH_BASE + 0)
> >      > +#define FLASH_KEYR (FLASH_BASE + 4)
> >      > +#define FLASH_OBKEYR (FLASH_BASE + 8)
> >      > +#define FLASH_STATR (FLASH_BASE + 12)
> >      > +#define FLASH_CTLR (FLASH_BASE + 16)
> >      > +#define FLASH_ADDR (FLASH_BASE + 20)
> >      > +#define FLASH_OBR (FLASH_BASE + 28)
> >      > +#define FLASH_WPR (FLASH_BASE + 32)
> >      >
> >      > -
> >      > -
> >      > -struct dns_data {
> >      > - struct dns_data *next;
> >      > - struct mg_connection *c;
> >      > - uint64_t expire;
> >      > - uint16_t txnid;
> >      > -};
> >      > -
> >      > -static void mg_sendnsreq(struct mg_connection *, struct mg_str
> >     *, int,
> >      > - struct mg_dns *, bool);
> >      > -
> >      > -static void mg_dns_free(struct mg_connection *c, struct dns_data
> >     *d) {
> >      > - LIST_DELETE(struct dns_data,
> >      > - (struct dns_data **) &c->mgr->active_dns_requests, d);
> >      > - free(d);
> >      > +void *mg_flash_start(void) {
> >      > + return (void *) 0x08000000;
> >      > }
> >      > -
> >      > -void mg_resolve_cancel(struct mg_connection *c) {
> >      > - struct dns_data *tmp, *d = (struct dns_data *)
> >     c->mgr->active_dns_requests;
> >      > - for (; d != NULL; d = tmp) {
> >      > - tmp = d->next;
> >      > - if (d->c == c) mg_dns_free(c, d);
> >      > +size_t mg_flash_size(void) {
> >      > + return 480 * 1024; // First 320k is 0-wait
> >      > +}
> >      > +size_t mg_flash_sector_size(void) {
> >      > + return 4096;
> >      > +}
> >      > +size_t mg_flash_write_align(void) {
> >      > + return 4;
> >      > +}
> >      > +int mg_flash_bank(void) {
> >      > + return 0;
> >      > +}
> >      > +void mg_device_reset(void) {
> >      > + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; //
> >     NVIC_SystemReset()
> >      > +}
> >      > +static void flash_unlock(void) {
> >      > + static bool unlocked;
> >      > + if (unlocked == false) {
> >      > + MG_REG(FLASH_KEYR) = 0x45670123;
> >      > + MG_REG(FLASH_KEYR) = 0xcdef89ab;
> >      > + unlocked = true;
> >      > }
> >      > }
> >      > +static void flash_wait(void) {
> >      > + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
> >      > +}
> >      >
> >      > -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
> >     len, size_t ofs,
> >      > - char *to, size_t tolen, size_t j,
> >      > - int depth) {
> >      > - size_t i = 0;
> >      > - if (tolen > 0 && depth == 0) to[0] = '\0';
> >      > - if (depth > 5) return 0;
> >      > - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
> >     + 1]));
> >      > - while (ofs + i + 1 < len) {
> >      > - size_t n = s[ofs + i];
> >      > - if (n == 0) {
> >      > - i++;
> >      > - break;
> >      > - }
> >      > - if (n & 0xc0) {
> >      > - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
> >     len
> >      > - // MG_INFO(("PTR %lx", (unsigned long) ptr));
> >      > - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> >      > - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
> >     == 0)
> >      > - return 0;
> >      > - i += 2;
> >      > - break;
> >      > - }
> >      > - if (ofs + i + n + 1 >= len) return 0;
> >      > - if (j > 0) {
> >      > - if (j < tolen) to[j] = '.';
> >      > - j++;
> >      > - }
> >      > - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> >      > - j += n;
> >      > - i += n + 1;
> >      > - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
> >      > - // MG_INFO(("--> [%s]", to));
> >      > - }
> >      > - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
> >     is nul-term
> >      > - return i;
> >      > +bool mg_flash_erase(void *addr) {
> >      > + //MG_INFO(("%p", addr));
> >      > + flash_unlock();
> >      > + flash_wait();
> >      > + MG_REG(FLASH_ADDR) = (uint32_t) addr;
> >      > + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT;
> >      > + flash_wait();
> >      > + return true;
> >      > }
> >      >
> >      > -static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
> >     size_t ofs,
> >      > - char *dst, size_t dstlen) {
> >      > - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
> >      > +static bool is_page_boundary(const void *addr) {
> >      > + uint32_t val = (uint32_t) addr;
> >      > + return (val & (mg_flash_sector_size() - 1)) == 0;
> >      > }
> >      >
> >      > -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> >      > - bool is_question, struct mg_dns_rr *rr) {
> >      > - const uint8_t *s = buf + ofs, *e = &buf[len];
> >      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> >      > + //MG_INFO(("%p %p %lu", addr, buf, len));
> >      > + //mg_hexdump(buf, len);
> >      > + flash_unlock();
> >      > + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
> >      > + uint16_t *dst = (uint16_t *) addr;
> >      > + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG
> >      > + //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
> >      > + while (src < end) {
> >      > + if (is_page_boundary(dst)) mg_flash_erase(dst);
> >      > + *dst++ = *src++;
> >      > + flash_wait();
> >      > + }
> >      > + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG
> >      > + return true;
> >      > +}
> >      > +#endif
> >      >
> >      > - memset(rr, 0, sizeof(*rr));
> >      > - if (len < sizeof(struct mg_dns_header)) return 0; // Too small
> >      > - if (len > 512) return 0; // Too large, we don't expect that
> >      > - if (s >= e) return 0; // Overflow
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/device_dummy.c"
> >      > +#endif
> >      >
> >      > - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
> >     NULL, 0)) == 0)
> >      > - return 0;
> >      > - s += rr->nlen + 4;
> >      > - if (s > e) return 0;
> >      > - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> >      > - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> >      > - if (is_question) return (size_t) (rr->nlen + 4);
> >      >
> >      > - s += 6;
> >      > - if (s > e) return 0;
> >      > - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> >      > - if (s + rr->alen > e) return 0;
> >      > - return (size_t) (rr->nlen + rr->alen + 10);
> >      > +#if MG_DEVICE == MG_DEVICE_NONE
> >      > +void *mg_flash_start(void) {
> >      > + return NULL;
> >      > +}
> >      > +size_t mg_flash_size(void) {
> >      > + return 0;
> >      > +}
> >      > +size_t mg_flash_sector_size(void) {
> >      > + return 0;
> >      > +}
> >      > +size_t mg_flash_write_align(void) {
> >      > + return 0;
> >      > +}
> >      > +int mg_flash_bank(void) {
> >      > + return 0;
> >      > +}
> >      > +bool mg_flash_erase(void *location) {
> >      > + (void) location;
> >      > + return false;
> >      > +}
> >      > +bool mg_flash_swap_bank(void) {
> >      > + return true;
> >      > +}
> >      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> >      > + (void) addr, (void) buf, (void) len;
> >      > + return false;
> >      > +}
> >      > +void mg_device_reset(void) {
> >      > }
> >      > +#endif
> >      >
> >      > -bool mg_dns_parse(const uint8_t *buf, size_t len, struct
> >     mg_dns_message *dm) {
> >      > - const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> >      > - struct mg_dns_rr rr;
> >      > - size_t i, n, ofs = sizeof(*h);
> >      > - memset(dm, 0, sizeof(*dm));
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/device_flash.c"
> >      > +#endif
> >      >
> >      > - if (len < sizeof(*h)) return 0; // Too small, headers dont fit
> >      > - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
> >      > - if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity
> >      > - dm->txnid = mg_ntohs(h->txnid);
> >      >
> >      > - for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> >      > - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
> >     return false;
> >      > - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> >      > - ofs += n;
> >      > +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE ==
> >     MG_DEVICE_STM32H5 || \
> >      > + MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> >      > +// Flash can be written only if it is erased. Erased flash is
> >     0xff (all bits 1)
> >      > +// Writes must be mg_flash_write_align() - aligned. Thus if we
> >     want to save an
> >      > +// object, we pad it at the end for alignment.
> >      > +//
> >      > +// Objects in the flash sector are stored sequentially:
> >      > +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size
> >     | ......
> >      > +//
> >      > +// In order to get to the next object, read its size, then align
> >     up.
> >      > +
> >      > +// Traverse the list of saved objects
> >      > +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t
> >     *size) {
> >      > + size_t aligned_size = 0, align = mg_flash_write_align(), left =
> >     end - p;
> >      > + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
> >      > + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
> >      > + if (size) *size = (size_t) p32[0];
> >      > + if (key) *key = p32[1];
> >      > + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
> >      > + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail
> >      > }
> >      > - for (i = 0; i < mg_ntohs(h->num_answers); i++) {
> >      > - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
> >     return false;
> >      > - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
> >     rr.aclass,
> >      > - // dm->name));
> >      > - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> >      > - ofs += n;
> >      > -
> >      > - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> >      > - dm->addr.is_ip6 = false;
> >      > - memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> >      > - dm->resolved = true;
> >      > - break; // Return success
> >      > - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> >      > - dm->addr.is_ip6 = true;
> >      > - memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
> >      > - dm->resolved = true;
> >      > - break; // Return success
> >      > + return aligned_size;
> >      > +}
> >      > +
> >      > +// Return the last sector of Bank 2
> >      > +static char *flash_last_sector(void) {
> >      > + size_t ss = mg_flash_sector_size(), size = mg_flash_size();
> >      > + char *base = (char *) mg_flash_start(), *last = base + size - ss;
> >      > + if (mg_flash_bank() == 2) last -= size / 2;
> >      > + return last;
> >      > +}
> >      > +
> >      > +// Find a saved object with a given key
> >      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
> >     len) {
> >      > + char *base = (char *) mg_flash_start(), *s = (char *) sector,
> >     *res = NULL;
> >      > + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
> >      > + bool ok = false;
> >      > + if (s == NULL) s = flash_last_sector();
> >      > + if (s < base || s >= base + mg_flash_size()) {
> >      > + MG_ERROR(("%p is outsize of flash", sector));
> >      > + } else if (((s - base) % ss) != 0) {
> >      > + MG_ERROR(("%p is not a sector boundary", sector));
> >      > + } else {
> >      > + uint32_t k, scanned = 0;
> >      > + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
> >      > + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k,
> >     key));
> >      > + // mg_hexdump(s + ofs, n);
> >      > + if (k == key && sz == len) {
> >      > + res = s + ofs + sizeof(uint32_t) * 2;
> >      > + memcpy(buf, res, len); // Copy object
> >      > + ok = true; // Keep scanning for the newer versions of it
> >      > + }
> >      > + ofs += n, scanned++;
> >      > }
> >      > + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key,
> >     res));
> >      > }
> >      > - return true;
> >      > + return ok;
> >      > }
> >      >
> >      > -static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
> >      > - void *fn_data) {
> >      > - struct dns_data *d, *tmp;
> >      > - if (ev == MG_EV_POLL) {
> >      > - uint64_t now = *(uint64_t *) ev_data;
> >      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
> >     NULL;
> >      > - d = tmp) {
> >      > - tmp = d->next;
> >      > - // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> >      > - if (now > d->expire) mg_error(d->c, "DNS timeout");
> >      > +// For all saved objects in the sector, delete old versions of
> >     objects
> >      > +static void mg_flash_sector_cleanup(char *sector) {
> >      > + // Buffer all saved objects into an IO buffer (backed by RAM)
> >      > + // erase sector, and re-save them.
> >      > + struct mg_iobuf io = {0, 0, 0, 2048};
> >      > + size_t ss = mg_flash_sector_size();
> >      > + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
> >      > + uint32_t key;
> >      > + // Traverse all objects
> >      > + MG_DEBUG(("Cleaning up sector %p", sector));
> >      > + while ((n = mg_flash_next(sector + ofs, sector + ss, &key,
> >     &size)) > 0) {
> >      > + // Delete an old copy of this object in the cache
> >      > + for (size_t o = 0; o < io.len; o += size2 + hs) {
> >      > + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
> >      > + size2 = *(uint32_t *) (io.buf + o);
> >      > + if (k == key) {
> >      > + mg_iobuf_del(&io, o, size2 + hs);
> >      > + break;
> >      > + }
> >      > }
> >      > - } else if (ev == MG_EV_READ) {
> >      > - struct mg_dns_message dm;
> >      > - int resolved = 0;
> >      > - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> >      > - MG_ERROR(("Unexpected DNS response:"));
> >      > - mg_hexdump(c->recv.buf, c->recv.len);
> >      > - } else {
> >      > - // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
> >      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
> >     NULL;
> >      > - d = tmp) {
> >      > - tmp = d->next;
> >      > - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> >      > - if (dm.txnid != d->txnid) continue;
> >      > - if (d->c->is_resolving) {
> >      > - if (dm.resolved) {
> >      > - dm.addr.port = d->c->rem.port; // Save port
> >      > - d->c->rem = dm.addr; // Copy resolved address
> >      > - MG_DEBUG(
> >      > - ("%lu %s is %I", d->c->id, dm.name <http://dm.name>,
> >     d->c->rem.is_ip6 ? 16 : 4,
> >      > - d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
> >      > - mg_connect_resolved(d->c);
> >      > -#if MG_ENABLE_IPV6
> >      > - } else if (dm.addr.is_ip6 == false && dm.name
> >     <http://dm.name>[0] != '\0' &&
> >      > - c->mgr->use_dns6 == false) {
> >      > - struct mg_str x = mg_str(dm.name <http://dm.name>);
> >      > - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> >      > -#endif
> >      > - } else {
> >      > - mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
> >      > + // And add the new copy
> >      > + mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
> >      > + ofs += n;
> >      > + }
> >      > + // All objects are cached in RAM now
> >      > + if (mg_flash_erase(sector)) { // Erase sector. If successful,
> >      > + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse
> >     cached objects
> >      > + size = *(uint32_t *) (io.buf + ofs);
> >      > + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
> >      > + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to
> >     flash
> >      > + }
> >      > + }
> >      > + mg_iobuf_free(&io);
> >      > +}
> >      > +
> >      > +// Save an object with a given key - append to the end of an
> >     object list
> >      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
> >     size_t len) {
> >      > + char *base = (char *) mg_flash_start(), *s = (char *) sector;
> >      > + size_t ss = mg_flash_sector_size(), ofs = 0, n;
> >      > + bool ok = false;
> >      > + if (s == NULL) s = flash_last_sector();
> >      > + if (s < base || s >= base + mg_flash_size()) {
> >      > + MG_ERROR(("%p is outsize of flash", sector));
> >      > + } else if (((s - base) % ss) != 0) {
> >      > + MG_ERROR(("%p is not a sector boundary", sector));
> >      > + } else {
> >      > + char ab[mg_flash_write_align()]; // Aligned write block
> >      > + uint32_t hdr[2] = {(uint32_t) len, key};
> >      > + size_t needed = sizeof(hdr) + len;
> >      > + size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
> >      > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
> >     ofs += n;
> >      > +
> >      > + // If there is not enough space left, cleanup sector and
> >     re-eval ofs
> >      > + if (ofs + needed_aligned >= ss) {
> >      > + mg_flash_sector_cleanup(s);
> >      > + ofs = 0;
> >      > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
> >     ofs += n;
> >      > + }
> >      > +
> >      > + if (ofs + needed_aligned <= ss) {
> >      > + // Enough space to save this object
> >      > + if (sizeof(ab) < sizeof(hdr)) {
> >      > + // Flash write granularity is 32 bit or less, write with no
> >     buffering
> >      > + ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
> >      > + if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
> >      > + } else {
> >      > + // Flash granularity is sizeof(hdr) or more. We need to save in
> >      > + // 3 chunks: initial block, bulk, rest. This is because we have
> >      > + // two memory chunks to write: hdr and buf, on aligned boundaries.
> >      > + n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
> >      > + if (n > len) n = len; // is
> >      > + memset(ab, 0xff, sizeof(ab)); // initialized to all-one
> >      > + memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
> >      > + memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
> >      > + MG_INFO(("saving initial block of %lu", sizeof(ab)));
> >      > + ok = mg_flash_write(s + ofs, ab, sizeof(ab));
> >      > + if (ok && len > n) {
> >      > + size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
> >      > + if (n2 > 0) {
> >      > + MG_INFO(("saving bulk, %lu", n2));
> >      > + ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
> >      > + }
> >      > + if (ok && len > n) {
> >      > + size_t n3 = len - n - n2;
> >      > + if (n3 > sizeof(ab)) n3 = sizeof(ab);
> >      > + memset(ab, 0xff, sizeof(ab));
> >      > + memcpy(ab, (char *) buf + n + n2, n3);
> >      > + MG_INFO(("saving rest, %lu", n3));
> >      > + ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
> >      > }
> >      > - } else {
> >      > - MG_ERROR(("%lu already resolved", d->c->id));
> >      > }
> >      > - mg_dns_free(c, d);
> >      > - resolved = 1;
> >      > }
> >      > - }
> >      > - if (!resolved) MG_ERROR(("stray DNS reply"));
> >      > - c->recv.len = 0;
> >      > - } else if (ev == MG_EV_CLOSE) {
> >      > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
> >     NULL;
> >      > - d = tmp) {
> >      > - tmp = d->next;
> >      > - mg_error(d->c, "DNS error");
> >      > - mg_dns_free(c, d);
> >      > + MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len,
> >     needed_aligned,
> >      > + s + ofs, key, ok));
> >      > + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs -
> >     needed_aligned));
> >      > + } else {
> >      > + MG_ERROR(("Sector is full"));
> >      > }
> >      > }
> >      > - (void) fn_data;
> >      > + return ok;
> >      > +}
> >      > +#else
> >      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
> >     size_t len) {
> >      > + (void) sector, (void) key, (void) buf, (void) len;
> >      > + return false;
> >      > +}
> >      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
> >     len) {
> >      > + (void) sector, (void) key, (void) buf, (void) len;
> >      > + return false;
> >      > }
> >      > +#endif
> >      >
> >      > -static bool mg_dns_send(struct mg_connection *c, const struct
> >     mg_str *name,
> >      > - uint16_t txnid, bool ipv6) {
> >      > - struct {
> >      > - struct mg_dns_header header;
> >      > - uint8_t data[256];
> >      > - } pkt;
> >      > - size_t i, n;
> >      > - memset(&pkt, 0, sizeof(pkt));
> >      > - pkt.header.txnid = mg_htons(txnid);
> >      > - pkt.header.flags = mg_htons(0x100);
> >      > - pkt.header.num_questions = mg_htons(1);
> >      > - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> >      > - if (name->ptr[i] == '.' || i >= name->len) {
> >      > - pkt.data[n] = (uint8_t) (i - n);
> >      > - memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
> >      > - n = i + 1;
> >      > - }
> >      > - if (i >= name->len) break;
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/device_imxrt.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
> >      > +
> >      > +struct mg_flexspi_lut_seq {
> >      > + uint8_t seqNum;
> >      > + uint8_t seqId;
> >      > + uint16_t reserved;
> >      > +};
> >      > +
> >      > +struct mg_flexspi_mem_config {
> >      > + uint32_t tag;
> >      > + uint32_t version;
> >      > + uint32_t reserved0;
> >      > + uint8_t readSampleClkSrc;
> >      > + uint8_t csHoldTime;
> >      > + uint8_t csSetupTime;
> >      > + uint8_t columnAddressWidth;
> >      > + uint8_t deviceModeCfgEnable;
> >      > + uint8_t deviceModeType;
> >      > + uint16_t waitTimeCfgCommands;
> >      > + struct mg_flexspi_lut_seq deviceModeSeq;
> >      > + uint32_t deviceModeArg;
> >      > + uint8_t configCmdEnable;
> >      > + uint8_t configModeType[3];
> >      > + struct mg_flexspi_lut_seq configCmdSeqs[3];
> >      > + uint32_t reserved1;
> >      > + uint32_t configCmdArgs[3];
> >      > + uint32_t reserved2;
> >      > + uint32_t controllerMiscOption;
> >      > + uint8_t deviceType;
> >      > + uint8_t sflashPadType;
> >      > + uint8_t serialClkFreq;
> >      > + uint8_t lutCustomSeqEnable;
> >      > + uint32_t reserved3[2];
> >      > + uint32_t sflashA1Size;
> >      > + uint32_t sflashA2Size;
> >      > + uint32_t sflashB1Size;
> >      > + uint32_t sflashB2Size;
> >      > + uint32_t csPadSettingOverride;
> >      > + uint32_t sclkPadSettingOverride;
> >      > + uint32_t dataPadSettingOverride;
> >      > + uint32_t dqsPadSettingOverride;
> >      > + uint32_t timeoutInMs;
> >      > + uint32_t commandInterval;
> >      > + uint16_t dataValidTime[2];
> >      > + uint16_t busyOffset;
> >      > + uint16_t busyBitPolarity;
> >      > + uint32_t lookupTable[64];
> >      > + struct mg_flexspi_lut_seq lutCustomSeq[12];
> >      > + uint32_t reserved4[4];
> >      > +};
> >      > +
> >      > +struct mg_flexspi_nor_config {
> >      > + struct mg_flexspi_mem_config memConfig;
> >      > + uint32_t pageSize;
> >      > + uint32_t sectorSize;
> >      > + uint8_t ipcmdSerialClkFreq;
> >      > + uint8_t isUniformBlockSize;
> >      > + uint8_t reserved0[2];
> >      > + uint8_t serialNorType;
> >      > + uint8_t needExitNoCmdMode;
> >      > + uint8_t halfClkForNonReadCmd;
> >      > + uint8_t needRestoreNoCmdMode;
> >      > + uint32_t blockSize;
> >      > + uint32_t reserve2[11];
> >      > +};
> >      > +
> >      > +/* FLEXSPI memory config block related defintions */
> >      > +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB"
> >     Big Endian
> >      > +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0
> >      > +
> >      > +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \
> >      > + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0)
> >     | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
> >      > + MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) |
> >     MG_FLEXSPI_LUT_OPCODE1(cmd1))
> >      > +
> >      > +#define MG_CMD_SDR 0x01
> >      > +#define MG_CMD_DDR 0x21
> >      > +#define MG_DUMMY_SDR 0x0C
> >      > +#define MG_DUMMY_DDR 0x2C
> >      > +#define MG_RADDR_SDR 0x02
> >      > +#define MG_RADDR_DDR 0x22
> >      > +#define MG_READ_SDR 0x09
> >      > +#define MG_READ_DDR 0x29
> >      > +#define MG_WRITE_SDR 0x08
> >      > +#define MG_WRITE_DDR 0x28
> >      > +#define MG_STOP 0
> >      > +
> >      > +#define MG_FLEXSPI_1PAD 0
> >      > +#define MG_FLEXSPI_2PAD 1
> >      > +#define MG_FLEXSPI_4PAD 2
> >      > +#define MG_FLEXSPI_8PAD 3
> >      > +
> >      > +#define MG_FLEXSPI_QSPI_LUT \
> >      > + { \
> >      > + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB,
> >     MG_RADDR_SDR, MG_FLEXSPI_4PAD, \
> >      > + 0x18), \
> >      > + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06,
> >     MG_READ_SDR, MG_FLEXSPI_4PAD, \
> >      > + 0x04), \
> >      > + [4 * 1 + 0] = \
> >      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05,
> >     MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \
> >      > + [4 * 3 + 0] = \
> >      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP,
> >     MG_FLEXSPI_1PAD, 0x0), \
> >      > + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
> >     0x20, MG_RADDR_SDR, \
> >      > + MG_FLEXSPI_1PAD, 0x18), \
> >      > + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
> >     0xD8, MG_RADDR_SDR, \
> >      > + MG_FLEXSPI_1PAD, 0x18), \
> >      > + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
> >     0x02, MG_RADDR_SDR, \
> >      > + MG_FLEXSPI_1PAD, 0x18), \
> >      > + [4 * 9 + 1] = \
> >      > + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04,
> >     MG_STOP, MG_FLEXSPI_1PAD, 0x0), \
> >      > + [4 * 11 + 0] = \
> >      > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP,
> >     MG_FLEXSPI_1PAD, 0x0), \
> >      > }
> >      > - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
> >      > - n += 5;
> >      > - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
> >      > - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
> >     query
> >      > - // n += 6;
> >      > - return mg_send(c, &pkt, sizeof(pkt.header) + n);
> >      > +
> >      > +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t)
> >     (x)))) & 0xFFU)
> >      > +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t)
> >     (x)) << 8U)) & 0x300U)
> >      > +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x))
> >     << 10U)) & 0xFC00U)
> >      > +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t)
> >     (x)) << 16U)) & 0xFF0000U)
> >      > +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t)
> >     (x)) << 24U)) & 0x3000000U)
> >      > +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x))
> >     << 26U)) & 0xFC000000U)
> >      > +
> >      > +#define FLEXSPI_NOR_INSTANCE 0
> >      > +
> >      > +#if MG_DEVICE == MG_DEVICE_RT1020
> >      > +struct mg_flexspi_nor_driver_interface {
> >      > + uint32_t version;
> >      > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config);
> >      > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config, uint32_t dst_addr,
> >      > + const uint32_t *src);
> >      > + uint32_t reserved;
> >      > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config, uint32_t start,
> >      > + uint32_t lengthInBytes);
> >      > + uint32_t reserved2;
> >      > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
> >     uint32_t *lutBase,
> >      > + uint32_t seqNumber);
> >      > + int (*xfer)(uint32_t instance, char *xfer);
> >      > + void (*clear_cache)(uint32_t instance);
> >      > +};
> >      > +#elif MG_DEVICE == MG_DEVICE_RT1060
> >      > +struct mg_flexspi_nor_driver_interface {
> >      > + uint32_t version;
> >      > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config);
> >      > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config, uint32_t dst_addr,
> >      > + const uint32_t *src);
> >      > + int (*erase_all)(uint32_t instance, struct
> >     mg_flexspi_nor_config *config);
> >      > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config, uint32_t start,
> >      > + uint32_t lengthInBytes);
> >      > + int (*read)(uint32_t instance, struct mg_flexspi_nor_config
> >     *config, uint32_t *dst, uint32_t addr,
> >      > + uint32_t lengthInBytes);
> >      > + void (*clear_cache)(uint32_t instance);
> >      > + int (*xfer)(uint32_t instance, char *xfer);
> >      > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
> >     uint32_t *lutBase,
> >      > + uint32_t seqNumber);
> >      > + int (*get_config)(uint32_t instance, struct
> >     mg_flexspi_nor_config *config, uint32_t *option);
> >      > +};
> >      > +#endif
> >      > +
> >      > +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
> >      > + (*(uint32_t*)0x0020001c + 16)))
> >      > +
> >      > +static bool s_flash_irq_disabled;
> >      > +
> >      > +MG_IRAM void *mg_flash_start(void) {
> >      > + return (void *) 0x60000000;
> >      > +}
> >      > +MG_IRAM size_t mg_flash_size(void) {
> >      > + return 8 * 1024 * 1024;
> >      > +}
> >      > +MG_IRAM size_t mg_flash_sector_size(void) {
> >      > + return 4 * 1024; // 4k
> >      > +}
> >      > +MG_IRAM size_t mg_flash_write_align(void) {
> >      > + return 256;
> >      > +}
> >      > +MG_IRAM int mg_flash_bank(void) {
> >      > + return 0;
> >      > }
> >      >
> >      > -static void mg_sendnsreq(struct mg_connection *c, struct mg_str
> >     *name, int ms,
> >      > - struct mg_dns *dnsc, bool ipv6) {
> >      > - struct dns_data *d = NULL;
> >      > - if (dnsc->url == NULL) {
> >      > - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> >      > - } else if (dnsc->c == NULL) {
> >      > - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> >      > - if (dnsc->c != NULL) {
> >      > - dnsc->c->pfn = dns_cb;
> >      > - // dnsc->c->is_hexdumping = 1;
> >      > - }
> >      > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> >      > + char *base = (char *) mg_flash_start(), *end = base +
> >     mg_flash_size();
> >      > + volatile char *p = (char *) dst;
> >      > + return p >= base && p < end && ((p - base) %
> >     mg_flash_sector_size()) == 0;
> >      > +}
> >      > +
> >      > +// Note: the get_config function below works both for RT1020 and
> >     1060
> >      > +#if MG_DEVICE == MG_DEVICE_RT1020
> >      > +MG_IRAM static int flexspi_nor_get_config(struct
> >     mg_flexspi_nor_config *config) {
> >      > + struct mg_flexspi_nor_config default_config = {
> >      > + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
> >      > + .version = MG_FLEXSPI_CFG_BLK_VERSION,
> >      > + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad
> >      > + .csHoldTime = 3,
> >      > + .csSetupTime = 3,
> >      > + .controllerMiscOption = MG_BIT(4),
> >      > + .deviceType = 1, // serial NOR
> >      > + .sflashPadType = 4,
> >      > + .serialClkFreq = 7, // 133MHz
> >      > + .sflashA1Size = 8 * 1024 * 1024,
> >      > + .lookupTable = MG_FLEXSPI_QSPI_LUT},
> >      > + .pageSize = 256,
> >      > + .sectorSize = 4 * 1024,
> >      > + .ipcmdSerialClkFreq = 1,
> >      > + .blockSize = 64 * 1024,
> >      > + .isUniformBlockSize = false};
> >      > +
> >      > + *config = default_config;
> >      > + return 0;
> >      > +}
> >      > +#else
> >      > +MG_IRAM static int flexspi_nor_get_config(struct
> >     mg_flexspi_nor_config *config) {
> >      > + uint32_t options[] = {0xc0000000, 0x00};
> >      > +
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + uint32_t status =
> >      > + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
> >      > + if (!s_flash_irq_disabled) {
> >      > + MG_ARM_ENABLE_IRQ();
> >      > }
> >      > - if (dnsc->c == NULL) {
> >      > - mg_error(c, "resolver");
> >      > - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
> >     NULL) {
> >      > - mg_error(c, "resolve OOM");
> >      > - } else {
> >      > - struct dns_data *reqs = (struct dns_data *)
> >     c->mgr->active_dns_requests;
> >      > - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> >      > - d->next = (struct dns_data *) c->mgr->active_dns_requests;
> >      > - c->mgr->active_dns_requests = d;
> >      > - d->expire = mg_millis() + (uint64_t) ms;
> >      > - d->c = c;
> >      > - c->is_resolving = 1;
> >      > - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
> >     name->len,
> >      > - name->ptr, &dnsc->url, d->txnid));
> >      > - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> >      > - mg_error(dnsc->c, "DNS send");
> >      > - }
> >      > + if (status) {
> >      > + MG_ERROR(("Failed to extract flash configuration: status %u",
> >     status));
> >      > }
> >      > + return status;
> >      > }
> >      > +#endif
> >      >
> >      > -void mg_resolve(struct mg_connection *c, const char *url) {
> >      > - struct mg_str host = mg_url_host(url);
> >      > - c->rem.port = mg_htons(mg_url_port(url));
> >      > - if (mg_aton(host, &c->rem)) {
> >      > - // host is an IP address, do not fire name resolution
> >      > - mg_connect_resolved(c);
> >      > - } else {
> >      > - // host is not an IP, send DNS resolution request
> >      > - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
> >     &c->mgr->dns4;
> >      > - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> >      > +MG_IRAM bool mg_flash_erase(void *addr) {
> >      > + struct mg_flexspi_nor_config config;
> >      > + if (flexspi_nor_get_config(&config) != 0) {
> >      > + return false;
> >      > + }
> >      > + if (flash_page_start(addr) == false) {
> >      > + MG_ERROR(("%p is not on a sector boundary", addr));
> >      > + return false;
> >      > + }
> >      > +
> >      > + void *dst = (void *)((char *) addr - (char *) mg_flash_start());
> >      > +
> >      > + // Note: Interrupts must be disabled before any call to the ROM
> >     API on RT1020
> >      > + // and 1060
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config,
> >     (uint32_t) dst,
> >      > + mg_flash_sector_size()) == 0);
> >      > + if (!s_flash_irq_disabled) {
> >      > + MG_ARM_ENABLE_IRQ(); // Reenable them after the call
> >      > }
> >      > + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok"
> >     : "fail"));
> >      > + return ok;
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/event.c"
> >      > -#endif
> >      > +MG_IRAM bool mg_flash_swap_bank(void) {
> >      > + return true;
> >      > +}
> >      > +
> >      > +static inline void spin(volatile uint32_t count) {
> >      > + while (count--) (void) 0;
> >      > +}
> >      > +
> >      > +static inline void flash_wait(void) {
> >      > + while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) &
> >     MG_BIT(1)) == 0)
> >      > + spin(1);
> >      > +}
> >      >
> >      > +MG_IRAM static void *flash_code_location(void) {
> >      > + return (void *) ((char *) mg_flash_start() + 0x2000);
> >      > +}
> >      >
> >      > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
> >     len) {
> >      > + struct mg_flexspi_nor_config config;
> >      > + if (flexspi_nor_get_config(&config) != 0) {
> >      > + return false;
> >      > + }
> >      > + if ((len % mg_flash_write_align()) != 0) {
> >      > + MG_ERROR(("%lu is not aligned to %lu", len,
> >     mg_flash_write_align()));
> >      > + return false;
> >      > + }
> >      >
> >      > + if ((char *) addr < (char *) mg_flash_start()) {
> >      > + MG_ERROR(("Invalid flash write address: %p", addr));
> >      > + return false;
> >      > + }
> >      >
> >      > + uint32_t *dst = (uint32_t *) addr;
> >      > + uint32_t *src = (uint32_t *) buf;
> >      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> >      > + bool ok = true;
> >      > +
> >      > + // Note: If we overwrite the flash irq section of the image, we
> >     must also
> >      > + // make sure interrupts are disabled and are not reenabled
> >     until we write
> >      > + // this sector with another irq table.
> >      > + if ((char *) addr == (char *) flash_code_location()) {
> >      > + s_flash_irq_disabled = true;
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + }
> >      >
> >      > -void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> >      > - // Run user-defined handler first, in order to give it an ability
> >      > - // to intercept processing (e.g. clean input buffer) before the
> >      > - // protocol handler kicks in
> >      > - if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
> >      > - if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
> >      > + while (ok && src < end) {
> >      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
> >      > + break;
> >      > + }
> >      > + uint32_t status;
> >      > + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
> >      > + if ((char *) buf >= (char *) mg_flash_start()) {
> >      > + // If we copy from FLASH to FLASH, then we first need to copy
> >     the source
> >      > + // to RAM
> >      > + size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
> >      > + uint32_t tmp[tmp_buf_size];
> >      > +
> >      > + for (size_t i = 0; i < tmp_buf_size; i++) {
> >      > + flash_wait();
> >      > + tmp[i] = src[i];
> >      > + }
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> >      > + (uint32_t) dst_ofs, tmp);
> >      > + } else {
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
> >      > + (uint32_t) dst_ofs, src);
> >      > + }
> >      > + if (!s_flash_irq_disabled) {
> >      > + MG_ARM_ENABLE_IRQ();
> >      > + }
> >      > + src = (uint32_t *) ((char *) src + mg_flash_write_align());
> >      > + dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
> >      > + if (status != 0) {
> >      > + ok = false;
> >      > + }
> >      > + }
> >      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ?
> >     "ok" : "fail"));
> >      > + return ok;
> >      > }
> >      >
> >      > -void mg_error(struct mg_connection *c, const char *fmt, ...) {
> >      > - char buf[64];
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> >      > - va_end(ap);
> >      > - MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
> >      > - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
> >      > - mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it
> >      > +MG_IRAM void mg_device_reset(void) {
> >      > + MG_DEBUG(("Resetting device..."));
> >      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> >      > }
> >      >
> >      > +#endif
> >      > +
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/fmt.c"
> >      > +#line 1 "src/device_stm32h5.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      > +#if MG_DEVICE == MG_DEVICE_STM32H5
> >      >
> >      > -static void mg_pfn_iobuf_private(char ch, void *param, bool
> >     expand) {
> >      > - struct mg_iobuf *io = (struct mg_iobuf *) param;
> >      > - if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
> >     io->len + 2);
> >      > - if (io->len + 2 <= io->size) {
> >      > - io->buf[io->len++] = (uint8_t) ch;
> >      > - io->buf[io->len] = 0;
> >      > - } else if (io->len < io->size) {
> >      > - io->buf[io->len++] = 0; // Guarantee to 0-terminate
> >      > - }
> >      > -}
> >      > +#define FLASH_BASE 0x40022000 // Base address of the flash
> >     controller
> >      > +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
> >      > +#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
> >      > +#define FLASH_OPTCR (FLASH_BASE + 0x1c)
> >      > +#define FLASH_NSSR (FLASH_BASE + 0x20)
> >      > +#define FLASH_NSCR (FLASH_BASE + 0x28)
> >      > +#define FLASH_NSCCR (FLASH_BASE + 0x30)
> >      > +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
> >      > +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
> >      >
> >      > -static void mg_putchar_iobuf_static(char ch, void *param) {
> >      > - mg_pfn_iobuf_private(ch, param, false);
> >      > +void *mg_flash_start(void) {
> >      > + return (void *) 0x08000000;
> >      > }
> >      > -
> >      > -void mg_pfn_iobuf(char ch, void *param) {
> >      > - mg_pfn_iobuf_private(ch, param, true);
> >      > +size_t mg_flash_size(void) {
> >      > + return 2 * 1024 * 1024; // 2Mb
> >      > }
> >      > -
> >      > -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
> >     va_list *ap) {
> >      > - struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> >      > - size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> >      > - if (n < len) buf[n] = '\0';
> >      > - return n;
> >      > +size_t mg_flash_sector_size(void) {
> >      > + return 8 * 1024; // 8k
> >      > }
> >      > -
> >      > -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> >      > - va_list ap;
> >      > - size_t n;
> >      > - va_start(ap, fmt);
> >      > - n = mg_vsnprintf(buf, len, fmt, &ap);
> >      > - va_end(ap);
> >      > - return n;
> >      > +size_t mg_flash_write_align(void) {
> >      > + return 16; // 128 bit
> >      > }
> >      > -
> >      > -char *mg_vmprintf(const char *fmt, va_list *ap) {
> >      > - struct mg_iobuf io = {0, 0, 0, 256};
> >      > - mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> >      > - return (char *) io.buf;
> >      > +int mg_flash_bank(void) {
> >      > + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
> >      > }
> >      >
> >      > -char *mg_mprintf(const char *fmt, ...) {
> >      > - char *s;
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - s = mg_vmprintf(fmt, &ap);
> >      > - va_end(ap);
> >      > - return s;
> >      > +static void flash_unlock(void) {
> >      > + static bool unlocked = false;
> >      > + if (unlocked == false) {
> >      > + MG_REG(FLASH_KEYR) = 0x45670123;
> >      > + MG_REG(FLASH_KEYR) = 0Xcdef89ab;
> >      > + MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
> >      > + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
> >      > + unlocked = true;
> >      > + }
> >      > }
> >      >
> >      > -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
> >     char *fmt, ...) {
> >      > - size_t len = 0;
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - len = mg_vxprintf(out, ptr, fmt, &ap);
> >      > - va_end(ap);
> >      > - return len;
> >      > +static int flash_page_start(volatile uint32_t *dst) {
> >      > + char *base = (char *) mg_flash_start(), *end = base +
> >     mg_flash_size();
> >      > + volatile char *p = (char *) dst;
> >      > + return p >= base && p < end && ((p - base) %
> >     mg_flash_sector_size()) == 0;
> >      > }
> >      >
> >      > -static bool is_digit(int c) {
> >      > - return c >= '0' && c <= '9';
> >      > +static bool flash_is_err(void) {
> >      > + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481
> >     7.11.9
> >      > }
> >      >
> >      > -static int addexp(char *buf, int e, int sign) {
> >      > - int n = 0;
> >      > - buf[n++] = 'e';
> >      > - buf[n++] = (char) sign;
> >      > - if (e > 400) return 0;
> >      > - if (e < 10) buf[n++] = '0';
> >      > - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
> >     / 100);
> >      > - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> >      > - buf[n++] = (char) (e + '0');
> >      > - return n;
> >      > +static void flash_wait(void) {
> >      > + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
> >      > + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
> >      > + (void) 0;
> >      > + }
> >      > }
> >      >
> >      > -static int xisinf(double x) {
> >      > - union {
> >      > - double f;
> >      > - uint64_t u;
> >      > - } ieee754 = {x};
> >      > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
> >     0x7ff00000 &&
> >      > - ((unsigned) ieee754.u == 0);
> >      > +static void flash_clear_err(void) {
> >      > + flash_wait(); // Wait until ready
> >      > + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all
> >     errors
> >      > }
> >      >
> >      > -static int xisnan(double x) {
> >      > - union {
> >      > - double f;
> >      > - uint64_t u;
> >      > - } ieee754 = {x};
> >      > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> >      > - ((unsigned) ieee754.u != 0) >
> >      > - 0x7ff00000;
> >      > +static bool flash_bank_is_swapped(void) {
> >      > + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8
> >      > }
> >      >
> >      > -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
> >     width) {
> >      > - char buf[40];
> >      > - int i, s = 0, n = 0, e = 0;
> >      > - double t, mul, saved;
> >      > - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> >      > - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
> >     "inf" : "-inf");
> >      > - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> >      > - if (d < 0.0) d = -d, buf[s++] = '-';
> >      > -
> >      > - // Round
> >      > - saved = d;
> >      > - mul = 1.0;
> >      > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> >      > - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> >      > - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> >      > - d += t;
> >      > - // Calculate exponent, and 'mul' for scientific representation
> >      > - mul = 1.0;
> >      > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> >      > - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> >      > - // printf(" --> %g %d %g %g\n", saved, e, t, mul);
> >      > -
> >      > - if (e >= width) {
> >      > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> >      > - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> >      > - n += addexp(buf + s + n, e, '+');
> >      > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> >      > - } else if (e <= -width) {
> >      > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
> >      > - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> >      > - n += addexp(buf + s + n, -e, '-');
> >      > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> >      > +bool mg_flash_erase(void *location) {
> >      > + bool ok = false;
> >      > + if (flash_page_start(location) == false) {
> >      > + MG_ERROR(("%p is not on a sector boundary"));
> >      > } else {
> >      > - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> >      > - int ch = (int) (d / t);
> >      > - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> >      > - d -= ch * t;
> >      > - t /= 10.0;
> >      > - }
> >      > - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
> >     + n, buf);
> >      > - if (n == 0) buf[s++] = '0';
> >      > - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
> >     /= 10.0;
> >      > - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> >      > - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> >      > - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
> >     i++) {
> >      > - int ch = (int) (d / t);
> >      > - buf[s + n++] = (char) (ch + '0');
> >      > - d -= ch * t;
> >      > - t /= 10.0;
> >      > + uintptr_t diff = (char *) location - (char *) mg_flash_start();
> >      > + uint32_t sector = diff / mg_flash_sector_size();
> >      > + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
> >      > + flash_unlock();
> >      > + flash_clear_err();
> >      > + MG_REG(FLASH_NSCR) = 0;
> >      > + if ((sector < 128 && flash_bank_is_swapped()) ||
> >      > + (sector > 127 && !flash_bank_is_swapped())) {
> >      > + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL
> >      > }
> >      > + if (sector > 127) sector -= 128;
> >      > + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase |
> >     sector_num
> >      > + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing
> >      > + flash_wait();
> >      > + ok = !flash_is_err();
> >      > + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector,
> >     location,
> >      > + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
> >      > + // mg_hexdump(location, 32);
> >      > + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
> >      > }
> >      > - while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
> >      > - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
> >      > - n += s;
> >      > - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> >      > - buf[n] = '\0';
> >      > - return mg_snprintf(dst, dstlen, "%s", buf);
> >      > + return ok;
> >      > }
> >      >
> >      > -static size_t mg_lld(char *buf, int64_t val, bool is_signed,
> >     bool is_hex) {
> >      > - const char *letters = "0123456789abcdef";
> >      > - uint64_t v = (uint64_t) val;
> >      > - size_t s = 0, n, i;
> >      > - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> >      > - // This loop prints a number in reverse order. I guess this is
> >     because we
> >      > - // write numbers from right to left: least significant digit
> >     comes last.
> >      > - // Maybe because we use Arabic numbers, and Arabs write RTL?
> >      > - if (is_hex) {
> >      > - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> >      > - } else {
> >      > - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
> >      > +bool mg_flash_swap_bank(void) {
> >      > + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
> >      > + flash_unlock();
> >      > + flash_clear_err();
> >      > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> >      > + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
> >      > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> >      > + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
> >      > + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired)
> >     (void) 0;
> >      > + return true;
> >      > +}
> >      > +
> >      > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
> >      > + if ((len % mg_flash_write_align()) != 0) {
> >      > + MG_ERROR(("%lu is not aligned to %lu", len,
> >     mg_flash_write_align()));
> >      > + return false;
> >      > }
> >      > - // Reverse a string
> >      > - for (i = 0; i < n / 2; i++) {
> >      > - char t = buf[s + i];
> >      > - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> >      > + uint32_t *dst = (uint32_t *) addr;
> >      > + uint32_t *src = (uint32_t *) buf;
> >      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> >      > + bool ok = true;
> >      > + flash_unlock();
> >      > + flash_clear_err();
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
> >      > + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
> >      > + while (ok && src < end) {
> >      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> >      > + *(volatile uint32_t *) dst++ = *src++;
> >      > + flash_wait();
> >      > + if (flash_is_err()) ok = false;
> >      > }
> >      > - if (val == 0) buf[n++] = '0'; // Handle special case
> >      > - return n + s;
> >      > + MG_ARM_ENABLE_IRQ();
> >      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
> >     len, dst,
> >      > + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
> >      > + MG_REG(FLASH_NSSR)));
> >      > + MG_REG(FLASH_NSCR) = 0; // Clear flags
> >      > + return ok;
> >      > }
> >      >
> >      > -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> >      > - size_t len) {
> >      > - size_t i = 0;
> >      > - while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> >      > - return i;
> >      > +void mg_device_reset(void) {
> >      > + // SCB->AIRCR = ((0x5fa <<
> >     SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> >      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> >      > }
> >      > +#endif
> >      >
> >      > -static char mg_esc(int c, bool esc) {
> >      > - const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> >      > - for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> >      > - if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/device_stm32h7.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +#if MG_DEVICE == MG_DEVICE_STM32H7
> >      > +
> >      > +#define FLASH_BASE1 0x52002000 // Base address for bank1
> >      > +#define FLASH_BASE2 0x52002100 // Base address for bank2
> >      > +#define FLASH_KEYR 0x04 // See RM0433 4.9.2
> >      > +#define FLASH_OPTKEYR 0x08
> >      > +#define FLASH_OPTCR 0x18
> >      > +#define FLASH_SR 0x10
> >      > +#define FLASH_CR 0x0c
> >      > +#define FLASH_CCR 0x14
> >      > +#define FLASH_OPTSR_CUR 0x1c
> >      > +#define FLASH_OPTSR_PRG 0x20
> >      > +#define FLASH_SIZE_REG 0x1ff1e880
> >      > +
> >      > +MG_IRAM void *mg_flash_start(void) {
> >      > + return (void *) 0x08000000;
> >      > +}
> >      > +MG_IRAM size_t mg_flash_size(void) {
> >      > + return MG_REG(FLASH_SIZE_REG) * 1024;
> >      > +}
> >      > +MG_IRAM size_t mg_flash_sector_size(void) {
> >      > + return 128 * 1024; // 128k
> >      > +}
> >      > +MG_IRAM size_t mg_flash_write_align(void) {
> >      > + return 32; // 256 bit
> >      > +}
> >      > +MG_IRAM int mg_flash_bank(void) {
> >      > + if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual
> >     bank support
> >      > + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
> >      > +}
> >      > +
> >      > +MG_IRAM static void flash_unlock(void) {
> >      > + static bool unlocked = false;
> >      > + if (unlocked == false) {
> >      > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
> >      > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
> >      > + if (mg_flash_bank() > 0) {
> >      > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
> >      > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
> >      > + }
> >      > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is
> >     "shared"
> >      > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus
> >     unlock once
> >      > + unlocked = true;
> >      > }
> >      > - return 0;
> >      > }
> >      >
> >      > -static char mg_escape(int c) {
> >      > - return mg_esc(c, true);
> >      > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
> >      > + char *base = (char *) mg_flash_start(), *end = base +
> >     mg_flash_size();
> >      > + volatile char *p = (char *) dst;
> >      > + return p >= base && p < end && ((p - base) %
> >     mg_flash_sector_size()) == 0;
> >      > }
> >      >
> >      > -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> >      > - size_t len) {
> >      > - size_t i = 0, extra = 0;
> >      > - for (i = 0; i < len && buf[i] != '\0'; i++) {
> >      > - char c = mg_escape(buf[i]);
> >      > - if (c) {
> >      > - out('\\', ptr), out(c, ptr), extra++;
> >      > - } else {
> >      > - out(buf[i], ptr);
> >      > - }
> >      > - }
> >      > - return i + extra;
> >      > +MG_IRAM static bool flash_is_err(uint32_t bank) {
> >      > + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); //
> >     RM0433 4.9.5
> >      > }
> >      >
> >      > -static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
> >      > - size_t len) {
> >      > - size_t n = 2;
> >      > - out('"', ptr);
> >      > - n += qcpy(out, ptr, buf, len);
> >      > - out('"', ptr);
> >      > - return n;
> >      > +MG_IRAM static void flash_wait(uint32_t bank) {
> >      > + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2)))
> >     (void) 0;
> >      > }
> >      >
> >      > -static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t
> >     *buf,
> >      > - size_t len) {
> >      > - size_t i, n = 0;
> >      > - const char *t =
> >      > -
> >     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> >      > - out('"', ptr), n++;
> >      > - for (i = 0; i < len; i += 3) {
> >      > - uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> >      > - c3 = i + 2 < len ? buf[i + 2] : 0;
> >      > - char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
> >     '='};
> >      > - if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> >      > - if (i + 2 < len) tmp[3] = t[c3 & 63];
> >      > - n += scpy(out, ptr, tmp, sizeof(tmp));
> >      > - }
> >      > - out('"', ptr), n++;
> >      > - return n;
> >      > +MG_IRAM static void flash_clear_err(uint32_t bank) {
> >      > + flash_wait(bank); // Wait until ready
> >      > + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear
> >     all errors
> >      > }
> >      >
> >      > -size_t mg_vxprintf(void (*out)(char, void *), void *param, const
> >     char *fmt,
> >      > - va_list *ap) {
> >      > - size_t i = 0, n = 0;
> >      > - while (fmt[i] != '\0') {
> >      > - if (fmt[i] == '%') {
> >      > - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
> >     prec */;
> >      > - char pad = ' ', minus = 0, c = fmt[++i];
> >      > - if (c == '#') x++, c = fmt[++i];
> >      > - if (c == '-') minus++, c = fmt[++i];
> >      > - if (c == '0') pad = '0', c = fmt[++i];
> >      > - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
> >     fmt[++i];
> >      > - if (c == '.') {
> >      > - c = fmt[++i];
> >      > - if (c == '*') {
> >      > - pr = (size_t) va_arg(*ap, int);
> >      > - c = fmt[++i];
> >      > - } else {
> >      > - pr = 0;
> >      > - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
> >     fmt[++i];
> >      > - }
> >      > - }
> >      > - while (c == 'h') c = fmt[++i]; // Treat h and hh as int
> >      > - if (c == 'l') {
> >      > - is_long++, c = fmt[++i];
> >      > - if (c == 'l') is_long++, c = fmt[++i];
> >      > - }
> >      > - if (c == 'p') x = 1, is_long = 1;
> >      > - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> >      > - c == 'g' || c == 'f') {
> >      > - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> >      > - char tmp[40];
> >      > - size_t xl = x ? 2 : 0;
> >      > - if (c == 'g' || c == 'f') {
> >      > - double v = va_arg(*ap, double);
> >      > - if (pr == ~0U) pr = 6;
> >      > - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
> >      > - } else if (is_long == 2) {
> >      > - int64_t v = va_arg(*ap, int64_t);
> >      > - k = mg_lld(tmp, v, s, h);
> >      > - } else if (is_long == 1) {
> >      > - long v = va_arg(*ap, long);
> >      > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
> >     s, h);
> >      > - } else {
> >      > - int v = va_arg(*ap, int);
> >      > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> >      > - }
> >      > - for (j = 0; j < xl && w > 0; j++) w--;
> >      > - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> >      > - n += scpy(out, param, &pad, 1);
> >      > - n += scpy(out, param, (char *) "0x", xl);
> >      > - for (j = 0; pad == '0' && k < w && j + k < w; j++)
> >      > - n += scpy(out, param, &pad, 1);
> >      > - n += scpy(out, param, tmp, k);
> >      > - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> >      > - n += scpy(out, param, &pad, 1);
> >      > - } else if (c == 'M') {
> >      > - mg_pm_t f = va_arg(*ap, mg_pm_t);
> >      > - n += f(out, param, ap);
> >      > - } else if (c == 'c') {
> >      > - int ch = va_arg(*ap, int);
> >      > - out((char) ch, param);
> >      > - n++;
> >      > - } else if (c == 'H') {
> >      > - // Print hex-encoded double-quoted string
> >      > - size_t bl = (size_t) va_arg(*ap, int);
> >      > - uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
> >      > - const char *hex = "0123456789abcdef";
> >      > - n += scpy(out, param, (char *) &dquote, 1);
> >      > - for (j = 0; j < bl; j++) {
> >      > - n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
> >      > - n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
> >      > - }
> >      > - n += scpy(out, param, (char *) &dquote, 1);
> >      > - } else if (c == 'I') {
> >      > - // Print IPv4 or IPv6 address
> >      > - size_t len = (size_t) va_arg(*ap, int); // Length 16 means IPv6
> >     address
> >      > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the IP
> >     address
> >      > - if (len == 6) {
> >      > - uint16_t *p = (uint16_t *) buf;
> >      > - n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x",
> >     mg_htons(p[0]),
> >      > - mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
> >      > - mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
> >      > - mg_htons(p[7]));
> >      > - } else {
> >      > - n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int)
> >     buf[1],
> >      > - (int) buf[2], (int) buf[3]);
> >      > - }
> >      > - } else if (c == 'A') {
> >      > - // Print hardware addresses (currently Ethernet MAC)
> >      > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the hw
> >     address
> >      > - n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
> >      > - (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
> >      > - (int) buf[4], (int) buf[5]);
> >      > - } else if (c == 'V') {
> >      > - // Print base64-encoded double-quoted string
> >      > - size_t len = (size_t) va_arg(*ap, int);
> >      > - uint8_t *buf = va_arg(*ap, uint8_t *);
> >      > - n += bcpy(out, param, buf, len);
> >      > - } else if (c == 's' || c == 'Q' || c == 'q') {
> >      > - char *p = va_arg(*ap, char *);
> >      > - size_t (*f)(void (*)(char, void *), void *, char *, size_t) =
> >     scpy;
> >      > - if (c == 'Q') f = Qcpy;
> >      > - if (c == 'q') f = qcpy;
> >      > - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> >      > - for (j = 0; !minus && pr < w && j + pr < w; j++)
> >      > - n += f(out, param, &pad, 1);
> >      > - n += f(out, param, p, pr);
> >      > - for (j = 0; minus && pr < w && j + pr < w; j++)
> >      > - n += f(out, param, &pad, 1);
> >      > - } else if (c == '%') {
> >      > - out('%', param);
> >      > - n++;
> >      > - } else {
> >      > - out('%', param);
> >      > - out(c, param);
> >      > - n += 2;
> >      > - }
> >      > - i++;
> >      > - } else {
> >      > - out(fmt[i], param), n++, i++;
> >      > - }
> >      > - }
> >      > - return n;
> >      > +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
> >      > + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/fs.c"
> >      > -#endif
> >      > -
> >      > -
> >      > -
> >      > -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
> >     flags) {
> >      > - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> >      > - if (fd != NULL) {
> >      > - fd->fd = fs->op(path, flags);
> >      > - fd->fs = fs;
> >      > - if (fd->fd == NULL) {
> >      > - free(fd);
> >      > - fd = NULL;
> >      > - }
> >      > - }
> >      > - return fd;
> >      > +// Figure out flash bank based on the address
> >      > +MG_IRAM static uint32_t flash_bank(void *addr) {
> >      > + size_t ofs = (char *) addr - (char *) mg_flash_start();
> >      > + if (mg_flash_bank() == 0) return FLASH_BASE1;
> >      > + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
> >      > }
> >      >
> >      > -void mg_fs_close(struct mg_fd *fd) {
> >      > - if (fd != NULL) {
> >      > - fd->fs->cl(fd->fd);
> >      > - free(fd);
> >      > +MG_IRAM bool mg_flash_erase(void *addr) {
> >      > + bool ok = false;
> >      > + if (flash_page_start(addr) == false) {
> >      > + MG_ERROR(("%p is not on a sector boundary", addr));
> >      > + } else {
> >      > + uintptr_t diff = (char *) addr - (char *) mg_flash_start();
> >      > + uint32_t sector = diff / mg_flash_sector_size();
> >      > + uint32_t bank = flash_bank(addr);
> >      > + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
> >      > +
> >      > + flash_unlock();
> >      > + if (sector > 7) sector -= 8;
> >      > +
> >      > + flash_clear_err(bank);
> >      > + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
> >      > + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
> >      > + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
> >      > + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
> >      > + ok = !flash_is_err(bank);
> >      > + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector,
> >     addr,
> >      > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> >      > + MG_REG(bank + FLASH_SR)));
> >      > + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
> >      > }
> >      > + return ok;
> >      > }
> >      >
> >      > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
> >     *sizep) {
> >      > - struct mg_fd *fd;
> >      > - char *data = NULL;
> >      > - size_t size = 0;
> >      > - fs->st(path, &size, NULL);
> >      > - if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
> >      > - data = (char *) calloc(1, size + 1);
> >      > - if (data != NULL) {
> >      > - if (fs->rd(fd->fd, data, size) != size) {
> >      > - free(data);
> >      > - data = NULL;
> >      > - } else {
> >      > - data[size] = '\0';
> >      > - if (sizep != NULL) *sizep = size;
> >      > - }
> >      > - }
> >      > - mg_fs_close(fd);
> >      > - }
> >      > - return data;
> >      > +MG_IRAM bool mg_flash_swap_bank(void) {
> >      > + if (mg_flash_bank() == 0) return true;
> >      > + uint32_t bank = FLASH_BASE1;
> >      > + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
> >      > + flash_unlock();
> >      > + flash_clear_err(bank);
> >      > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
> >      > + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
> >      > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
> >      > + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
> >      > + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) !=
> >     desired) (void) 0;
> >      > + return true;
> >      > }
> >      >
> >      > -bool mg_file_write(struct mg_fs *fs, const char *path, const
> >     void *buf,
> >      > - size_t len) {
> >      > - bool result = false;
> >      > - struct mg_fd *fd;
> >      > - char tmp[MG_PATH_MAX];
> >      > - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> >      > - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> >      > - result = fs->wr(fd->fd, buf, len) == len;
> >      > - mg_fs_close(fd);
> >      > - if (result) {
> >      > - fs->rm(path);
> >      > - fs->mv(tmp, path);
> >      > - } else {
> >      > - fs->rm(tmp);
> >      > - }
> >      > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
> >     len) {
> >      > + if ((len % mg_flash_write_align()) != 0) {
> >      > + MG_ERROR(("%lu is not aligned to %lu", len,
> >     mg_flash_write_align()));
> >      > + return false;
> >      > }
> >      > - return result;
> >      > + uint32_t bank = flash_bank(addr);
> >      > + uint32_t *dst = (uint32_t *) addr;
> >      > + uint32_t *src = (uint32_t *) buf;
> >      > + uint32_t *end = (uint32_t *) ((char *) buf + len);
> >      > + bool ok = true;
> >      > + flash_unlock();
> >      > + flash_clear_err(bank);
> >      > + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
> >      > + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
> >      > + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
> >      > + MG_ARM_DISABLE_IRQ();
> >      > + while (ok && src < end) {
> >      > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
> >      > + *(volatile uint32_t *) dst++ = *src++;
> >      > + flash_wait(bank);
> >      > + if (flash_is_err(bank)) ok = false;
> >      > + }
> >      > + MG_ARM_ENABLE_IRQ();
> >      > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
> >     len, dst,
> >      > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
> >      > + MG_REG(bank + FLASH_SR)));
> >      > + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
> >      > + return ok;
> >      > }
> >      >
> >      > -bool mg_file_printf(struct mg_fs *fs, const char *path, const
> >     char *fmt, ...) {
> >      > - va_list ap;
> >      > - char *data;
> >      > - bool result = false;
> >      > - va_start(ap, fmt);
> >      > - data = mg_vmprintf(fmt, &ap);
> >      > - va_end(ap);
> >      > - result = mg_file_write(fs, path, data, strlen(data));
> >      > - free(data);
> >      > - return result;
> >      > +MG_IRAM void mg_device_reset(void) {
> >      > + // SCB->AIRCR = ((0x5fa <<
> >     SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
> >      > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
> >      > }
> >      > +#endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/fs_fat.c"
> >      > +#line 1 "src/dns.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      > -#if MG_ENABLE_FATFS
> >      > -#include <ff.h>
> >      >
> >      > -static int mg_days_from_epoch(int y, int m, int d) {
> >      > - y -= m <= 2;
> >      > - int era = y / 400;
> >      > - int yoe = y - era * 400;
> >      > - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> >      > - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> >      > - return era * 146097 + doe - 719468;
> >      > -}
> >      >
> >      > -static time_t mg_timegm(const struct tm *t) {
> >      > - int year = t->tm_year + 1900;
> >      > - int month = t->tm_mon; // 0-11
> >      > - if (month > 11) {
> >      > - year += month / 12;
> >      > - month %= 12;
> >      > - } else if (month < 0) {
> >      > - int years_diff = (11 - month) / 12;
> >      > - year -= years_diff;
> >      > - month += 12 * years_diff;
> >      > - }
> >      > - int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> >      > - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
> >      > -}
> >      >
> >      > -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> >      > - struct tm tm;
> >      > - memset(&tm, 0, sizeof(struct tm));
> >      > - tm.tm_sec = (ftime << 1) & 0x3e;
> >      > - tm.tm_min = ((ftime >> 5) & 0x3f);
> >      > - tm.tm_hour = ((ftime >> 11) & 0x1f);
> >      > - tm.tm_mday = (fdate & 0x1f);
> >      > - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> >      > - tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> >      > - return mg_timegm(&tm);
> >      > -}
> >      >
> >      > -static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> >      > - FILINFO fi;
> >      > - if (path[0] == '\0') {
> >      > - if (size) *size = 0;
> >      > - if (mtime) *mtime = 0;
> >      > - return MG_FS_DIR;
> >      > - } else if (f_stat(path, &fi) == 0) {
> >      > - if (size) *size = (size_t) fi.fsize;
> >      > - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> >      > - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
> >     MG_FS_DIR : 0);
> >      > - } else {
> >      > - return 0;
> >      > - }
> >      > -}
> >      >
> >      > -static void ff_list(const char *dir, void (*fn)(const char *,
> >     void *),
> >      > - void *userdata) {
> >      > - DIR d;
> >      > - FILINFO fi;
> >      > - if (f_opendir(&d, dir) == FR_OK) {
> >      > - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> >      > - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> >      > - fn(fi.fname, userdata);
> >      > - }
> >      > - f_closedir(&d);
> >      > - }
> >      > -}
> >      > +struct dns_data {
> >      > + struct dns_data *next;
> >      > + struct mg_connection *c;
> >      > + uint64_t expire;
> >      > + uint16_t txnid;
> >      > +};
> >      >
> >      > -static void *ff_open(const char *path, int flags) {
> >      > - FIL f;
> >      > - unsigned char mode = FA_READ;
> >      > - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
> >     FA_OPEN_APPEND;
> >      > - if (f_open(&f, path, mode) == 0) {
> >      > - FIL *fp = calloc(1, sizeof(*fp));
> >      > - memcpy(fp, &f, sizeof(*fp));
> >      > - return fp;
> >      > - } else {
> >      > - return NULL;
> >      > - }
> >      > +static void mg_sendnsreq(struct mg_connection *, struct mg_str
> >     *, int,
> >      > + struct mg_dns *, bool);
> >      > +
> >      > +static void mg_dns_free(struct dns_data **head, struct dns_data
> >     *d) {
> >      > + LIST_DELETE(struct dns_data, head, d);
> >      > + free(d);
> >      > }
> >      >
> >      > -static void ff_close(void *fp) {
> >      > - if (fp != NULL) {
> >      > - f_close((FIL *) fp);
> >      > - free(fp);
> >      > +void mg_resolve_cancel(struct mg_connection *c) {
> >      > + struct dns_data *tmp, *d;
> >      > + struct dns_data **head = (struct dns_data **)
> >     &c->mgr->active_dns_requests;
> >      > + for (d = *head; d != NULL; d = tmp) {
> >      > + tmp = d->next;
> >      > + if (d->c == c) mg_dns_free(head, d);
> >      > }
> >      > }
> >      >
> >      > -static size_t ff_read(void *fp, void *buf, size_t len) {
> >      > - UINT n = 0, misalign = ((size_t) buf) & 3;
> >      > - if (misalign) {
> >      > - char aligned[4];
> >      > - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> >      > - memcpy(buf, aligned, n);
> >      > - } else {
> >      > - f_read((FIL *) fp, buf, len, &n);
> >      > +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
> >     len, size_t ofs,
> >      > + char *to, size_t tolen, size_t j,
> >      > + int depth) {
> >      > + size_t i = 0;
> >      > + if (tolen > 0 && depth == 0) to[0] = '\0';
> >      > + if (depth > 5) return 0;
> >      > + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
> >     + 1]));
> >      > + while (ofs + i + 1 < len) {
> >      > + size_t n = s[ofs + i];
> >      > + if (n == 0) {
> >      > + i++;
> >      > + break;
> >      > + }
> >      > + if (n & 0xc0) {
> >      > + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
> >     len
> >      > + // MG_INFO(("PTR %lx", (unsigned long) ptr));
> >      > + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
> >      > + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
> >     == 0)
> >      > + return 0;
> >      > + i += 2;
> >      > + break;
> >      > + }
> >      > + if (ofs + i + n + 1 >= len) return 0;
> >      > + if (j > 0) {
> >      > + if (j < tolen) to[j] = '.';
> >      > + j++;
> >      > + }
> >      > + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
> >      > + j += n;
> >      > + i += n + 1;
> >      > + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
> >      > + // MG_INFO(("--> [%s]", to));
> >      > }
> >      > - return n;
> >      > + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
> >     is nul-term
> >      > + return i;
> >      > }
> >      >
> >      > -static size_t ff_write(void *fp, const void *buf, size_t len) {
> >      > - UINT n = 0;
> >      > - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
> >     : 0;
> >      > +static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
> >     size_t ofs,
> >      > + char *dst, size_t dstlen) {
> >      > + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
> >      > }
> >      >
> >      > -static size_t ff_seek(void *fp, size_t offset) {
> >      > - f_lseek((FIL *) fp, offset);
> >      > - return offset;
> >      > +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
> >      > + bool is_question, struct mg_dns_rr *rr) {
> >      > + const uint8_t *s = buf + ofs, *e = &buf[len];
> >      > +
> >      > + memset(rr, 0, sizeof(*rr));
> >      > + if (len < sizeof(struct mg_dns_header)) return 0; // Too small
> >      > + if (len > 512) return 0; // Too large, we don't expect that
> >      > + if (s >= e) return 0; // Overflow
> >      > +
> >      > + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
> >     NULL, 0)) == 0)
> >      > + return 0;
> >      > + s += rr->nlen + 4;
> >      > + if (s > e) return 0;
> >      > + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
> >      > + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> >      > + if (is_question) return (size_t) (rr->nlen + 4);
> >      > +
> >      > + s += 6;
> >      > + if (s > e) return 0;
> >      > + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
> >      > + if (s + rr->alen > e) return 0;
> >      > + return (size_t) (rr->nlen + rr->alen + 10);
> >      > }
> >      >
> >      > -static bool ff_rename(const char *from, const char *to) {
> >      > - return f_rename(from, to) == FR_OK;
> >      > +bool mg_dns_parse(const uint8_t *buf, size_t len, struct
> >     mg_dns_message *dm) {
> >      > + const struct mg_dns_header *h = (struct mg_dns_header *) buf;
> >      > + struct mg_dns_rr rr;
> >      > + size_t i, n, num_answers, ofs = sizeof(*h);
> >      > + memset(dm, 0, sizeof(*dm));
> >      > +
> >      > + if (len < sizeof(*h)) return 0; // Too small, headers dont fit
> >      > + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
> >      > + num_answers = mg_ntohs(h->num_answers);
> >      > + if (num_answers > 10) {
> >      > + MG_DEBUG(("Got %u answers, ignoring beyond 10th one",
> >     num_answers));
> >      > + num_answers = 10; // Sanity cap
> >      > + }
> >      > + dm->txnid = mg_ntohs(h->txnid);
> >      > +
> >      > + for (i = 0; i < mg_ntohs(h->num_questions); i++) {
> >      > + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
> >     return false;
> >      > + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
> >      > + ofs += n;
> >      > + }
> >      > + for (i = 0; i < num_answers; i++) {
> >      > + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
> >     return false;
> >      > + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
> >     rr.aclass,
> >      > + // dm->name));
> >      > + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
> >      > + ofs += n;
> >      > +
> >      > + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
> >      > + dm->addr.is_ip6 = false;
> >      > + memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
> >      > + dm->resolved = true;
> >      > + break; // Return success
> >      > + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
> >      > + dm->addr.is_ip6 = true;
> >      > + memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
> >      > + dm->resolved = true;
> >      > + break; // Return success
> >      > + }
> >      > + }
> >      > + return true;
> >      > }
> >      >
> >      > -static bool ff_remove(const char *path) {
> >      > - return f_unlink(path) == FR_OK;
> >      > +static void dns_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + struct dns_data *d, *tmp;
> >      > + struct dns_data **head = (struct dns_data **)
> >     &c->mgr->active_dns_requests;
> >      > + if (ev == MG_EV_POLL) {
> >      > + uint64_t now = *(uint64_t *) ev_data;
> >      > + for (d = *head; d != NULL; d = tmp) {
> >      > + tmp = d->next;
> >      > + // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
> >      > + if (now > d->expire) mg_error(d->c, "DNS timeout");
> >      > + }
> >      > + } else if (ev == MG_EV_READ) {
> >      > + struct mg_dns_message dm;
> >      > + int resolved = 0;
> >      > + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
> >      > + MG_ERROR(("Unexpected DNS response:"));
> >      > + mg_hexdump(c->recv.buf, c->recv.len);
> >      > + } else {
> >      > + // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
> >      > + for (d = *head; d != NULL; d = tmp) {
> >      > + tmp = d->next;
> >      > + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
> >      > + if (dm.txnid != d->txnid) continue;
> >      > + if (d->c->is_resolving) {
> >      > + if (dm.resolved) {
> >      > + dm.addr.port = d->c->rem.port; // Save port
> >      > + d->c->rem = dm.addr; // Copy resolved address
> >      > + MG_DEBUG(
> >      > + ("%lu %s is %M", d->c->id, dm.name <http://dm.name>,
> >     mg_print_ip, &d->c->rem));
> >      > + mg_connect_resolved(d->c);
> >      > +#if MG_ENABLE_IPV6
> >      > + } else if (dm.addr.is_ip6 == false && dm.name
> >     <http://dm.name>[0] != '\0' &&
> >      > + c->mgr->use_dns6 == false) {
> >      > + struct mg_str x = mg_str(dm.name <http://dm.name>);
> >      > + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
> >      > +#endif
> >      > + } else {
> >      > + mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
> >      > + }
> >      > + } else {
> >      > + MG_ERROR(("%lu already resolved", d->c->id));
> >      > + }
> >      > + mg_dns_free(head, d);
> >      > + resolved = 1;
> >      > + }
> >      > + }
> >      > + if (!resolved) MG_ERROR(("stray DNS reply"));
> >      > + c->recv.len = 0;
> >      > + } else if (ev == MG_EV_CLOSE) {
> >      > + for (d = *head; d != NULL; d = tmp) {
> >      > + tmp = d->next;
> >      > + mg_error(d->c, "DNS error");
> >      > + mg_dns_free(head, d);
> >      > + }
> >      > + }
> >      > }
> >      >
> >      > -static bool ff_mkdir(const char *path) {
> >      > - return f_mkdir(path) == FR_OK;
> >      > +static bool mg_dns_send(struct mg_connection *c, const struct
> >     mg_str *name,
> >      > + uint16_t txnid, bool ipv6) {
> >      > + struct {
> >      > + struct mg_dns_header header;
> >      > + uint8_t data[256];
> >      > + } pkt;
> >      > + size_t i, n;
> >      > + memset(&pkt, 0, sizeof(pkt));
> >      > + pkt.header.txnid = mg_htons(txnid);
> >      > + pkt.header.flags = mg_htons(0x100);
> >      > + pkt.header.num_questions = mg_htons(1);
> >      > + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
> >      > + if (name->buf[i] == '.' || i >= name->len) {
> >      > + pkt.data[n] = (uint8_t) (i - n);
> >      > + memcpy(&pkt.data[n + 1], name->buf + n, i - n);
> >      > + n = i + 1;
> >      > + }
> >      > + if (i >= name->len) break;
> >      > + }
> >      > + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
> >      > + n += 5;
> >      > + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
> >      > + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
> >     query
> >      > + // n += 6;
> >      > + return mg_send(c, &pkt, sizeof(pkt.header) + n);
> >      > }
> >      >
> >      > -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
> >     ff_read,
> >      > - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> >      > -#endif
> >      > +static void mg_sendnsreq(struct mg_connection *c, struct mg_str
> >     *name, int ms,
> >      > + struct mg_dns *dnsc, bool ipv6) {
> >      > + struct dns_data *d = NULL;
> >      > + if (dnsc->url == NULL) {
> >      > + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
> >      > + } else if (dnsc->c == NULL) {
> >      > + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
> >      > + if (dnsc->c != NULL) {
> >      > + dnsc->c->pfn = dns_cb;
> >      > + // dnsc->c->is_hexdumping = 1;
> >      > + }
> >      > + }
> >      > + if (dnsc->c == NULL) {
> >      > + mg_error(c, "resolver");
> >      > + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
> >     NULL) {
> >      > + mg_error(c, "resolve OOM");
> >      > + } else {
> >      > + struct dns_data *reqs = (struct dns_data *)
> >     c->mgr->active_dns_requests;
> >      > + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
> >      > + d->next = (struct dns_data *) c->mgr->active_dns_requests;
> >      > + c->mgr->active_dns_requests = d;
> >      > + d->expire = mg_millis() + (uint64_t) ms;
> >      > + d->c = c;
> >      > + c->is_resolving = 1;
> >      > + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
> >     name->len,
> >      > + name->buf, dnsc->url, d->txnid));
> >      > + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
> >      > + mg_error(dnsc->c, "DNS send");
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_resolve(struct mg_connection *c, const char *url) {
> >      > + struct mg_str host = mg_url_host(url);
> >      > + c->rem.port = mg_htons(mg_url_port(url));
> >      > + if (mg_aton(host, &c->rem)) {
> >      > + // host is an IP address, do not fire name resolution
> >      > + mg_connect_resolved(c);
> >      > + } else {
> >      > + // host is not an IP, send DNS resolution request
> >      > + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
> >     &c->mgr->dns4;
> >      > + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
> >      > + }
> >      > +}
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/fs_packed.c"
> >      > +#line 1 "src/event.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      >
> >      > -struct packed_file {
> >      > - const char *data;
> >      > - size_t size;
> >      > - size_t pos;
> >      > -};
> >      >
> >      > -const char *mg_unpack(const char *path, size_t *size, time_t
> >     *mtime);
> >      > -const char *mg_unlist(size_t no);
> >      >
> >      > -#if MG_ENABLE_PACKED_FS
> >      > -#else
> >      > -const char *mg_unpack(const char *path, size_t *size, time_t
> >     *mtime) {
> >      > - (void) path, (void) size, (void) mtime;
> >      > - return NULL;
> >      > -}
> >      > -const char *mg_unlist(size_t no) {
> >      > - (void) no;
> >      > - return NULL;
> >      > -}
> >      > +void mg_call(struct mg_connection *c, int ev, void *ev_data) {
> >      > +#if MG_ENABLE_PROFILE
> >      > + const char *names[] = {
> >      > + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE",
> >      > + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ",
> >      > + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK",
> >      > + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD",
> >      > + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
> >      > + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) /
> >     sizeof(names[0]))) {
> >      > + MG_PROF_ADD(c, names[ev]);
> >      > + }
> >      > #endif
> >      > -
> >      > -static int is_dir_prefix(const char *prefix, size_t n, const
> >     char *path) {
> >      > - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> >      > - return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> >      > - (n == 0 || path[n] == '/' || path[n - 1] == '/');
> >      > + // Fire protocol handler first, user handler second. See #2559
> >      > + if (c->pfn != NULL) c->pfn(c, ev, ev_data);
> >      > + if (c->fn != NULL) c->fn(c, ev, ev_data);
> >      > }
> >      >
> >      > -static int packed_stat(const char *path, size_t *size, time_t
> >     *mtime) {
> >      > - const char *p;
> >      > - size_t i, n = strlen(path);
> >      > - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
> >     file
> >      > - // Scan all files. If `path` is a dir prefix for any of them,
> >     it's a dir
> >      > - for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> >      > - if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
> >      > - }
> >      > - return 0;
> >      > -}
> >      > -
> >      > -static void packed_list(const char *dir, void (*fn)(const char
> >     *, void *),
> >      > - void *userdata) {
> >      > - char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> >      > - const char *path, *begin, *end;
> >      > - size_t i, n = strlen(dir);
> >      > - tmp[0] = '\0'; // Previously listed entry
> >      > - for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> >      > - if (!is_dir_prefix(dir, n, path)) continue;
> >      > - begin = &path[n + 1];
> >      > - end = strchr(begin, '/');
> >      > - if (end == NULL) end = begin + strlen(begin);
> >      > - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> >      > - buf[sizeof(buf) - 1] = '\0';
> >      > - // If this entry has been already listed, skip
> >      > - // NOTE: we're assuming that file list is sorted alphabetically
> >      > - if (strcmp(buf, tmp) == 0) continue;
> >      > - fn(buf, userdata); // Not yet listed, call user function
> >      > - strcpy(tmp, buf); // And save this entry as listed
> >      > - }
> >      > -}
> >      > -
> >      > -static void *packed_open(const char *path, int flags) {
> >      > - size_t size = 0;
> >      > - const char *data = mg_unpack(path, &size, NULL);
> >      > - struct packed_file *fp = NULL;
> >      > - if (data == NULL) return NULL;
> >      > - if (flags & MG_FS_WRITE) return NULL;
> >      > - fp = (struct packed_file *) calloc(1, sizeof(*fp));
> >      > - fp->size = size;
> >      > - fp->data = data;
> >      > - return (void *) fp;
> >      > +void mg_error(struct mg_connection *c, const char *fmt, ...) {
> >      > + char buf[64];
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
> >      > + va_end(ap);
> >      > + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
> >      > + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
> >      > + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it
> >      > }
> >      >
> >      > -static void packed_close(void *fp) {
> >      > - if (fp != NULL) free(fp);
> >      > -}
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/fmt.c"
> >      > +#endif
> >      >
> >      > -static size_t packed_read(void *fd, void *buf, size_t len) {
> >      > - struct packed_file *fp = (struct packed_file *) fd;
> >      > - if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> >      > - memcpy(buf, &fp->data[fp->pos], len);
> >      > - fp->pos += len;
> >      > - return len;
> >      > -}
> >      >
> >      > -static size_t packed_write(void *fd, const void *buf, size_t len) {
> >      > - (void) fd, (void) buf, (void) len;
> >      > - return 0;
> >      > -}
> >      >
> >      > -static size_t packed_seek(void *fd, size_t offset) {
> >      > - struct packed_file *fp = (struct packed_file *) fd;
> >      > - fp->pos = offset;
> >      > - if (fp->pos > fp->size) fp->pos = fp->size;
> >      > - return fp->pos;
> >      > -}
> >      >
> >      > -static bool packed_rename(const char *from, const char *to) {
> >      > - (void) from, (void) to;
> >      > - return false;
> >      > +static bool is_digit(int c) {
> >      > + return c >= '0' && c <= '9';
> >      > }
> >      >
> >      > -static bool packed_remove(const char *path) {
> >      > - (void) path;
> >      > - return false;
> >      > +static int addexp(char *buf, int e, int sign) {
> >      > + int n = 0;
> >      > + buf[n++] = 'e';
> >      > + buf[n++] = (char) sign;
> >      > + if (e > 400) return 0;
> >      > + if (e < 10) buf[n++] = '0';
> >      > + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
> >     / 100);
> >      > + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
> >      > + buf[n++] = (char) (e + '0');
> >      > + return n;
> >      > }
> >      >
> >      > -static bool packed_mkdir(const char *path) {
> >      > - (void) path;
> >      > - return false;
> >      > +static int xisinf(double x) {
> >      > + union {
> >      > + double f;
> >      > + uint64_t u;
> >      > + } ieee754 = {x};
> >      > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
> >     0x7ff00000 &&
> >      > + ((unsigned) ieee754.u == 0);
> >      > }
> >      >
> >      > -struct mg_fs mg_fs_packed = {
> >      > - packed_stat, packed_list, packed_open, packed_close, packed_read,
> >      > - packed_write, packed_seek, packed_rename, packed_remove,
> >     packed_mkdir};
> >      > -
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/fs_posix.c"
> >      > -#endif
> >      > -
> >      > -
> >      > -#if MG_ENABLE_FILE
> >      > -
> >      > -#ifndef MG_STAT_STRUCT
> >      > -#define MG_STAT_STRUCT stat
> >      > -#endif
> >      > -
> >      > -#ifndef MG_STAT_FUNC
> >      > -#define MG_STAT_FUNC stat
> >      > -#endif
> >      > -
> >      > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> >      > -#if !defined(S_ISDIR)
> >      > - MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
> >     mtime));
> >      > - return 0;
> >      > -#else
> >      > -#if MG_ARCH == MG_ARCH_WIN32
> >      > - struct _stati64 st;
> >      > - wchar_t tmp[MG_PATH_MAX];
> >      > - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
> >     sizeof(tmp[0]));
> >      > - if (_wstati64(tmp, &st) != 0) return 0;
> >      > -#else
> >      > - struct MG_STAT_STRUCT st;
> >      > - if (MG_STAT_FUNC(path, &st) != 0) return 0;
> >      > -#endif
> >      > - if (size) *size = (size_t) st.st_size;
> >      > - if (mtime) *mtime = st.st_mtime;
> >      > - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
> >     MG_FS_DIR : 0);
> >      > -#endif
> >      > +static int xisnan(double x) {
> >      > + union {
> >      > + double f;
> >      > + uint64_t u;
> >      > + } ieee754 = {x};
> >      > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
> >      > + ((unsigned) ieee754.u != 0) >
> >      > + 0x7ff00000;
> >      > }
> >      >
> >      > -#if MG_ARCH == MG_ARCH_WIN32
> >      > -struct dirent {
> >      > - char d_name[MAX_PATH];
> >      > -};
> >      > -
> >      > -typedef struct win32_dir {
> >      > - HANDLE handle;
> >      > - WIN32_FIND_DATAW info;
> >      > - struct dirent result;
> >      > -} DIR;
> >      > +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
> >     width, bool tz) {
> >      > + char buf[40];
> >      > + int i, s = 0, n = 0, e = 0;
> >      > + double t, mul, saved;
> >      > + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
> >      > + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
> >     "inf" : "-inf");
> >      > + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
> >      > + if (d < 0.0) d = -d, buf[s++] = '-';
> >      >
> >      > -int gettimeofday(struct timeval *tv, void *tz) {
> >      > - FILETIME ft;
> >      > - unsigned __int64 tmpres = 0;
> >      > + // Round
> >      > + saved = d;
> >      > + mul = 1.0;
> >      > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
> >      > + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
> >      > + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
> >      > + d += t;
> >      > + // Calculate exponent, and 'mul' for scientific representation
> >      > + mul = 1.0;
> >      > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
> >      > + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
> >      > + // printf(" --> %g %d %g %g\n", saved, e, t, mul);
> >      >
> >      > - if (tv != NULL) {
> >      > - GetSystemTimeAsFileTime(&ft);
> >      > - tmpres |= ft.dwHighDateTime;
> >      > - tmpres <<= 32;
> >      > - tmpres |= ft.dwLowDateTime;
> >      > - tmpres /= 10; // convert into microseconds
> >      > - tmpres -= (int64_t) 11644473600000000;
> >      > - tv->tv_sec = (long) (tmpres / 1000000UL);
> >      > - tv->tv_usec = (long) (tmpres % 1000000UL);
> >      > + if (e >= width && width > 1) {
> >      > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> >      > + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
> >      > + n += addexp(buf + s + n, e, '+');
> >      > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> >      > + } else if (e <= -width && width > 1) {
> >      > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
> >      > + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
> >      > + n += addexp(buf + s + n, -e, '-');
> >      > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
> >      > + } else {
> >      > + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
> >      > + int ch = (int) (d / t);
> >      > + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
> >      > + d -= ch * t;
> >      > + t /= 10.0;
> >      > + }
> >      > + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
> >     + n, buf);
> >      > + if (n == 0) buf[s++] = '0';
> >      > + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
> >     /= 10.0;
> >      > + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
> >      > + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
> >      > + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
> >     i++) {
> >      > + int ch = (int) (d / t);
> >      > + buf[s + n++] = (char) (ch + '0');
> >      > + d -= ch * t;
> >      > + t /= 10.0;
> >      > + }
> >      > }
> >      > - (void) tz;
> >      > - return 0;
> >      > + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim
> >     trailing zeroes
> >      > + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
> >      > + n += s;
> >      > + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
> >      > + buf[n] = '\0';
> >      > + return mg_snprintf(dst, dstlen, "%s", buf);
> >      > }
> >      >
> >      > -static int to_wchar(const char *path, wchar_t *wbuf, size_t
> >     wbuf_len) {
> >      > - int ret;
> >      > - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> >      > - strncpy(buf, path, sizeof(buf));
> >      > - buf[sizeof(buf) - 1] = '\0';
> >      > - // Trim trailing slashes. Leave backslash for paths like "X:\"
> >      > - p = buf + strlen(buf) - 1;
> >      > - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
> >     '/')) *p-- = '\0';
> >      > - memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> >      > - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
> >     wbuf_len);
> >      > - // Convert back to Unicode. If doubly-converted string does not
> >     match the
> >      > - // original, something is fishy, reject.
> >      > - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
> >     sizeof(buf2),
> >      > - NULL, NULL);
> >      > - if (strcmp(buf, buf2) != 0) {
> >      > - wbuf[0] = L'\0';
> >      > - ret = 0;
> >      > +static size_t mg_lld(char *buf, int64_t val, bool is_signed,
> >     bool is_hex) {
> >      > + const char *letters = "0123456789abcdef";
> >      > + uint64_t v = (uint64_t) val;
> >      > + size_t s = 0, n, i;
> >      > + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
> >      > + // This loop prints a number in reverse order. I guess this is
> >     because we
> >      > + // write numbers from right to left: least significant digit
> >     comes last.
> >      > + // Maybe because we use Arabic numbers, and Arabs write RTL?
> >      > + if (is_hex) {
> >      > + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
> >      > + } else {
> >      > + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
> >      > }
> >      > - return ret;
> >      > + // Reverse a string
> >      > + for (i = 0; i < n / 2; i++) {
> >      > + char t = buf[s + i];
> >      > + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
> >      > + }
> >      > + if (val == 0) buf[n++] = '0'; // Handle special case
> >      > + return n + s;
> >      > }
> >      >
> >      > -DIR *opendir(const char *name) {
> >      > - DIR *d = NULL;
> >      > - wchar_t wpath[MAX_PATH];
> >      > - DWORD attrs;
> >      > +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
> >      > + size_t len) {
> >      > + size_t i = 0;
> >      > + while (i < len && buf[i] != '\0') out(buf[i++], ptr);
> >      > + return i;
> >      > +}
> >      >
> >      > - if (name == NULL) {
> >      > - SetLastError(ERROR_BAD_ARGUMENTS);
> >      > - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> >      > - SetLastError(ERROR_NOT_ENOUGH_MEMORY);
> >      > - } else {
> >      > - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> >      > - attrs = GetFileAttributesW(wpath);
> >      > - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> >      > - (void) wcscat(wpath, L"\\*");
> >      > - d->handle = FindFirstFileW(wpath, &d->info);
> >      > - d->result.d_name[0] = '\0';
> >      > +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
> >     char *fmt, ...) {
> >      > + size_t len = 0;
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + len = mg_vxprintf(out, ptr, fmt, &ap);
> >      > + va_end(ap);
> >      > + return len;
> >      > +}
> >      > +
> >      > +size_t mg_vxprintf(void (*out)(char, void *), void *param, const
> >     char *fmt,
> >      > + va_list *ap) {
> >      > + size_t i = 0, n = 0;
> >      > + while (fmt[i] != '\0') {
> >      > + if (fmt[i] == '%') {
> >      > + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
> >     prec */;
> >      > + char pad = ' ', minus = 0, c = fmt[++i];
> >      > + if (c == '#') x++, c = fmt[++i];
> >      > + if (c == '-') minus++, c = fmt[++i];
> >      > + if (c == '0') pad = '0', c = fmt[++i];
> >      > + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
> >     fmt[++i];
> >      > + if (c == '.') {
> >      > + c = fmt[++i];
> >      > + if (c == '*') {
> >      > + pr = (size_t) va_arg(*ap, int);
> >      > + c = fmt[++i];
> >      > + } else {
> >      > + pr = 0;
> >      > + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
> >     fmt[++i];
> >      > + }
> >      > + }
> >      > + while (c == 'h') c = fmt[++i]; // Treat h and hh as int
> >      > + if (c == 'l') {
> >      > + is_long++, c = fmt[++i];
> >      > + if (c == 'l') is_long++, c = fmt[++i];
> >      > + }
> >      > + if (c == 'p') x = 1, is_long = 1;
> >      > + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
> >      > + c == 'g' || c == 'f') {
> >      > + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
> >      > + char tmp[40];
> >      > + size_t xl = x ? 2 : 0;
> >      > + if (c == 'g' || c == 'f') {
> >      > + double v = va_arg(*ap, double);
> >      > + if (pr == ~0U) pr = 6;
> >      > + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
> >      > + } else if (is_long == 2) {
> >      > + int64_t v = va_arg(*ap, int64_t);
> >      > + k = mg_lld(tmp, v, s, h);
> >      > + } else if (is_long == 1) {
> >      > + long v = va_arg(*ap, long);
> >      > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
> >     s, h);
> >      > + } else {
> >      > + int v = va_arg(*ap, int);
> >      > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
> >      > + }
> >      > + for (j = 0; j < xl && w > 0; j++) w--;
> >      > + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
> >      > + n += scpy(out, param, &pad, 1);
> >      > + n += scpy(out, param, (char *) "0x", xl);
> >      > + for (j = 0; pad == '0' && k < w && j + k < w; j++)
> >      > + n += scpy(out, param, &pad, 1);
> >      > + n += scpy(out, param, tmp, k);
> >      > + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
> >      > + n += scpy(out, param, &pad, 1);
> >      > + } else if (c == 'm' || c == 'M') {
> >      > + mg_pm_t f = va_arg(*ap, mg_pm_t);
> >      > + if (c == 'm') out('"', param);
> >      > + n += f(out, param, ap);
> >      > + if (c == 'm') n += 2, out('"', param);
> >      > + } else if (c == 'c') {
> >      > + int ch = va_arg(*ap, int);
> >      > + out((char) ch, param);
> >      > + n++;
> >      > + } else if (c == 's') {
> >      > + char *p = va_arg(*ap, char *);
> >      > + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
> >      > + for (j = 0; !minus && pr < w && j + pr < w; j++)
> >      > + n += scpy(out, param, &pad, 1);
> >      > + n += scpy(out, param, p, pr);
> >      > + for (j = 0; minus && pr < w && j + pr < w; j++)
> >      > + n += scpy(out, param, &pad, 1);
> >      > + } else if (c == '%') {
> >      > + out('%', param);
> >      > + n++;
> >      > + } else {
> >      > + out('%', param);
> >      > + out(c, param);
> >      > + n += 2;
> >      > + }
> >      > + i++;
> >      > } else {
> >      > - free(d);
> >      > - d = NULL;
> >      > + out(fmt[i], param), n++, i++;
> >      > }
> >      > }
> >      > - return d;
> >      > + return n;
> >      > }
> >      >
> >      > -int closedir(DIR *d) {
> >      > - int result = 0;
> >      > - if (d != NULL) {
> >      > - if (d->handle != INVALID_HANDLE_VALUE)
> >      > - result = FindClose(d->handle) ? 0 : -1;
> >      > - free(d);
> >      > - } else {
> >      > - result = -1;
> >      > - SetLastError(ERROR_BAD_ARGUMENTS);
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/fs.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
> >     flags) {
> >      > + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
> >      > + if (fd != NULL) {
> >      > + fd->fd = fs->op(path, flags);
> >      > + fd->fs = fs;
> >      > + if (fd->fd == NULL) {
> >      > + free(fd);
> >      > + fd = NULL;
> >      > + }
> >      > }
> >      > - return result;
> >      > + return fd;
> >      > }
> >      >
> >      > -struct dirent *readdir(DIR *d) {
> >      > - struct dirent *result = NULL;
> >      > - if (d != NULL) {
> >      > - memset(&d->result, 0, sizeof(d->result));
> >      > - if (d->handle != INVALID_HANDLE_VALUE) {
> >      > - result = &d->result;
> >      > - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
> >     result->d_name,
> >      > - sizeof(result->d_name), NULL, NULL);
> >      > - if (!FindNextFileW(d->handle, &d->info)) {
> >      > - FindClose(d->handle);
> >      > - d->handle = INVALID_HANDLE_VALUE;
> >      > - }
> >      > - } else {
> >      > - SetLastError(ERROR_FILE_NOT_FOUND);
> >      > - }
> >      > - } else {
> >      > - SetLastError(ERROR_BAD_ARGUMENTS);
> >      > +void mg_fs_close(struct mg_fd *fd) {
> >      > + if (fd != NULL) {
> >      > + fd->fs->cl(fd->fd);
> >      > + free(fd);
> >      > }
> >      > - return result;
> >      > }
> >      > -#endif
> >      >
> >      > -static void p_list(const char *dir, void (*fn)(const char *,
> >     void *),
> >      > - void *userdata) {
> >      > -#if MG_ENABLE_DIRLIST
> >      > - struct dirent *dp;
> >      > - DIR *dirp;
> >      > - if ((dirp = (opendir(dir))) == NULL) return;
> >      > - while ((dp = readdir(dirp)) != NULL) {
> >      > - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
> >     continue;
> >      > - fn(dp->d_name, userdata);
> >      > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
> >      > + struct mg_str result = {NULL, 0};
> >      > + void *fp;
> >      > + fs->st(path, &result.len, NULL);
> >      > + if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
> >      > + result.buf = (char *) calloc(1, result.len + 1);
> >      > + if (result.buf != NULL &&
> >      > + fs->rd(fp, (void *) result.buf, result.len) != result.len) {
> >      > + free((void *) result.buf);
> >      > + result.buf = NULL;
> >      > + }
> >      > + fs->cl(fp);
> >      > }
> >      > - closedir(dirp);
> >      > -#else
> >      > - (void) dir, (void) fn, (void) userdata;
> >      > -#endif
> >      > + if (result.buf == NULL) result.len = 0;
> >      > + return result;
> >      > }
> >      >
> >      > -static void *p_open(const char *path, int flags) {
> >      > - const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> >      > -#if MG_ARCH == MG_ARCH_WIN32
> >      > - wchar_t b1[MG_PATH_MAX], b2[10];
> >      > - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
> >     sizeof(b1[0]));
> >      > - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
> >     sizeof(b2[0]));
> >      > - return (void *) _wfopen(b1, b2);
> >      > -#else
> >      > - return (void *) fopen(path, mode);
> >      > -#endif
> >      > +bool mg_file_write(struct mg_fs *fs, const char *path, const
> >     void *buf,
> >      > + size_t len) {
> >      > + bool result = false;
> >      > + struct mg_fd *fd;
> >      > + char tmp[MG_PATH_MAX];
> >      > + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
> >      > + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
> >      > + result = fs->wr(fd->fd, buf, len) == len;
> >      > + mg_fs_close(fd);
> >      > + if (result) {
> >      > + fs->rm(path);
> >      > + fs->mv(tmp, path);
> >      > + } else {
> >      > + fs->rm(tmp);
> >      > + }
> >      > + }
> >      > + return result;
> >      > }
> >      >
> >      > -static void p_close(void *fp) {
> >      > - fclose((FILE *) fp);
> >      > +bool mg_file_printf(struct mg_fs *fs, const char *path, const
> >     char *fmt, ...) {
> >      > + va_list ap;
> >      > + char *data;
> >      > + bool result = false;
> >      > + va_start(ap, fmt);
> >      > + data = mg_vmprintf(fmt, &ap);
> >      > + va_end(ap);
> >      > + result = mg_file_write(fs, path, data, strlen(data));
> >      > + free(data);
> >      > + return result;
> >      > }
> >      >
> >      > -static size_t p_read(void *fp, void *buf, size_t len) {
> >      > - return fread(buf, 1, len, (FILE *) fp);
> >      > +// This helper function allows to scan a filesystem in a
> >     sequential way,
> >      > +// without using callback function:
> >      > +// char buf[100] = "";
> >      > +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
> >      > +// ...
> >      > +static void mg_fs_ls_fn(const char *filename, void *param) {
> >      > + struct mg_str *s = (struct mg_str *) param;
> >      > + if (s->buf[0] == '\0') {
> >      > + mg_snprintf((char *) s->buf, s->len, "%s", filename);
> >      > + } else if (strcmp(s->buf, filename) == 0) {
> >      > + ((char *) s->buf)[0] = '\0'; // Fetch next file
> >      > + }
> >      > }
> >      >
> >      > -static size_t p_write(void *fp, const void *buf, size_t len) {
> >      > - return fwrite(buf, 1, len, (FILE *) fp);
> >      > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
> >     size_t len) {
> >      > + struct mg_str s = {buf, len};
> >      > + fs->ls(path, mg_fs_ls_fn, &s);
> >      > + return buf[0] != '\0';
> >      > }
> >      >
> >      > -static size_t p_seek(void *fp, size_t offset) {
> >      > -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
> >      > - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> >      > - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> >      > - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> >      > -#else
> >      > - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/fs_fat.c"
> >      > #endif
> >      > - return (size_t) ftell((FILE *) fp);
> >      > -}
> >      >
> >      > -static bool p_rename(const char *from, const char *to) {
> >      > - return rename(from, to) == 0;
> >      > -}
> >      >
> >      > -static bool p_remove(const char *path) {
> >      > - return remove(path) == 0;
> >      > -}
> >      >
> >      > -static bool p_mkdir(const char *path) {
> >      > - return mkdir(path, 0775) == 0;
> >      > -}
> >      > +#if MG_ENABLE_FATFS
> >      > +#include <ff.h>
> >      >
> >      > -#else
> >      > +static int mg_days_from_epoch(int y, int m, int d) {
> >      > + y -= m <= 2;
> >      > + int era = y / 400;
> >      > + int yoe = y - era * 400;
> >      > + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
> >      > + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
> >      > + return era * 146097 + doe - 719468;
> >      > +}
> >      >
> >      > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
> >      > - (void) path, (void) size, (void) mtime;
> >      > - return 0;
> >      > +static time_t mg_timegm(const struct tm *t) {
> >      > + int year = t->tm_year + 1900;
> >      > + int month = t->tm_mon; // 0-11
> >      > + if (month > 11) {
> >      > + year += month / 12;
> >      > + month %= 12;
> >      > + } else if (month < 0) {
> >      > + int years_diff = (11 - month) / 12;
> >      > + year -= years_diff;
> >      > + month += 12 * years_diff;
> >      > + }
> >      > + int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
> >      > + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
> >      > }
> >      > -static void p_list(const char *path, void (*fn)(const char *,
> >     void *),
> >      > - void *userdata) {
> >      > - (void) path, (void) fn, (void) userdata;
> >      > -}
> >      > -static void *p_open(const char *path, int flags) {
> >      > - (void) path, (void) flags;
> >      > - return NULL;
> >      > -}
> >      > -static void p_close(void *fp) {
> >      > - (void) fp;
> >      > -}
> >      > -static size_t p_read(void *fd, void *buf, size_t len) {
> >      > - (void) fd, (void) buf, (void) len;
> >      > - return 0;
> >      > -}
> >      > -static size_t p_write(void *fd, const void *buf, size_t len) {
> >      > - (void) fd, (void) buf, (void) len;
> >      > - return 0;
> >      > -}
> >      > -static size_t p_seek(void *fd, size_t offset) {
> >      > - (void) fd, (void) offset;
> >      > - return (size_t) ~0;
> >      > -}
> >      > -static bool p_rename(const char *from, const char *to) {
> >      > - (void) from, (void) to;
> >      > - return false;
> >      > -}
> >      > -static bool p_remove(const char *path) {
> >      > - (void) path;
> >      > - return false;
> >      > -}
> >      > -static bool p_mkdir(const char *path) {
> >      > - (void) path;
> >      > - return false;
> >      > -}
> >      > -#endif
> >      > -
> >      > -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
> >     p_read,
> >      > - p_write, p_seek, p_rename, p_remove, p_mkdir};
> >      > -
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/http.c"
> >      > -#endif
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -// Chunk deletion marker is the MSB in the "processed" counter
> >      > -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> >      > -
> >      > -// Multipart POST example:
> >      > -// --xyz
> >      > -// Content-Disposition: form-data; name="val"
> >      > -//
> >      > -// abcdef
> >      > -// --xyz
> >      > -// Content-Disposition: form-data; name="foo"; filename="a.txt"
> >      > -// Content-Type: text/plain
> >      > -//
> >      > -// hello world
> >      > -//
> >      > -// --xyz--
> >      > -size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> >      > - struct mg_http_part *part) {
> >      > - struct mg_str cd = mg_str_n("Content-Disposition", 19);
> >      > - const char *s = body.ptr;
> >      > - size_t b = ofs, h1, h2, b1, b2, max = body.len;
> >      > -
> >      > - // Init part params
> >      > - if (part != NULL) part->name = part->filename = part->body =
> >     mg_str_n(0, 0);
> >      > -
> >      > - // Skip boundary
> >      > - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> >      > - if (b <= ofs || b + 2 >= max) return 0;
> >      > - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
> >     s));
> >      > -
> >      > - // Skip headers
> >      > - h1 = h2 = b + 2;
> >      > - for (;;) {
> >      > - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> >      > - if (h2 == h1) break;
> >      > - if (h2 + 2 >= max) return 0;
> >      > - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> >      > - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
> >     ':' &&
> >      > - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
> >      > - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
> >     cd.len + 2));
> >      > - part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> >      > - part->filename = mg_http_get_header_var(v, mg_str_n("filename",
> >     8));
> >      > - }
> >      > - h1 = h2 = h2 + 2;
> >      > - }
> >      > - b1 = b2 = h2 + 2;
> >      > - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
> >     + 1] == '\n' &&
> >      > - memcmp(&s[b2 + 2], s, b - ofs) == 0))
> >      > - b2++;
> >      >
> >      > - if (b2 + 2 >= max) return 0;
> >      > - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> >      > - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> >      > - return b2 + 2;
> >      > +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
> >      > + struct tm tm;
> >      > + memset(&tm, 0, sizeof(struct tm));
> >      > + tm.tm_sec = (ftime << 1) & 0x3e;
> >      > + tm.tm_min = ((ftime >> 5) & 0x3f);
> >      > + tm.tm_hour = ((ftime >> 11) & 0x1f);
> >      > + tm.tm_mday = (fdate & 0x1f);
> >      > + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
> >      > + tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
> >      > + return mg_timegm(&tm);
> >      > }
> >      >
> >      > -void mg_http_bauth(struct mg_connection *c, const char *user,
> >      > - const char *pass) {
> >      > - struct mg_str u = mg_str(user), p = mg_str(pass);
> >      > - size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> >      > - if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> >      > - if (c->send.size >= need) {
> >      > - int i, n = 0;
> >      > - char *buf = (char *) &c->send.buf[c->send.len];
> >      > - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
> >      > - for (i = 0; i < (int) u.len; i++) {
> >      > - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
> >      > - }
> >      > - if (p.len > 0) {
> >      > - n = mg_base64_update(':', buf + 21, n);
> >      > - for (i = 0; i < (int) p.len; i++) {
> >      > - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
> >      > - }
> >      > - }
> >      > - n = mg_base64_final(buf + 21, n);
> >      > - c->send.len += 21 + (size_t) n + 2;
> >      > - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> >      > +static int ff_stat(const char *path, size_t *size, time_t *mtime) {
> >      > + FILINFO fi;
> >      > + if (path[0] == '\0') {
> >      > + if (size) *size = 0;
> >      > + if (mtime) *mtime = 0;
> >      > + return MG_FS_DIR;
> >      > + } else if (f_stat(path, &fi) == 0) {
> >      > + if (size) *size = (size_t) fi.fsize;
> >      > + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
> >      > + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
> >     MG_FS_DIR : 0);
> >      > } else {
> >      > - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
> >     need));
> >      > + return 0;
> >      > }
> >      > }
> >      >
> >      > -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> >      > - struct mg_str k, v, result = mg_str_n(NULL, 0);
> >      > - while (mg_split(&buf, &k, &v, '&')) {
> >      > - if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) ==
> >     0) {
> >      > - result = v;
> >      > - break;
> >      > +static void ff_list(const char *dir, void (*fn)(const char *,
> >     void *),
> >      > + void *userdata) {
> >      > + DIR d;
> >      > + FILINFO fi;
> >      > + if (f_opendir(&d, dir) == FR_OK) {
> >      > + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
> >      > + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
> >      > + fn(fi.fname, userdata);
> >      > }
> >      > + f_closedir(&d);
> >      > }
> >      > - return result;
> >      > }
> >      >
> >      > -int mg_http_get_var(const struct mg_str *buf, const char *name,
> >     char *dst,
> >      > - size_t dst_len) {
> >      > - int len;
> >      > - if (dst == NULL || dst_len == 0) {
> >      > - len = -2; // Bad destination
> >      > - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
> >      > - len = -1; // Bad source
> >      > - dst[0] = '\0';
> >      > - } else {
> >      > - struct mg_str v = mg_http_var(*buf, mg_str(name));
> >      > - if (v.ptr == NULL) {
> >      > - len = -4; // Name does not exist
> >      > - } else {
> >      > - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
> >      > - if (len < 0) len = -3; // Failed to decode
> >      > +static void *ff_open(const char *path, int flags) {
> >      > + FIL f;
> >      > + unsigned char mode = FA_READ;
> >      > + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
> >     FA_OPEN_APPEND;
> >      > + if (f_open(&f, path, mode) == 0) {
> >      > + FIL *fp;
> >      > + if ((fp = calloc(1, sizeof(*fp))) != NULL) {
> >      > + memcpy(fp, &f, sizeof(*fp));
> >      > + return fp;
> >      > }
> >      > }
> >      > - return len;
> >      > + return NULL;
> >      > }
> >      >
> >      > -static bool isx(int c) {
> >      > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> >      > - (c >= 'A' && c <= 'F');
> >      > +static void ff_close(void *fp) {
> >      > + if (fp != NULL) {
> >      > + f_close((FIL *) fp);
> >      > + free(fp);
> >      > + }
> >      > }
> >      >
> >      > -int mg_url_decode(const char *src, size_t src_len, char *dst,
> >     size_t dst_len,
> >      > - int is_form_url_encoded) {
> >      > - size_t i, j;
> >      > - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> >      > - if (src[i] == '%') {
> >      > - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
> >     src_len
> >      > - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> >      > - mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
> >      > - i += 2;
> >      > - } else {
> >      > - return -1;
> >      > - }
> >      > - } else if (is_form_url_encoded && src[i] == '+') {
> >      > - dst[j] = ' ';
> >      > - } else {
> >      > - dst[j] = src[i];
> >      > - }
> >      > +static size_t ff_read(void *fp, void *buf, size_t len) {
> >      > + UINT n = 0, misalign = ((size_t) buf) & 3;
> >      > + if (misalign) {
> >      > + char aligned[4];
> >      > + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
> >      > + memcpy(buf, aligned, n);
> >      > + } else {
> >      > + f_read((FIL *) fp, buf, len, &n);
> >      > }
> >      > - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
> >      > - return i >= src_len && j < dst_len ? (int) j : -1;
> >      > + return n;
> >      > }
> >      >
> >      > -static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c
> >      >= ' '; }
> >      > -
> >      > -int mg_http_get_request_len(const unsigned char *buf, size_t
> >     buf_len) {
> >      > - size_t i;
> >      > - for (i = 0; i < buf_len; i++) {
> >      > - if (!isok(buf[i])) return -1;
> >      > - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> >      > - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
> >     '\n'))
> >      > - return (int) i + 1;
> >      > - }
> >      > - return 0;
> >      > +static size_t ff_write(void *fp, const void *buf, size_t len) {
> >      > + UINT n = 0;
> >      > + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
> >     : 0;
> >      > }
> >      >
> >      > -static const char *skip(const char *s, const char *e, const char
> >     *d,
> >      > - struct mg_str *v) {
> >      > - v->ptr = s;
> >      > - while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
> >      > - v->len = (size_t) (s - v->ptr);
> >      > - while (s < e && strchr(d, *s) != NULL) s++;
> >      > - return s;
> >      > +static size_t ff_seek(void *fp, size_t offset) {
> >      > + f_lseek((FIL *) fp, offset);
> >      > + return offset;
> >      > }
> >      >
> >      > -struct mg_str *mg_http_get_header(struct mg_http_message *h,
> >     const char *name) {
> >      > - size_t i, n = strlen(name), max = sizeof(h->headers) /
> >     sizeof(h->headers[0]);
> >      > - for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> >      > - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> >      > - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
> >      > - }
> >      > - return NULL;
> >      > +static bool ff_rename(const char *from, const char *to) {
> >      > + return f_rename(from, to) == FR_OK;
> >      > }
> >      >
> >      > -static void mg_http_parse_headers(const char *s, const char *end,
> >      > - struct mg_http_header *h, int max_headers) {
> >      > - int i;
> >      > - for (i = 0; i < max_headers; i++) {
> >      > - struct mg_str k, v, tmp;
> >      > - const char *he = skip(s, end, "\n", &tmp);
> >      > - s = skip(s, he, ": \r\n", &k);
> >      > - s = skip(s, he, "\r\n", &v);
> >      > - if (k.len == tmp.len) continue;
> >      > - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim
> >     spaces
> >      > - if (k.len == 0) break;
> >      > - // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1,
> >     tmp.ptr,
> >      > - //(int) k.len, k.ptr, (int) v.len, v.ptr));
> >      > - h[i].name = k;
> >      > - h[i].value = v;
> >      > - }
> >      > +static bool ff_remove(const char *path) {
> >      > + return f_unlink(path) == FR_OK;
> >      > }
> >      >
> >      > -int mg_http_parse(const char *s, size_t len, struct
> >     mg_http_message *hm) {
> >      > - int is_response, req_len = mg_http_get_request_len((unsigned
> >     char *) s, len);
> >      > - const char *end = s == NULL ? NULL : s + req_len, *qs; //
> >     Cannot add to NULL
> >      > - struct mg_str *cl;
> >      > +static bool ff_mkdir(const char *path) {
> >      > + return f_mkdir(path) == FR_OK;
> >      > +}
> >      >
> >      > - memset(hm, 0, sizeof(*hm));
> >      > - if (req_len <= 0) return req_len;
> >      > +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
> >     ff_read,
> >      > + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
> >      > +#endif
> >      >
> >      > - hm->message.ptr = hm->head.ptr = s;
> >      > - hm->body.ptr = end;
> >      > - hm->head.len = (size_t) req_len;
> >      > - hm->chunk.ptr = end;
> >      > - hm->message.len = hm->body.len = (size_t) ~0; // Set body
> >     length to infinite
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/fs_packed.c"
> >      > +#endif
> >      >
> >      > - // Parse request line
> >      > - s = skip(s, end, " ", &hm->method);
> >      > - s = skip(s, end, " ", &hm->uri);
> >      > - s = skip(s, end, "\r\n", &hm->proto);
> >      >
> >      > - // Sanity check. Allow protocol/reason to be empty
> >      > - if (hm->method.len == 0 || hm->uri.len == 0) return -1;
> >      >
> >      > - // If URI contains '?' character, setup query string
> >      > - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len))
> >     != NULL) {
> >      > - hm->query.ptr = qs + 1;
> >      > - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
> >      > - hm->uri.len = (size_t) (qs - hm->uri.ptr);
> >      > - }
> >      >
> >      > - mg_http_parse_headers(s, end, hm->headers,
> >      > - sizeof(hm->headers) / sizeof(hm->headers[0]));
> >      > - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> >      > - hm->body.len = (size_t) mg_to64(*cl);
> >      > - hm->message.len = (size_t) req_len + hm->body.len;
> >      > - }
> >      > +struct packed_file {
> >      > + const char *data;
> >      > + size_t size;
> >      > + size_t pos;
> >      > +};
> >      >
> >      > - // mg_http_parse() is used to parse both HTTP requests and HTTP
> >      > - // responses. If HTTP response does not have Content-Length
> >     set, then
> >      > - // body is read until socket is closed, i.e. body.len is
> >     infinite (~0).
> >      > - //
> >      > - // For HTTP requests though, according to
> >      > - // http://tools.ietf.org/html/rfc7231#section-8.1.3
> >     <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
> >      > - // only POST and PUT methods have defined body semantics.
> >      > - // Therefore, if Content-Length is not specified and methods are
> >      > - // not one of PUT or POST, set body length to 0.
> >      > - //
> >      > - // So, if it is HTTP request, and Content-Length is not set,
> >      > - // and method is not (PUT or POST) then reset body length to zero.
> >      > - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
> >      > - if (hm->body.len == (size_t) ~0 && !is_response &&
> >      > - mg_vcasecmp(&hm->method, "PUT") != 0 &&
> >      > - mg_vcasecmp(&hm->method, "POST") != 0) {
> >      > - hm->body.len = 0;
> >      > - hm->message.len = (size_t) req_len;
> >      > - }
> >      > +#if MG_ENABLE_PACKED_FS
> >      > +#else
> >      > +const char *mg_unpack(const char *path, size_t *size, time_t
> >     *mtime) {
> >      > + *size = 0, *mtime = 0;
> >      > + (void) path;
> >      > + return NULL;
> >      > +}
> >      > +const char *mg_unlist(size_t no) {
> >      > + (void) no;
> >      > + return NULL;
> >      > +}
> >      > +#endif
> >      >
> >      > - // The 204 (No content) responses also have 0 body length
> >      > - if (hm->body.len == (size_t) ~0 && is_response &&
> >      > - mg_vcasecmp(&hm->uri, "204") == 0) {
> >      > - hm->body.len = 0;
> >      > - hm->message.len = (size_t) req_len;
> >      > - }
> >      > +struct mg_str mg_unpacked(const char *path) {
> >      > + size_t len = 0;
> >      > + const char *buf = mg_unpack(path, &len, NULL);
> >      > + return mg_str_n(buf, len);
> >      > +}
> >      >
> >      > - return req_len;
> >      > +static int is_dir_prefix(const char *prefix, size_t n, const
> >     char *path) {
> >      > + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
> >      > + return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
> >      > + (n == 0 || path[n] == '/' || path[n - 1] == '/');
> >      > }
> >      >
> >      > -static void mg_http_vprintf_chunk(struct mg_connection *c, const
> >     char *fmt,
> >      > - va_list *ap) {
> >      > - size_t len = c->send.len;
> >      > - mg_send(c, " \r\n", 10);
> >      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> >      > - if (c->send.len >= len + 10) {
> >      > - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
> >     - len - 10);
> >      > - c->send.buf[len + 8] = '\r';
> >      > - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
> >     reset marker
> >      > +static int packed_stat(const char *path, size_t *size, time_t
> >     *mtime) {
> >      > + const char *p;
> >      > + size_t i, n = strlen(path);
> >      > + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
> >     file
> >      > + // Scan all files. If `path` is a dir prefix for any of them,
> >     it's a dir
> >      > + for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
> >      > + if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
> >      > }
> >      > - mg_send(c, "\r\n", 2);
> >      > + return 0;
> >      > }
> >      >
> >      > -void mg_http_printf_chunk(struct mg_connection *c, const char
> >     *fmt, ...) {
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - mg_http_vprintf_chunk(c, fmt, &ap);
> >      > - va_end(ap);
> >      > +static void packed_list(const char *dir, void (*fn)(const char
> >     *, void *),
> >      > + void *userdata) {
> >      > + char buf[MG_PATH_MAX], tmp[sizeof(buf)];
> >      > + const char *path, *begin, *end;
> >      > + size_t i, n = strlen(dir);
> >      > + tmp[0] = '\0'; // Previously listed entry
> >      > + for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
> >      > + if (!is_dir_prefix(dir, n, path)) continue;
> >      > + begin = &path[n + 1];
> >      > + end = strchr(begin, '/');
> >      > + if (end == NULL) end = begin + strlen(begin);
> >      > + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
> >      > + buf[sizeof(buf) - 1] = '\0';
> >      > + // If this entry has been already listed, skip
> >      > + // NOTE: we're assuming that file list is sorted alphabetically
> >      > + if (strcmp(buf, tmp) == 0) continue;
> >      > + fn(buf, userdata); // Not yet listed, call user function
> >      > + strcpy(tmp, buf); // And save this entry as listed
> >      > + }
> >      > }
> >      >
> >      > -void mg_http_write_chunk(struct mg_connection *c, const char
> >     *buf, size_t len) {
> >      > - mg_printf(c, "%lx\r\n", (unsigned long) len);
> >      > - mg_send(c, buf, len);
> >      > - mg_send(c, "\r\n", 2);
> >      > - if (len == 0) c->is_resp = 0;
> >      > +static void *packed_open(const char *path, int flags) {
> >      > + size_t size = 0;
> >      > + const char *data = mg_unpack(path, &size, NULL);
> >      > + struct packed_file *fp = NULL;
> >      > + if (data == NULL) return NULL;
> >      > + if (flags & MG_FS_WRITE) return NULL;
> >      > + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) !=
> >     NULL) {
> >      > + fp->size = size;
> >      > + fp->data = data;
> >      > + }
> >      > + return (void *) fp;
> >      > }
> >      >
> >      > -// clang-format off
> >      > -static const char *mg_http_status_code_str(int status_code) {
> >      > - switch (status_code) {
> >      > - case 100: return "Continue";
> >      > - case 201: return "Created";
> >      > - case 202: return "Accepted";
> >      > - case 204: return "No Content";
> >      > - case 206: return "Partial Content";
> >      > - case 301: return "Moved Permanently";
> >      > - case 302: return "Found";
> >      > - case 304: return "Not Modified";
> >      > - case 400: return "Bad Request";
> >      > - case 401: return "Unauthorized";
> >      > - case 403: return "Forbidden";
> >      > - case 404: return "Not Found";
> >      > - case 418: return "I'm a teapot";
> >      > - case 500: return "Internal Server Error";
> >      > - case 501: return "Not Implemented";
> >      > - default: return "OK";
> >      > - }
> >      > +static void packed_close(void *fp) {
> >      > + if (fp != NULL) free(fp);
> >      > }
> >      > -// clang-format on
> >      >
> >      > -void mg_http_reply(struct mg_connection *c, int code, const char
> >     *headers,
> >      > - const char *fmt, ...) {
> >      > - va_list ap;
> >      > - size_t len;
> >      > - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
> >      > - mg_http_status_code_str(code), headers == NULL ? "" : headers);
> >      > - len = c->send.len;
> >      > - va_start(ap, fmt);
> >      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> >      > - va_end(ap);
> >      > - if (c->send.len > 15) {
> >      > - mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
> >      > - (unsigned long) (c->send.len - len));
> >      > - c->is_resp = 0;
> >      > - c->send.buf[len - 4] = '\r'; // Change ending 0 to space
> >      > - }
> >      > - c->is_resp = 0;
> >      > +static size_t packed_read(void *fd, void *buf, size_t len) {
> >      > + struct packed_file *fp = (struct packed_file *) fd;
> >      > + if (fp->pos + len > fp->size) len = fp->size - fp->pos;
> >      > + memcpy(buf, &fp->data[fp->pos], len);
> >      > + fp->pos += len;
> >      > + return len;
> >      > }
> >      >
> >      > -static void http_cb(struct mg_connection *, int, void *, void *);
> >      > -static void restore_http_cb(struct mg_connection *c) {
> >      > - mg_fs_close((struct mg_fd *) c->pfn_data);
> >      > - c->pfn_data = NULL;
> >      > - c->pfn = http_cb;
> >      > - c->is_resp = 0;
> >      > +static size_t packed_write(void *fd, const void *buf, size_t len) {
> >      > + (void) fd, (void) buf, (void) len;
> >      > + return 0;
> >      > }
> >      >
> >      > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
> >     mtime);
> >      > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
> >     mtime) {
> >      > - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
> >     (int64_t) size);
> >      > - return buf;
> >      > +static size_t packed_seek(void *fd, size_t offset) {
> >      > + struct packed_file *fp = (struct packed_file *) fd;
> >      > + fp->pos = offset;
> >      > + if (fp->pos > fp->size) fp->pos = fp->size;
> >      > + return fp->pos;
> >      > }
> >      >
> >      > -static void static_cb(struct mg_connection *c, int ev, void
> >     *ev_data,
> >      > - void *fn_data) {
> >      > - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> >      > - struct mg_fd *fd = (struct mg_fd *) fn_data;
> >      > - // Read to send IO buffer directly, avoid extra on-stack buffer
> >      > - size_t n, max = MG_IO_SIZE, space;
> >      > - size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
> >     sizeof(size_t)) /
> >      > - sizeof(size_t) * sizeof(size_t)];
> >      > - if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> >      > - if (c->send.len >= c->send.size) return; // Rate limit
> >      > - if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> >      > - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> >      > - c->send.len += n;
> >      > - *cl -= n;
> >      > - if (n == 0) restore_http_cb(c);
> >      > - } else if (ev == MG_EV_CLOSE) {
> >      > - restore_http_cb(c);
> >      > - }
> >      > - (void) ev_data;
> >      > +static bool packed_rename(const char *from, const char *to) {
> >      > + (void) from, (void) to;
> >      > + return false;
> >      > }
> >      >
> >      > -// Known mime types. Keep it outside guess_content_type()
> >     function, since
> >      > -// some environments don't like it defined there.
> >      > -// clang-format off
> >      > -static struct mg_str s_known_types[] = {
> >      > - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> >      > - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> >      > - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> >      > - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> >      > - MG_C_STR("gif"), MG_C_STR("image/gif"),
> >      > - MG_C_STR("png"), MG_C_STR("image/png"),
> >      > - MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> >      > - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> >      > - MG_C_STR("woff"), MG_C_STR("font/woff"),
> >      > - MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> >      > - MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> >      > - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> >      > - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> >      > - MG_C_STR("csv"), MG_C_STR("text/csv"),
> >      > - MG_C_STR("doc"), MG_C_STR("application/msword"),
> >      > - MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> >      > - MG_C_STR("gz"), MG_C_STR("application/gzip"),
> >      > - MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> >      > - MG_C_STR("json"), MG_C_STR("application/json"),
> >      > - MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> >      > - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> >      > - MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> >      > - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> >      > - MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> >      > - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> >      > - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> >      > - MG_C_STR("wav"), MG_C_STR("audio/wav"),
> >      > - MG_C_STR("webp"), MG_C_STR("image/webp"),
> >      > - MG_C_STR("zip"), MG_C_STR("application/zip"),
> >      > - MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> >      > - {0, 0},
> >      > -};
> >      > -// clang-format on
> >      > +static bool packed_remove(const char *path) {
> >      > + (void) path;
> >      > + return false;
> >      > +}
> >      >
> >      > -static struct mg_str guess_content_type(struct mg_str path,
> >     const char *extra) {
> >      > - struct mg_str k, v, s = mg_str(extra);
> >      > - size_t i = 0;
> >      > +static bool packed_mkdir(const char *path) {
> >      > + (void) path;
> >      > + return false;
> >      > +}
> >      >
> >      > - // Shrink path to its extension only
> >      > - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
> >      > - path.ptr += path.len - i;
> >      > - path.len = i;
> >      > +struct mg_fs mg_fs_packed = {
> >      > + packed_stat, packed_list, packed_open, packed_close, packed_read,
> >      > + packed_write, packed_seek, packed_rename, packed_remove,
> >     packed_mkdir};
> >      >
> >      > - // Process user-provided mime type overrides, if any
> >      > - while (mg_commalist(&s, &k, &v)) {
> >      > - if (mg_strcmp(path, k) == 0) return v;
> >      > - }
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/fs_posix.c"
> >      > +#endif
> >      >
> >      > - // Process built-in mime types
> >      > - for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
> >      > - if (mg_strcmp(path, s_known_types[i]) == 0) return
> >     s_known_types[i + 1];
> >      > - }
> >      >
> >      > - return mg_str("text/plain; charset=utf-8");
> >      > -}
> >      > +#if MG_ENABLE_POSIX_FS
> >      >
> >      > -static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
> >      > - size_t i, numparsed = 0;
> >      > - // MG_INFO(("%.*s", (int) s->len, s->ptr));
> >      > - for (i = 0; i + 6 < s->len; i++) {
> >      > - if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
> >      > - struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
> >      > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> >      > - *a = mg_to64(p);
> >      > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> >      > - while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++,
> >     p.len--;
> >      > - if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
> >      > - *b = mg_to64(p);
> >      > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
> >      > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
> >      > - break;
> >      > - }
> >      > - }
> >      > - return (int) numparsed;
> >      > -}
> >      > +#ifndef MG_STAT_STRUCT
> >      > +#define MG_STAT_STRUCT stat
> >      > +#endif
> >      >
> >      > -void mg_http_serve_file(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - const char *path,
> >      > - const struct mg_http_serve_opts *opts) {
> >      > - char etag[64], tmp[MG_PATH_MAX];
> >      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > - struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path,
> >     MG_FS_READ);
> >      > - size_t size = 0;
> >      > - time_t mtime = 0;
> >      > - struct mg_str *inm = NULL;
> >      > - struct mg_str mime = guess_content_type(mg_str(path),
> >     opts->mime_types);
> >      > - bool gzip = false;
> >      > +#ifndef MG_STAT_FUNC
> >      > +#define MG_STAT_FUNC stat
> >      > +#endif
> >      >
> >      > - // If file does not exist, we try to open file PATH.gz - and if
> >     such
> >      > - // pre-compressed .gz file exists, serve it with the
> >     Content-Encoding: gzip
> >      > - // Note - we ignore Accept-Encoding, cause we don't have a choice
> >      > - if (fd == NULL) {
> >      > - MG_DEBUG(("NULL [%s]", path));
> >      > - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> >      > - if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
> >      > - gzip = true;
> >      > - path = tmp;
> >      > - } else if (opts->page404 != NULL) {
> >      > - // No precompressed file, serve 404
> >      > - fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> >      > - mime = guess_content_type(mg_str(path), opts->mime_types);
> >      > - path = opts->page404;
> >      > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> >      > +#if !defined(S_ISDIR)
> >      > + MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
> >     mtime));
> >      > + return 0;
> >      > +#else
> >      > +#if MG_ARCH == MG_ARCH_WIN32
> >      > + struct _stati64 st;
> >      > + wchar_t tmp[MG_PATH_MAX];
> >      > + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
> >     sizeof(tmp[0]));
> >      > + if (_wstati64(tmp, &st) != 0) return 0;
> >      > + // If path is a symlink, windows reports 0 in st.st_size.
> >      > + // Get a real file size by opening it and jumping to the end
> >      > + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
> >      > + FILE *fp = _wfopen(tmp, L"rb");
> >      > + if (fp != NULL) {
> >      > + fseek(fp, 0, SEEK_END);
> >      > + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on
> >     win10+
> >      > + fclose(fp);
> >      > }
> >      > }
> >      > +#else
> >      > + struct MG_STAT_STRUCT st;
> >      > + if (MG_STAT_FUNC(path, &st) != 0) return 0;
> >      > +#endif
> >      > + if (size) *size = (size_t) st.st_size;
> >      > + if (mtime) *mtime = st.st_mtime;
> >      > + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
> >     MG_FS_DIR : 0);
> >      > +#endif
> >      > +}
> >      >
> >      > - if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> >      > - mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> >      > - mg_fs_close(fd);
> >      > - // NOTE: mg_http_etag() call should go first!
> >      > - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
> >     NULL &&
> >      > - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> >      > - mg_vcasecmp(inm, etag) == 0) {
> >      > - mg_fs_close(fd);
> >      > - mg_http_reply(c, 304, opts->extra_headers, "");
> >      > - } else {
> >      > - int n, status = 200;
> >      > - char range[100];
> >      > - int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
> >      > +#if MG_ARCH == MG_ARCH_WIN32
> >      > +struct dirent {
> >      > + char d_name[MAX_PATH];
> >      > +};
> >      >
> >      > - // Handle Range header
> >      > - struct mg_str *rh = mg_http_get_header(hm, "Range");
> >      > - range[0] = '\0';
> >      > - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0
> >     && r2 >= 0) {
> >      > - // If range is specified like "400-", set second limit to
> >     content len
> >      > - if (n == 1) r2 = cl - 1;
> >      > - if (r1 > r2 || r2 >= cl) {
> >      > - status = 416;
> >      > - cl = 0;
> >      > - mg_snprintf(range, sizeof(range), "Content-Range: bytes
> >     */%lld\r\n",
> >      > - (int64_t) size);
> >      > - } else {
> >      > - status = 206;
> >      > - cl = r2 - r1 + 1;
> >      > - mg_snprintf(range, sizeof(range),
> >      > - "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
> >      > - (int64_t) size);
> >      > - fs->sk(fd->fd, (size_t) r1);
> >      > - }
> >      > - }
> >      > - mg_printf(c,
> >      > - "HTTP/1.1 %d %s\r\n"
> >      > - "Content-Type: %.*s\r\n"
> >      > - "Etag: %s\r\n"
> >      > - "Content-Length: %llu\r\n"
> >      > - "%s%s%s\r\n",
> >      > - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
> >      > - etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
> >      > - opts->extra_headers ? opts->extra_headers : "");
> >      > - if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
> >      > - c->is_draining = 1;
> >      > - c->is_resp = 0;
> >      > - mg_fs_close(fd);
> >      > - } else {
> >      > - // Track to-be-sent content length at the end of c->data, aligned
> >      > - size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
> >     sizeof(size_t)) /
> >      > - sizeof(size_t) * sizeof(size_t)];
> >      > - c->pfn = static_cb;
> >      > - c->pfn_data = fd;
> >      > - *clp = (size_t) cl;
> >      > - }
> >      > +typedef struct win32_dir {
> >      > + HANDLE handle;
> >      > + WIN32_FIND_DATAW info;
> >      > + struct dirent result;
> >      > +} DIR;
> >      > +
> >      > +#if 0
> >      > +int gettimeofday(struct timeval *tv, void *tz) {
> >      > + FILETIME ft;
> >      > + unsigned __int64 tmpres = 0;
> >      > +
> >      > + if (tv != NULL) {
> >      > + GetSystemTimeAsFileTime(&ft);
> >      > + tmpres |= ft.dwHighDateTime;
> >      > + tmpres <<= 32;
> >      > + tmpres |= ft.dwLowDateTime;
> >      > + tmpres /= 10; // convert into microseconds
> >      > + tmpres -= (int64_t) 11644473600000000;
> >      > + tv->tv_sec = (long) (tmpres / 1000000UL);
> >      > + tv->tv_usec = (long) (tmpres % 1000000UL);
> >      > }
> >      > + (void) tz;
> >      > + return 0;
> >      > }
> >      > +#endif
> >      >
> >      > -struct printdirentrydata {
> >      > - struct mg_connection *c;
> >      > - struct mg_http_message *hm;
> >      > - const struct mg_http_serve_opts *opts;
> >      > - const char *dir;
> >      > -};
> >      > +static int to_wchar(const char *path, wchar_t *wbuf, size_t
> >     wbuf_len) {
> >      > + int ret;
> >      > + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
> >      > + strncpy(buf, path, sizeof(buf));
> >      > + buf[sizeof(buf) - 1] = '\0';
> >      > + // Trim trailing slashes. Leave backslash for paths like "X:\"
> >      > + p = buf + strlen(buf) - 1;
> >      > + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
> >     '/')) *p-- = '\0';
> >      > + memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
> >      > + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
> >     wbuf_len);
> >      > + // Convert back to Unicode. If doubly-converted string does not
> >     match the
> >      > + // original, something is fishy, reject.
> >      > + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
> >     sizeof(buf2),
> >      > + NULL, NULL);
> >      > + if (strcmp(buf, buf2) != 0) {
> >      > + wbuf[0] = L'\0';
> >      > + ret = 0;
> >      > + }
> >      > + return ret;
> >      > +}
> >      >
> >      > -static void printdirentry(const char *name, void *userdata) {
> >      > - struct printdirentrydata *d = (struct printdirentrydata *)
> >     userdata;
> >      > - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
> >     d->opts->fs;
> >      > - size_t size = 0;
> >      > - time_t t = 0;
> >      > - char path[MG_PATH_MAX], sz[40], mod[40];
> >      > - int flags, n = 0;
> >      > +DIR *opendir(const char *name) {
> >      > + DIR *d = NULL;
> >      > + wchar_t wpath[MAX_PATH];
> >      > + DWORD attrs;
> >      >
> >      > - // MG_DEBUG(("[%s] [%s]", d->dir, name));
> >      > - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> >      > - sizeof(path)) {
> >      > - MG_ERROR(("%s truncated", name));
> >      > - } else if ((flags = fs->st(path, &size, &t)) == 0) {
> >      > - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> >      > + if (name == NULL) {
> >      > + SetLastError(ERROR_BAD_ARGUMENTS);
> >      > + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
> >      > + SetLastError(ERROR_NOT_ENOUGH_MEMORY);
> >      > } else {
> >      > - const char *slash = flags & MG_FS_DIR ? "/" : "";
> >      > - if (flags & MG_FS_DIR) {
> >      > - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> >      > + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
> >      > + attrs = GetFileAttributesW(wpath);
> >      > + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
> >      > + (void) wcscat(wpath, L"\\*");
> >      > + d->handle = FindFirstFileW(wpath, &d->info);
> >      > + d->result.d_name[0] = '\0';
> >      > } else {
> >      > - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> >      > + free(d);
> >      > + d = NULL;
> >      > }
> >      > -#if defined(MG_HTTP_DIRLIST_TIME)
> >      > - char time_str[30];
> >      > - struct tm *time_info = localtime(&t);
> >      > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
> >     time_info);
> >      > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
> >      > -#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
> >      > - char time_str[30];
> >      > - struct tm *time_info = gmtime(&t);
> >      > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
> >     time_info);
> >      > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
> >      > -#else
> >      > - mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
> >      > -#endif
> >      > - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> >      > - mg_printf(d->c,
> >      > - " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> >      > - "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> >      > - n, path, slash, name, slash, (unsigned long) t, mod,
> >      > - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
> >      > }
> >      > + return d;
> >      > }
> >      >
> >      > -static void listdir(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - const struct mg_http_serve_opts *opts, char *dir) {
> >      > - const char *sort_js_code =
> >      > - "<script>function srt(tb, sc, so, d) {"
> >      > - "var tr = Array.prototype.slice.call(tb.rows, 0),"
> >      > - "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
> >     b.cells[sc],"
> >      > - "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> >      > - "t1 = a.cells[2].getAttribute('name'), "
> >      > - "t2 = b.cells[2].getAttribute('name'); "
> >      > - "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> >      > - "n1 ? parseInt(n2) - parseInt(n1) : "
> >      > - "c1.textContent.trim().localeCompare(c2.textContent.trim()));
> >     });";
> >      > - const char *sort_js_code2 =
> >      > - "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> >      > - "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> >      > - "};"
> >      > - "window.onload = function() {"
> >      > - "var tb = document.getElementById('tb');"
> >      > - "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
> >     [0, 2, 1];"
> >      > - "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> >      > - "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
> >     c, so); "
> >      > - "sc = c; ev.preventDefault();}};"
> >      > - "srt(tb, sc, so, true);"
> >      > - "}"
> >      > - "</script>";
> >      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > - struct printdirentrydata d = {c, hm, opts, dir};
> >      > - char tmp[10], buf[MG_PATH_MAX];
> >      > - size_t off, n;
> >      > - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf,
> >     sizeof(buf), 0);
> >      > - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
> >     hm->uri;
> >      > -
> >      > - mg_printf(c,
> >      > - "HTTP/1.1 200 OK\r\n"
> >      > - "Content-Type: text/html; charset=utf-8\r\n"
> >      > - "%s"
> >      > - "Content-Length: \r\n\r\n",
> >      > - opts->extra_headers == NULL ? "" : opts->extra_headers);
> >      > - off = c->send.len; // Start of body
> >      > - mg_printf(c,
> >      > - "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> >      > - "<style>th,td {text-align: left; padding-right: 1em; "
> >      > - "font-family: monospace; }</style></head>"
> >      > - "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> >      > - "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> >      > - "<a href=\"#\" rel=\"1\">Modified</a></th>"
> >      > - "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> >      > - "<tr><td colspan=\"3\"><hr></td></tr>"
> >      > - "</thead>"
> >      > - "<tbody id=\"tb\">\n",
> >      > - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int)
> >     uri.len,
> >      > - uri.ptr);
> >      > - mg_printf(c, "%s",
> >      > - " <tr><td><a href=\"..\">..</a></td>"
> >      > - "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> >      > -
> >      > - fs->ls(dir, printdirentry, &d);
> >      > - mg_printf(c,
> >      > - "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> >      > - "</table><address>Mongoose v.%s</address></body></html>\n",
> >      > - MG_VERSION);
> >      > - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
> >     (c->send.len - off));
> >      > - if (n > sizeof(tmp)) n = 0;
> >      > - memcpy(c->send.buf + off - 12, tmp, n); // Set content length
> >      > - c->is_resp = 0; // Mark response end
> >      > +int closedir(DIR *d) {
> >      > + int result = 0;
> >      > + if (d != NULL) {
> >      > + if (d->handle != INVALID_HANDLE_VALUE)
> >      > + result = FindClose(d->handle) ? 0 : -1;
> >      > + free(d);
> >      > + } else {
> >      > + result = -1;
> >      > + SetLastError(ERROR_BAD_ARGUMENTS);
> >      > + }
> >      > + return result;
> >      > }
> >      >
> >      > -// Resolve requested file into `path` and return its fs->st()
> >     result
> >      > -static int uri_to_path2(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> >      > - char *path, size_t path_size) {
> >      > - int flags, tmp;
> >      > - // Append URI to the root_dir, and sanitize it
> >      > - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
> >     dir.ptr);
> >      > - if (n > path_size) n = path_size;
> >      > - path[path_size - 1] = '\0';
> >      > - if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
> >      > - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len,
> >     path + n,
> >      > - path_size - n, 0);
> >      > - path[path_size - 1] = '\0'; // Double-check
> >      > - mg_remove_double_dots(path);
> >      > - n = strlen(path);
> >      > - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
> >     trailing slashes
> >      > - flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path,
> >     NULL, NULL);
> >      > - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
> >     hm->uri.ptr, path,
> >      > - flags));
> >      > - if (flags == 0) {
> >      > - // Do nothing - let's caller decide
> >      > - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> >      > - hm->uri.ptr[hm->uri.len - 1] != '/') {
> >      > - mg_printf(c,
> >      > - "HTTP/1.1 301 Moved\r\n"
> >      > - "Location: %.*s/\r\n"
> >      > - "Content-Length: 0\r\n"
> >      > +struct dirent *readdir(DIR *d) {
> >      > + struct dirent *result = NULL;
> >      > + if (d != NULL) {
> >      > + memset(&d->result, 0, sizeof(d->result));
> >      > + if (d->handle != INVALID_HANDLE_VALUE) {
> >      > + result = &d->result;
> >      > + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
> >     result->d_name,
> >      > + sizeof(result->d_name), NULL, NULL);
> >      > + if (!FindNextFileW(d->handle, &d->info)) {
> >      > + FindClose(d->handle);
> >      > + d->handle = INVALID_HANDLE_VALUE;
> >      > + }
> >      > + } else {
> >      > + SetLastError(ERROR_FILE_NOT_FOUND);
> >      > + }
> >      > + } else {
> >      > + SetLastError(ERROR_BAD_ARGUMENTS);
> >      > + }
> >      > + return result;
> >      > +}
> >      > +#endif
> >      > +
> >      > +static void p_list(const char *dir, void (*fn)(const char *,
> >     void *),
> >      > + void *userdata) {
> >      > +#if MG_ENABLE_DIRLIST
> >      > + struct dirent *dp;
> >      > + DIR *dirp;
> >      > + if ((dirp = (opendir(dir))) == NULL) return;
> >      > + while ((dp = readdir(dirp)) != NULL) {
> >      > + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
> >     continue;
> >      > + fn(dp->d_name, userdata);
> >      > + }
> >      > + closedir(dirp);
> >      > +#else
> >      > + (void) dir, (void) fn, (void) userdata;
> >      > +#endif
> >      > +}
> >      > +
> >      > +static void *p_open(const char *path, int flags) {
> >      > +#if MG_ARCH == MG_ARCH_WIN32
> >      > + const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
> >      > + wchar_t b1[MG_PATH_MAX], b2[10];
> >      > + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
> >     sizeof(b1[0]));
> >      > + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
> >     sizeof(b2[0]));
> >      > + return (void *) _wfopen(b1, b2);
> >      > +#else
> >      > + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e
> >     for CLOEXEC
> >      > + return (void *) fopen(path, mode);
> >      > +#endif
> >      > +}
> >      > +
> >      > +static void p_close(void *fp) {
> >      > + fclose((FILE *) fp);
> >      > +}
> >      > +
> >      > +static size_t p_read(void *fp, void *buf, size_t len) {
> >      > + return fread(buf, 1, len, (FILE *) fp);
> >      > +}
> >      > +
> >      > +static size_t p_write(void *fp, const void *buf, size_t len) {
> >      > + return fwrite(buf, 1, len, (FILE *) fp);
> >      > +}
> >      > +
> >      > +static size_t p_seek(void *fp, size_t offset) {
> >      > +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
> >      > + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
> >      > + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
> >      > + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
> >      > +#else
> >      > + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
> >      > +#endif
> >      > + return (size_t) ftell((FILE *) fp);
> >      > +}
> >      > +
> >      > +static bool p_rename(const char *from, const char *to) {
> >      > + return rename(from, to) == 0;
> >      > +}
> >      > +
> >      > +static bool p_remove(const char *path) {
> >      > + return remove(path) == 0;
> >      > +}
> >      > +
> >      > +static bool p_mkdir(const char *path) {
> >      > + return mkdir(path, 0775) == 0;
> >      > +}
> >      > +
> >      > +#else
> >      > +
> >      > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
> >      > + (void) path, (void) size, (void) mtime;
> >      > + return 0;
> >      > +}
> >      > +static void p_list(const char *path, void (*fn)(const char *,
> >     void *),
> >      > + void *userdata) {
> >      > + (void) path, (void) fn, (void) userdata;
> >      > +}
> >      > +static void *p_open(const char *path, int flags) {
> >      > + (void) path, (void) flags;
> >      > + return NULL;
> >      > +}
> >      > +static void p_close(void *fp) {
> >      > + (void) fp;
> >      > +}
> >      > +static size_t p_read(void *fd, void *buf, size_t len) {
> >      > + (void) fd, (void) buf, (void) len;
> >      > + return 0;
> >      > +}
> >      > +static size_t p_write(void *fd, const void *buf, size_t len) {
> >      > + (void) fd, (void) buf, (void) len;
> >      > + return 0;
> >      > +}
> >      > +static size_t p_seek(void *fd, size_t offset) {
> >      > + (void) fd, (void) offset;
> >      > + return (size_t) ~0;
> >      > +}
> >      > +static bool p_rename(const char *from, const char *to) {
> >      > + (void) from, (void) to;
> >      > + return false;
> >      > +}
> >      > +static bool p_remove(const char *path) {
> >      > + (void) path;
> >      > + return false;
> >      > +}
> >      > +static bool p_mkdir(const char *path) {
> >      > + (void) path;
> >      > + return false;
> >      > +}
> >      > +#endif
> >      > +
> >      > +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
> >     p_read,
> >      > + p_write, p_seek, p_rename, p_remove, p_mkdir};
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/http.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +static int mg_ncasecmp(const char *s1, const char *s2, size_t
> >     len) {
> >      > + int diff = 0;
> >      > + if (len > 0) do {
> >      > + int c = *s1++, d = *s2++;
> >      > + if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> >      > + if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
> >      > + diff = c - d;
> >      > + } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> >      > + return diff;
> >      > +}
> >      > +
> >      > +bool mg_to_size_t(struct mg_str str, size_t *val);
> >      > +bool mg_to_size_t(struct mg_str str, size_t *val) {
> >      > + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0,
> >     ndigits = 0;
> >      > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
> >     '\t')) i++;
> >      > + if (i < str.len && str.buf[i] == '-') return false;
> >      > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> >      > + size_t digit = (size_t) (str.buf[i] - '0');
> >      > + if (result > max2) return false; // Overflow
> >      > + result *= 10;
> >      > + if (result > max - digit) return false; // Overflow
> >      > + result += digit;
> >      > + i++, ndigits++;
> >      > + }
> >      > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
> >     '\t')) i++;
> >      > + if (ndigits == 0) return false; // #2322: Content-Length = 1 *
> >     DIGIT
> >      > + if (i != str.len) return false; // Ditto
> >      > + *val = (size_t) result;
> >      > + return true;
> >      > +}
> >      > +
> >      > +// Chunk deletion marker is the MSB in the "processed" counter
> >      > +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
> >      > +
> >      > +// Multipart POST example:
> >      > +// --xyz
> >      > +// Content-Disposition: form-data; name="val"
> >      > +//
> >      > +// abcdef
> >      > +// --xyz
> >      > +// Content-Disposition: form-data; name="foo"; filename="a.txt"
> >      > +// Content-Type: text/plain
> >      > +//
> >      > +// hello world
> >      > +//
> >      > +// --xyz--
> >      > +size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
> >      > + struct mg_http_part *part) {
> >      > + struct mg_str cd = mg_str_n("Content-Disposition", 19);
> >      > + const char *s = body.buf;
> >      > + size_t b = ofs, h1, h2, b1, b2, max = body.len;
> >      > +
> >      > + // Init part params
> >      > + if (part != NULL) part->name = part->filename = part->body =
> >     mg_str_n(0, 0);
> >      > +
> >      > + // Skip boundary
> >      > + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
> >      > + if (b <= ofs || b + 2 >= max) return 0;
> >      > + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
> >     s));
> >      > +
> >      > + // Skip headers
> >      > + h1 = h2 = b + 2;
> >      > + for (;;) {
> >      > + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
> >      > + if (h2 == h1) break;
> >      > + if (h2 + 2 >= max) return 0;
> >      > + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
> >      > + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
> >     ':' &&
> >      > + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
> >      > + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
> >     cd.len + 2));
> >      > + part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
> >      > + part->filename = mg_http_get_header_var(v, mg_str_n("filename",
> >     8));
> >      > + }
> >      > + h1 = h2 = h2 + 2;
> >      > + }
> >      > + b1 = b2 = h2 + 2;
> >      > + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
> >     + 1] == '\n' &&
> >      > + memcmp(&s[b2 + 2], s, b - ofs) == 0))
> >      > + b2++;
> >      > +
> >      > + if (b2 + 2 >= max) return 0;
> >      > + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
> >      > + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
> >      > + return b2 + 2;
> >      > +}
> >      > +
> >      > +void mg_http_bauth(struct mg_connection *c, const char *user,
> >      > + const char *pass) {
> >      > + struct mg_str u = mg_str(user), p = mg_str(pass);
> >      > + size_t need = c->send.len + 36 + (u.len + p.len) * 2;
> >      > + if (c->send.size < need) mg_iobuf_resize(&c->send, need);
> >      > + if (c->send.size >= need) {
> >      > + size_t i, n = 0;
> >      > + char *buf = (char *) &c->send.buf[c->send.len];
> >      > + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
> >      > + for (i = 0; i < u.len; i++) {
> >      > + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
> >      > + }
> >      > + if (p.len > 0) {
> >      > + n = mg_base64_update(':', buf + 21, n);
> >      > + for (i = 0; i < p.len; i++) {
> >      > + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
> >      > + }
> >      > + }
> >      > + n = mg_base64_final(buf + 21, n);
> >      > + c->send.len += 21 + (size_t) n + 2;
> >      > + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
> >      > + } else {
> >      > + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
> >     need));
> >      > + }
> >      > +}
> >      > +
> >      > +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
> >      > + struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
> >      > + while (mg_span(buf, &entry, &buf, '&')) {
> >      > + if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
> >      > + mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
> >      > + result = v;
> >      > + break;
> >      > + }
> >      > + }
> >      > + return result;
> >      > +}
> >      > +
> >      > +int mg_http_get_var(const struct mg_str *buf, const char *name,
> >     char *dst,
> >      > + size_t dst_len) {
> >      > + int len;
> >      > + if (dst != NULL && dst_len > 0) {
> >      > + dst[0] = '\0'; // If destination buffer is valid, always
> >     nul-terminate it
> >      > + }
> >      > + if (dst == NULL || dst_len == 0) {
> >      > + len = -2; // Bad destination
> >      > + } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
> >      > + len = -1; // Bad source
> >      > + } else {
> >      > + struct mg_str v = mg_http_var(*buf, mg_str(name));
> >      > + if (v.buf == NULL) {
> >      > + len = -4; // Name does not exist
> >      > + } else {
> >      > + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
> >      > + if (len < 0) len = -3; // Failed to decode
> >      > + }
> >      > + }
> >      > + return len;
> >      > +}
> >      > +
> >      > +static bool isx(int c) {
> >      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> >      > + (c >= 'A' && c <= 'F');
> >      > +}
> >      > +
> >      > +int mg_url_decode(const char *src, size_t src_len, char *dst,
> >     size_t dst_len,
> >      > + int is_form_url_encoded) {
> >      > + size_t i, j;
> >      > + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
> >      > + if (src[i] == '%') {
> >      > + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
> >     src_len
> >      > + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
> >      > + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j],
> >     sizeof(uint8_t));
> >      > + i += 2;
> >      > + } else {
> >      > + return -1;
> >      > + }
> >      > + } else if (is_form_url_encoded && src[i] == '+') {
> >      > + dst[j] = ' ';
> >      > + } else {
> >      > + dst[j] = src[i];
> >      > + }
> >      > + }
> >      > + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
> >      > + return i >= src_len && j < dst_len ? (int) j : -1;
> >      > +}
> >      > +
> >      > +static bool isok(uint8_t c) {
> >      > + return c == '\n' || c == '\r' || c >= ' ';
> >      > +}
> >      > +
> >      > +int mg_http_get_request_len(const unsigned char *buf, size_t
> >     buf_len) {
> >      > + size_t i;
> >      > + for (i = 0; i < buf_len; i++) {
> >      > + if (!isok(buf[i])) return -1;
> >      > + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
> >      > + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
> >     '\n'))
> >      > + return (int) i + 1;
> >      > + }
> >      > + return 0;
> >      > +}
> >      > +struct mg_str *mg_http_get_header(struct mg_http_message *h,
> >     const char *name) {
> >      > + size_t i, n = strlen(name), max = sizeof(h->headers) /
> >     sizeof(h->headers[0]);
> >      > + for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
> >      > + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
> >      > + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
> >      > + }
> >      > + return NULL;
> >      > +}
> >      > +
> >      > +// Is it a valid utf-8 continuation byte
> >      > +static bool vcb(uint8_t c) {
> >      > + return (c & 0xc0) == 0x80;
> >      > +}
> >      > +
> >      > +// Get character length (valid utf-8). Used to parse method,
> >     URI, headers
> >      > +static size_t clen(const char *s, const char *end) {
> >      > + const unsigned char *u = (unsigned char *) s, c = *u;
> >      > + long n = (long) (end - s);
> >      > + if (c > ' ' && c < '~') return 1; // Usual ascii printed char
> >      > + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; //
> >     2-byte UTF8
> >      > + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2]))
> >     return 3;
> >      > + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) &&
> >     vcb(u[3]))
> >      > + return 4;
> >      > + return 0;
> >      > +}
> >      > +
> >      > +// Skip until the newline. Return advanced `s`, or NULL on error
> >      > +static const char *skiptorn(const char *s, const char *end,
> >     struct mg_str *v) {
> >      > + v->buf = (char *) s;
> >      > + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;
> >     // To newline
> >      > + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; //
> >     Stray \r
> >      > + if (s < end && s[0] == '\r') s++; // Skip \r
> >      > + if (s >= end || *s++ != '\n') return NULL; // Skip \n
> >      > + return s;
> >      > +}
> >      > +
> >      > +static bool mg_http_parse_headers(const char *s, const char *end,
> >      > + struct mg_http_header *h, size_t max_hdrs) {
> >      > + size_t i, n;
> >      > + for (i = 0; i < max_hdrs; i++) {
> >      > + struct mg_str k = {NULL, 0}, v = {NULL, 0};
> >      > + if (s >= end) return false;
> >      > + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
> >      > + k.buf = (char *) s;
> >      > + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s +=
> >     n, k.len += n;
> >      > + if (k.len == 0) return false; // Empty name
> >      > + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8
> >      > + if (*s++ != ':') return false; // Invalid, not followed by :
> >      > + // if (clen(s, end) == 0) return false; // Invalid UTF-8
> >      > + while (s < end && s[0] == ' ') s++; // Skip spaces
> >      > + if ((s = skiptorn(s, end, &v)) == NULL) return false;
> >      > + while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim
> >     spaces
> >      > + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int)
> >     v.len, v.buf));
> >      > + h[i].name = k, h[i].value = v; // Success. Assign values
> >      > + }
> >      > + return true;
> >      > +}
> >      > +
> >      > +int mg_http_parse(const char *s, size_t len, struct
> >     mg_http_message *hm) {
> >      > + int is_response, req_len = mg_http_get_request_len((unsigned
> >     char *) s, len);
> >      > + const char *end = s == NULL ? NULL : s + req_len, *qs; //
> >     Cannot add to NULL
> >      > + const struct mg_str *cl;
> >      > + size_t n;
> >      > +
> >      > + memset(hm, 0, sizeof(*hm));
> >      > + if (req_len <= 0) return req_len;
> >      > +
> >      > + hm->message.buf = hm->head.buf = (char *) s;
> >      > + hm->body.buf = (char *) end;
> >      > + hm->head.len = (size_t) req_len;
> >      > + hm->message.len = hm->body.len = (size_t) -1; // Set body
> >     length to infinite
> >      > +
> >      > + // Parse request line
> >      > + hm->method.buf = (char *) s;
> >      > + while (s < end && (n = clen(s, end)) > 0) s += n,
> >     hm->method.len += n;
> >      > + while (s < end && s[0] == ' ') s++; // Skip spaces
> >      > + hm->uri.buf = (char *) s;
> >      > + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len
> >     += n;
> >      > + while (s < end && s[0] == ' ') s++; // Skip spaces
> >      > + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
> >      > +
> >      > + // If URI contains '?' character, setup query string
> >      > + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len))
> >     != NULL) {
> >      > + hm->query.buf = (char *) qs + 1;
> >      > + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
> >      > + hm->uri.len = (size_t) (qs - hm->uri.buf);
> >      > + }
> >      > +
> >      > + // Sanity check. Allow protocol/reason to be empty
> >      > + // Do this check after hm->method.len and hm->uri.len are
> >     finalised
> >      > + if (hm->method.len == 0 || hm->uri.len == 0) return -1;
> >      > +
> >      > + if (!mg_http_parse_headers(s, end, hm->headers,
> >      > + sizeof(hm->headers) / sizeof(hm->headers[0])))
> >      > + return -1; // error when parsing
> >      > + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
> >      > + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
> >      > + hm->message.len = (size_t) req_len + hm->body.len;
> >      > + }
> >      > +
> >      > + // mg_http_parse() is used to parse both HTTP requests and HTTP
> >      > + // responses. If HTTP response does not have Content-Length
> >     set, then
> >      > + // body is read until socket is closed, i.e. body.len is
> >     infinite (~0).
> >      > + //
> >      > + // For HTTP requests though, according to
> >      > + // http://tools.ietf.org/html/rfc7231#section-8.1.3
> >     <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
> >      > + // only POST and PUT methods have defined body semantics.
> >      > + // Therefore, if Content-Length is not specified and methods are
> >      > + // not one of PUT or POST, set body length to 0.
> >      > + //
> >      > + // So, if it is HTTP request, and Content-Length is not set,
> >      > + // and method is not (PUT or POST) then reset body length to zero.
> >      > + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
> >      > + if (hm->body.len == (size_t) ~0 && !is_response &&
> >      > + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
> >      > + mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
> >      > + hm->body.len = 0;
> >      > + hm->message.len = (size_t) req_len;
> >      > + }
> >      > +
> >      > + // The 204 (No content) responses also have 0 body length
> >      > + if (hm->body.len == (size_t) ~0 && is_response &&
> >      > + mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
> >      > + hm->body.len = 0;
> >      > + hm->message.len = (size_t) req_len;
> >      > + }
> >      > + if (hm->message.len < (size_t) req_len) return -1; // Overflow
> >     protection
> >      > +
> >      > + return req_len;
> >      > +}
> >      > +
> >      > +static void mg_http_vprintf_chunk(struct mg_connection *c, const
> >     char *fmt,
> >      > + va_list *ap) {
> >      > + size_t len = c->send.len;
> >      > + mg_send(c, " \r\n", 10);
> >      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> >      > + if (c->send.len >= len + 10) {
> >      > + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
> >     - len - 10);
> >      > + c->send.buf[len + 8] = '\r';
> >      > + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
> >     reset marker
> >      > + }
> >      > + mg_send(c, "\r\n", 2);
> >      > +}
> >      > +
> >      > +void mg_http_printf_chunk(struct mg_connection *c, const char
> >     *fmt, ...) {
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + mg_http_vprintf_chunk(c, fmt, &ap);
> >      > + va_end(ap);
> >      > +}
> >      > +
> >      > +void mg_http_write_chunk(struct mg_connection *c, const char
> >     *buf, size_t len) {
> >      > + mg_printf(c, "%lx\r\n", (unsigned long) len);
> >      > + mg_send(c, buf, len);
> >      > + mg_send(c, "\r\n", 2);
> >      > + if (len == 0) c->is_resp = 0;
> >      > +}
> >      > +
> >      > +// clang-format off
> >      > +static const char *mg_http_status_code_str(int status_code) {
> >      > + switch (status_code) {
> >      > + case 100: return "Continue";
> >      > + case 101: return "Switching Protocols";
> >      > + case 102: return "Processing";
> >      > + case 200: return "OK";
> >      > + case 201: return "Created";
> >      > + case 202: return "Accepted";
> >      > + case 203: return "Non-authoritative Information";
> >      > + case 204: return "No Content";
> >      > + case 205: return "Reset Content";
> >      > + case 206: return "Partial Content";
> >      > + case 207: return "Multi-Status";
> >      > + case 208: return "Already Reported";
> >      > + case 226: return "IM Used";
> >      > + case 300: return "Multiple Choices";
> >      > + case 301: return "Moved Permanently";
> >      > + case 302: return "Found";
> >      > + case 303: return "See Other";
> >      > + case 304: return "Not Modified";
> >      > + case 305: return "Use Proxy";
> >      > + case 307: return "Temporary Redirect";
> >      > + case 308: return "Permanent Redirect";
> >      > + case 400: return "Bad Request";
> >      > + case 401: return "Unauthorized";
> >      > + case 402: return "Payment Required";
> >      > + case 403: return "Forbidden";
> >      > + case 404: return "Not Found";
> >      > + case 405: return "Method Not Allowed";
> >      > + case 406: return "Not Acceptable";
> >      > + case 407: return "Proxy Authentication Required";
> >      > + case 408: return "Request Timeout";
> >      > + case 409: return "Conflict";
> >      > + case 410: return "Gone";
> >      > + case 411: return "Length Required";
> >      > + case 412: return "Precondition Failed";
> >      > + case 413: return "Payload Too Large";
> >      > + case 414: return "Request-URI Too Long";
> >      > + case 415: return "Unsupported Media Type";
> >      > + case 416: return "Requested Range Not Satisfiable";
> >      > + case 417: return "Expectation Failed";
> >      > + case 418: return "I'm a teapot";
> >      > + case 421: return "Misdirected Request";
> >      > + case 422: return "Unprocessable Entity";
> >      > + case 423: return "Locked";
> >      > + case 424: return "Failed Dependency";
> >      > + case 426: return "Upgrade Required";
> >      > + case 428: return "Precondition Required";
> >      > + case 429: return "Too Many Requests";
> >      > + case 431: return "Request Header Fields Too Large";
> >      > + case 444: return "Connection Closed Without Response";
> >      > + case 451: return "Unavailable For Legal Reasons";
> >      > + case 499: return "Client Closed Request";
> >      > + case 500: return "Internal Server Error";
> >      > + case 501: return "Not Implemented";
> >      > + case 502: return "Bad Gateway";
> >      > + case 503: return "Service Unavailable";
> >      > + case 504: return "Gateway Timeout";
> >      > + case 505: return "HTTP Version Not Supported";
> >      > + case 506: return "Variant Also Negotiates";
> >      > + case 507: return "Insufficient Storage";
> >      > + case 508: return "Loop Detected";
> >      > + case 510: return "Not Extended";
> >      > + case 511: return "Network Authentication Required";
> >      > + case 599: return "Network Connect Timeout Error";
> >      > + default: return "";
> >      > + }
> >      > +}
> >      > +// clang-format on
> >      > +
> >      > +void mg_http_reply(struct mg_connection *c, int code, const char
> >     *headers,
> >      > + const char *fmt, ...) {
> >      > + va_list ap;
> >      > + size_t len;
> >      > + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
> >      > + mg_http_status_code_str(code), headers == NULL ? "" : headers);
> >      > + len = c->send.len;
> >      > + va_start(ap, fmt);
> >      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
> >      > + va_end(ap);
> >      > + if (c->send.len > 16) {
> >      > + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11,
> >     "%-10lu",
> >      > + (unsigned long) (c->send.len - len));
> >      > + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space
> >      > + }
> >      > + c->is_resp = 0;
> >      > +}
> >      > +
> >      > +static void http_cb(struct mg_connection *, int, void *);
> >      > +static void restore_http_cb(struct mg_connection *c) {
> >      > + mg_fs_close((struct mg_fd *) c->pfn_data);
> >      > + c->pfn_data = NULL;
> >      > + c->pfn = http_cb;
> >      > + c->is_resp = 0;
> >      > +}
> >      > +
> >      > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
> >     mtime);
> >      > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
> >     mtime) {
> >      > + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
> >     (int64_t) size);
> >      > + return buf;
> >      > +}
> >      > +
> >      > +static void static_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
> >      > + struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
> >      > + // Read to send IO buffer directly, avoid extra on-stack buffer
> >      > + size_t n, max = MG_IO_SIZE, space;
> >      > + size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
> >     sizeof(size_t)) /
> >      > + sizeof(size_t) * sizeof(size_t)];
> >      > + if (c->send.size < max) mg_iobuf_resize(&c->send, max);
> >      > + if (c->send.len >= c->send.size) return; // Rate limit
> >      > + if ((space = c->send.size - c->send.len) > *cl) space = *cl;
> >      > + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
> >      > + c->send.len += n;
> >      > + *cl -= n;
> >      > + if (n == 0) restore_http_cb(c);
> >      > + } else if (ev == MG_EV_CLOSE) {
> >      > + restore_http_cb(c);
> >      > + }
> >      > + (void) ev_data;
> >      > +}
> >      > +
> >      > +// Known mime types. Keep it outside guess_content_type()
> >     function, since
> >      > +// some environments don't like it defined there.
> >      > +// clang-format off
> >      > +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
> >      > +static struct mg_str s_known_types[] = {
> >      > + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
> >      > + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
> >      > + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
> >      > + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
> >      > + MG_C_STR("gif"), MG_C_STR("image/gif"),
> >      > + MG_C_STR("png"), MG_C_STR("image/png"),
> >      > + MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
> >      > + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
> >      > + MG_C_STR("woff"), MG_C_STR("font/woff"),
> >      > + MG_C_STR("ttf"), MG_C_STR("font/ttf"),
> >      > + MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
> >      > + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
> >      > + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
> >      > + MG_C_STR("csv"), MG_C_STR("text/csv"),
> >      > + MG_C_STR("doc"), MG_C_STR("application/msword"),
> >      > + MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
> >      > + MG_C_STR("gz"), MG_C_STR("application/gzip"),
> >      > + MG_C_STR("ico"), MG_C_STR("image/x-icon"),
> >      > + MG_C_STR("json"), MG_C_STR("application/json"),
> >      > + MG_C_STR("mov"), MG_C_STR("video/quicktime"),
> >      > + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
> >      > + MG_C_STR("mp4"), MG_C_STR("video/mp4"),
> >      > + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
> >      > + MG_C_STR("pdf"), MG_C_STR("application/pdf"),
> >      > + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
> >      > + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
> >      > + MG_C_STR("wav"), MG_C_STR("audio/wav"),
> >      > + MG_C_STR("webp"), MG_C_STR("image/webp"),
> >      > + MG_C_STR("zip"), MG_C_STR("application/zip"),
> >      > + MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
> >      > + {0, 0},
> >      > +};
> >      > +// clang-format on
> >      > +
> >      > +static struct mg_str guess_content_type(struct mg_str path,
> >     const char *extra) {
> >      > + struct mg_str entry, k, v, s = mg_str(extra);
> >      > + size_t i = 0;
> >      > +
> >      > + // Shrink path to its extension only
> >      > + while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
> >      > + path.buf += path.len - i;
> >      > + path.len = i;
> >      > +
> >      > + // Process user-provided mime type overrides, if any
> >      > + while (mg_span(s, &entry, &s, ',')) {
> >      > + if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0)
> >     return v;
> >      > + }
> >      > +
> >      > + // Process built-in mime types
> >      > + for (i = 0; s_known_types[i].buf != NULL; i += 2) {
> >      > + if (mg_strcmp(path, s_known_types[i]) == 0) return
> >     s_known_types[i + 1];
> >      > + }
> >      > +
> >      > + return mg_str("text/plain; charset=utf-8");
> >      > +}
> >      > +
> >      > +static int getrange(struct mg_str *s, size_t *a, size_t *b) {
> >      > + size_t i, numparsed = 0;
> >      > + for (i = 0; i + 6 < s->len; i++) {
> >      > + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
> >      > + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
> >      > + if (mg_span(v, &k, &v, '-')) {
> >      > + if (mg_to_size_t(k, a)) numparsed++;
> >      > + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
> >      > + } else {
> >      > + if (mg_to_size_t(v, a)) numparsed++;
> >      > + }
> >      > + break;
> >      > + }
> >      > + return (int) numparsed;
> >      > +}
> >      > +
> >      > +void mg_http_serve_file(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + const char *path,
> >      > + const struct mg_http_serve_opts *opts) {
> >      > + char etag[64], tmp[MG_PATH_MAX];
> >      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > + struct mg_fd *fd = NULL;
> >      > + size_t size = 0;
> >      > + time_t mtime = 0;
> >      > + struct mg_str *inm = NULL;
> >      > + struct mg_str mime = guess_content_type(mg_str(path),
> >     opts->mime_types);
> >      > + bool gzip = false;
> >      > +
> >      > + if (path != NULL) {
> >      > + // If a browser sends us "Accept-Encoding: gzip", try to open
> >     .gz first
> >      > + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
> >      > + if (ae != NULL) {
> >      > + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
> >      > + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
> >      > + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
> >      > + fd = mg_fs_open(fs, tmp, MG_FS_READ);
> >      > + if (fd != NULL) gzip = true, path = tmp;
> >      > + }
> >      > + free(ae_);
> >      > + }
> >      > + // No luck opening .gz? Open what we've told to open
> >      > + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
> >      > + }
> >      > +
> >      > + // Failed to open, and page404 is configured? Open it, then
> >      > + if (fd == NULL && opts->page404 != NULL) {
> >      > + fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
> >      > + path = opts->page404;
> >      > + mime = guess_content_type(mg_str(path), opts->mime_types);
> >      > + }
> >      > +
> >      > + if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
> >      > + mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
> >      > + mg_fs_close(fd);
> >      > + // NOTE: mg_http_etag() call should go first!
> >      > + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
> >     NULL &&
> >      > + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
> >      > + mg_strcasecmp(*inm, mg_str(etag)) == 0) {
> >      > + mg_fs_close(fd);
> >      > + mg_http_reply(c, 304, opts->extra_headers, "");
> >      > + } else {
> >      > + int n, status = 200;
> >      > + char range[100];
> >      > + size_t r1 = 0, r2 = 0, cl = size;
> >      > +
> >      > + // Handle Range header
> >      > + struct mg_str *rh = mg_http_get_header(hm, "Range");
> >      > + range[0] = '\0';
> >      > + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
> >      > + // If range is specified like "400-", set second limit to
> >     content len
> >      > + if (n == 1) r2 = cl - 1;
> >      > + if (r1 > r2 || r2 >= cl) {
> >      > + status = 416;
> >      > + cl = 0;
> >      > + mg_snprintf(range, sizeof(range), "Content-Range: bytes
> >     */%lld\r\n",
> >      > + (int64_t) size);
> >      > + } else {
> >      > + status = 206;
> >      > + cl = r2 - r1 + 1;
> >      > + mg_snprintf(range, sizeof(range),
> >      > + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
> >      > + (uint64_t) (r1 + cl - 1), (uint64_t) size);
> >      > + fs->sk(fd->fd, r1);
> >      > + }
> >      > + }
> >      > + mg_printf(c,
> >      > + "HTTP/1.1 %d %s\r\n"
> >      > + "Content-Type: %.*s\r\n"
> >      > + "Etag: %s\r\n"
> >      > + "Content-Length: %llu\r\n"
> >      > + "%s%s%s\r\n",
> >      > + status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
> >      > + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
> >      > + range, opts->extra_headers ? opts->extra_headers : "");
> >      > + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
> >      > + c->is_draining = 1;
> >      > + c->is_resp = 0;
> >      > + mg_fs_close(fd);
> >      > + } else {
> >      > + // Track to-be-sent content length at the end of c->data, aligned
> >      > + size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
> >     sizeof(size_t)) /
> >      > + sizeof(size_t) * sizeof(size_t)];
> >      > + c->pfn = static_cb;
> >      > + c->pfn_data = fd;
> >      > + *clp = cl;
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +struct printdirentrydata {
> >      > + struct mg_connection *c;
> >      > + struct mg_http_message *hm;
> >      > + const struct mg_http_serve_opts *opts;
> >      > + const char *dir;
> >      > +};
> >      > +
> >      > +#if MG_ENABLE_DIRLIST
> >      > +static void printdirentry(const char *name, void *userdata) {
> >      > + struct printdirentrydata *d = (struct printdirentrydata *)
> >     userdata;
> >      > + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
> >     d->opts->fs;
> >      > + size_t size = 0;
> >      > + time_t t = 0;
> >      > + char path[MG_PATH_MAX], sz[40], mod[40];
> >      > + int flags, n = 0;
> >      > +
> >      > + // MG_DEBUG(("[%s] [%s]", d->dir, name));
> >      > + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
> >      > + sizeof(path)) {
> >      > + MG_ERROR(("%s truncated", name));
> >      > + } else if ((flags = fs->st(path, &size, &t)) == 0) {
> >      > + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
> >      > + } else {
> >      > + const char *slash = flags & MG_FS_DIR ? "/" : "";
> >      > + if (flags & MG_FS_DIR) {
> >      > + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
> >      > + } else {
> >      > + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
> >      > + }
> >      > +#if defined(MG_HTTP_DIRLIST_TIME_FMT)
> >      > + {
> >      > + char time_str[40];
> >      > + struct tm *time_info = localtime(&t);
> >      > + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
> >     time_info);
> >      > + mg_snprintf(mod, sizeof(mod), "%s", time_str);
> >      > + }
> >      > +#else
> >      > + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
> >      > +#endif
> >      > + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
> >      > + mg_printf(d->c,
> >      > + " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
> >      > + "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
> >      > + n, path, slash, name, slash, (unsigned long) t, mod,
> >      > + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
> >      > + }
> >      > +}
> >      > +
> >      > +static void listdir(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + const struct mg_http_serve_opts *opts, char *dir) {
> >      > + const char *sort_js_code =
> >      > + "<script>function srt(tb, sc, so, d) {"
> >      > + "var tr = Array.prototype.slice.call(tb.rows, 0),"
> >      > + "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
> >     b.cells[sc],"
> >      > + "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
> >      > + "t1 = a.cells[2].getAttribute('name'), "
> >      > + "t2 = b.cells[2].getAttribute('name'); "
> >      > + "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
> >      > + "n1 ? parseInt(n2) - parseInt(n1) : "
> >      > + "c1.textContent.trim().localeCompare(c2.textContent.trim()));
> >     });";
> >      > + const char *sort_js_code2 =
> >      > + "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
> >      > + "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
> >      > + "};"
> >      > + "window.onload = function() {"
> >      > + "var tb = document.getElementById('tb');"
> >      > + "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
> >     [0, 2, 1];"
> >      > + "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
> >      > + "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
> >     c, so); "
> >      > + "sc = c; ev.preventDefault();}};"
> >      > + "srt(tb, sc, so, true);"
> >      > + "}"
> >      > + "</script>";
> >      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > + struct printdirentrydata d = {c, hm, opts, dir};
> >      > + char tmp[10], buf[MG_PATH_MAX];
> >      > + size_t off, n;
> >      > + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf,
> >     sizeof(buf), 0);
> >      > + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
> >     hm->uri;
> >      > +
> >      > + mg_printf(c,
> >      > + "HTTP/1.1 200 OK\r\n"
> >      > + "Content-Type: text/html; charset=utf-8\r\n"
> >      > + "%s"
> >      > + "Content-Length: \r\n\r\n",
> >      > + opts->extra_headers == NULL ? "" : opts->extra_headers);
> >      > + off = c->send.len; // Start of body
> >      > + mg_printf(c,
> >      > + "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
> >      > + "<style>th,td {text-align: left; padding-right: 1em; "
> >      > + "font-family: monospace; }</style></head>"
> >      > + "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
> >      > + "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
> >      > + "<a href=\"#\" rel=\"1\">Modified</a></th>"
> >      > + "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
> >      > + "<tr><td colspan=\"3\"><hr></td></tr>"
> >      > + "</thead>"
> >      > + "<tbody id=\"tb\">\n",
> >      > + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int)
> >     uri.len,
> >      > + uri.buf);
> >      > + mg_printf(c, "%s",
> >      > + " <tr><td><a href=\"..\">..</a></td>"
> >      > + "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
> >      > +
> >      > + fs->ls(dir, printdirentry, &d);
> >      > + mg_printf(c,
> >      > + "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
> >      > + "</table><address>Mongoose v.%s</address></body></html>\n",
> >      > + MG_VERSION);
> >      > + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
> >     (c->send.len - off));
> >      > + if (n > sizeof(tmp)) n = 0;
> >      > + memcpy(c->send.buf + off - 12, tmp, n); // Set content length
> >      > + c->is_resp = 0; // Mark response end
> >      > +}
> >      > +#endif
> >      > +
> >      > +// Resolve requested file into `path` and return its fs->st()
> >     result
> >      > +static int uri_to_path2(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + struct mg_fs *fs, struct mg_str url, struct mg_str dir,
> >      > + char *path, size_t path_size) {
> >      > + int flags, tmp;
> >      > + // Append URI to the root_dir, and sanitize it
> >      > + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
> >     dir.buf);
> >      > + if (n + 2 >= path_size) {
> >      > + mg_http_reply(c, 400, "", "Exceeded path size");
> >      > + return -1;
> >      > + }
> >      > + path[path_size - 1] = '\0';
> >      > + // Terminate root dir with slash
> >      > + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
> >      > + if (url.len < hm->uri.len) {
> >      > + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len,
> >     path + n,
> >      > + path_size - n, 0);
> >      > + }
> >      > + path[path_size - 1] = '\0'; // Double-check
> >      > + if (!mg_path_is_sane(mg_str_n(path, path_size))) {
> >      > + mg_http_reply(c, 400, "", "Invalid path");
> >      > + return -1;
> >      > + }
> >      > + n = strlen(path);
> >      > + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
> >     trailing slashes
> >      > + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
> >      > + : fs->st(path, NULL, NULL);
> >      > + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
> >     hm->uri.buf, path,
> >      > + flags));
> >      > + if (flags == 0) {
> >      > + // Do nothing - let's caller decide
> >      > + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
> >      > + hm->uri.buf[hm->uri.len - 1] != '/') {
> >      > + mg_printf(c,
> >      > + "HTTP/1.1 301 Moved\r\n"
> >      > + "Location: %.*s/\r\n"
> >      > + "Content-Length: 0\r\n"
> >      > "\r\n",
> >      > - (int) hm->uri.len, hm->uri.ptr);
> >      > + (int) hm->uri.len, hm->uri.buf);
> >      > c->is_resp = 0;
> >      > flags = -1;
> >      > } else if (flags & MG_FS_DIR) {
> >      > @@ -2057,3318 +3036,11754 @@ static int uri_to_path2(struct
> >     mg_connection *c, struct mg_http_message *hm,
> >      > path[n + 1 + strlen(MG_HTTP_INDEX)] =
> >      > '\0'; // Remove appended .gz in index file name
> >      > } else {
> >      > - path[n] = '\0'; // Remove appended index file name
> >      > + path[n] = '\0'; // Remove appended index file name
> >      > + }
> >      > + }
> >      > + return flags;
> >      > +}
> >      > +
> >      > +static int uri_to_path(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + const struct mg_http_serve_opts *opts, char *path,
> >      > + size_t path_size) {
> >      > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > + struct mg_str k, v, part, s = mg_str(opts->root_dir), u =
> >     {NULL, 0}, p = u;
> >      > + while (mg_span(s, &part, &s, ',')) {
> >      > + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
> >      > + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
> >      > + if (hm->uri.len < k.len) continue;
> >      > + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
> >      > + u = k, p = v;
> >      > + }
> >      > + return uri_to_path2(c, hm, fs, u, p, path, path_size);
> >      > +}
> >      > +
> >      > +void mg_http_serve_dir(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + const struct mg_http_serve_opts *opts) {
> >      > + char path[MG_PATH_MAX];
> >      > + const char *sp = opts->ssi_pattern;
> >      > + int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> >      > + if (flags < 0) {
> >      > + // Do nothing: the response has already been sent by uri_to_path()
> >      > + } else if (flags & MG_FS_DIR) {
> >      > +#if MG_ENABLE_DIRLIST
> >      > + listdir(c, hm, opts, path);
> >      > +#else
> >      > + mg_http_reply(c, 403, "", "Forbidden\n");
> >      > +#endif
> >      > + } else if (flags && sp != NULL && mg_match(mg_str(path),
> >     mg_str(sp), NULL)) {
> >      > + mg_http_serve_ssi(c, opts->root_dir, path);
> >      > + } else {
> >      > + mg_http_serve_file(c, hm, path, opts);
> >      > + }
> >      > +}
> >      > +
> >      > +static bool mg_is_url_safe(int c) {
> >      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> >      > + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
> >     == '~';
> >      > +}
> >      > +
> >      > +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
> >     len) {
> >      > + size_t i, n = 0;
> >      > + for (i = 0; i < sl; i++) {
> >      > + int c = *(unsigned char *) &s[i];
> >      > + if (n + 4 >= len) return 0;
> >      > + if (mg_is_url_safe(c)) {
> >      > + buf[n++] = s[i];
> >      > + } else {
> >      > + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
> >      > + n += 3;
> >      > + }
> >      > + }
> >      > + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
> >     the destination
> >      > + if (len > 0) buf[len - 1] = '\0'; // Always.
> >      > + return n;
> >      > +}
> >      > +
> >      > +void mg_http_creds(struct mg_http_message *hm, char *user,
> >     size_t userlen,
> >      > + char *pass, size_t passlen) {
> >      > + struct mg_str *v = mg_http_get_header(hm, "Authorization");
> >      > + user[0] = pass[0] = '\0';
> >      > + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
> >      > + char buf[256];
> >      > + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf,
> >     sizeof(buf));
> >      > + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
> >      > + if (p != NULL) {
> >      > + mg_snprintf(user, userlen, "%.*s", p - buf, buf);
> >      > + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1,
> >     p + 1);
> >      > + }
> >      > + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ",
> >     7) == 0) {
> >      > + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
> >      > + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> >      > + struct mg_str t = mg_http_get_header_var(*v,
> >     mg_str_n("access_token", 12));
> >      > + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
> >     t.buf);
> >      > + } else {
> >      > + mg_http_get_var(&hm->query, "access_token", pass, passlen);
> >      > + }
> >      > +}
> >      > +
> >      > +static struct mg_str stripquotes(struct mg_str s) {
> >      > + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
> >      > + ? mg_str_n(s.buf + 1, s.len - 2)
> >      > + : s;
> >      > +}
> >      > +
> >      > +struct mg_str mg_http_get_header_var(struct mg_str s, struct
> >     mg_str v) {
> >      > + size_t i;
> >      > + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> >      > + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len)
> >     == 0) {
> >      > + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
> >      > + int q = p < x && *p == '"' ? 1 : 0;
> >      > + while (p < x &&
> >      > + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> >      > + p++;
> >      > + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int)
> >     v.len,
> >      > + // v.buf, (int) (p - b), b));
> >      > + return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> >      > + }
> >      > + }
> >      > + return mg_str_n(NULL, 0);
> >      > +}
> >      > +
> >      > +long mg_http_upload(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > + struct mg_fs *fs, const char *dir, size_t max_size) {
> >      > + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
> >      > + long res = 0, offset;
> >      > + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> >      > + mg_http_get_var(&hm->query, "file", file, sizeof(file));
> >      > + offset = strtol(buf, NULL, 0);
> >      > + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
> >      > + if (hm->body.len == 0) {
> >      > + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
> >      > + } else if (file[0] == '\0') {
> >      > + mg_http_reply(c, 400, "", "file required");
> >      > + res = -1;
> >      > + } else if (mg_path_is_sane(mg_str(file)) == false) {
> >      > + mg_http_reply(c, 400, "", "%s: invalid file", file);
> >      > + res = -2;
> >      > + } else if (offset < 0) {
> >      > + mg_http_reply(c, 400, "", "offset required");
> >      > + res = -3;
> >      > + } else if ((size_t) offset + hm->body.len > max_size) {
> >      > + mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> >      > + (unsigned long) max_size);
> >      > + res = -4;
> >      > + } else {
> >      > + struct mg_fd *fd;
> >      > + size_t current_size = 0;
> >      > + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
> >      > + if (offset == 0) fs->rm(path); // If offset if 0, truncate file
> >      > + fs->st(path, &current_size, NULL);
> >      > + if (offset > 0 && current_size != (size_t) offset) {
> >      > + mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> >      > + res = -5;
> >      > + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> >      > + mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> >      > + res = -6;
> >      > + } else {
> >      > + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
> >      > + mg_fs_close(fd);
> >      > + mg_http_reply(c, 200, "", "%ld", res);
> >      > + }
> >      > + }
> >      > + return res;
> >      > +}
> >      > +
> >      > +int mg_http_status(const struct mg_http_message *hm) {
> >      > + return atoi(hm->uri.buf);
> >      > +}
> >      > +
> >      > +static bool is_hex_digit(int c) {
> >      > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
> >      > + (c >= 'A' && c <= 'F');
> >      > +}
> >      > +
> >      > +static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
> >      > + int i = 0, n = 0;
> >      > + if (len < 3) return 0;
> >      > + while (i < len && is_hex_digit(buf[i])) i++;
> >      > + if (i == 0) return -1; // Error, no length specified
> >      > + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is
> >     too big
> >      > + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return
> >     -1; // Error
> >      > + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n,
> >     sizeof(int)) == false)
> >      > + return -1; // Decode chunk length, overflow
> >      > + if (n < 0) return -1; // Error. TODO(): some checks now redundant
> >      > + if (n > len - i - 4) return 0; // Chunk not yet fully buffered
> >      > + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return
> >     -1; // Error
> >      > + *pl = i + 2, *dl = n;
> >      > + return i + 2 + n + 2;
> >      > +}
> >      > +
> >      > +static void http_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> >      > + struct mg_http_message hm;
> >      > + size_t ofs = 0; // Parsing offset
> >      > + while (c->is_resp == 0 && ofs < c->recv.len) {
> >      > + const char *buf = (char *) c->recv.buf + ofs;
> >      > + int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
> >      > + struct mg_str *te; // Transfer - encoding header
> >      > + bool is_chunked = false;
> >      > + if (n < 0) {
> >      > + // We don't use mg_error() here, to avoid closing pipelined
> >     requests
> >      > + // prematurely, see #2592
> >      > + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
> >      > + c->is_draining = 1;
> >      > + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
> >      > + c->recv.len = 0;
> >      > + return;
> >      > + }
> >      > + if (n == 0) break; // Request is not buffered yet
> >      > + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers
> >      > + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
> >      > + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG
> >      > + hm.body.len = hm.message.len - (size_t) (hm.body.buf -
> >     hm.message.buf);
> >      > + }
> >      > + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
> >      > + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
> >      > + is_chunked = true;
> >      > + } else {
> >      > + mg_error(c, "Invalid Transfer-Encoding"); // See #2460
> >      > + return;
> >      > + }
> >      > + } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
> >      > + // #2593: HTTP packets must contain either Transfer-Encoding or
> >      > + // Content-length
> >      > + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
> >      > + bool require_content_len = false;
> >      > + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST"))
> >     == 0 ||
> >      > + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
> >      > + // POST and PUT should include an entity body. Therefore, they
> >     should
> >      > + // contain a Content-length header. Other requests can also
> >     contain a
> >      > + // body, but their content has no defined semantics (RFC 7231)
> >      > + require_content_len = true;
> >      > + } else if (is_response) {
> >      > + // HTTP spec 7.2 Entity body: All other responses must include
> >     a body
> >      > + // or Content-Length header field defined with a value of 0.
> >      > + int status = mg_http_status(&hm);
> >      > + require_content_len = status >= 200 && status != 204 && status
> >     != 304;
> >      > + }
> >      > + if (require_content_len) {
> >      > + mg_http_reply(c, 411, "", "");
> >      > + MG_ERROR(("%s", "Content length missing from request"));
> >      > + }
> >      > + }
> >      > +
> >      > + if (is_chunked) {
> >      > + // For chunked data, strip off prefixes and suffixes from chunks
> >      > + // and relocate them right after the headers, then report a
> >     message
> >      > + char *s = (char *) c->recv.buf + ofs + n;
> >      > + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs -
> >     (size_t) n);
> >      > +
> >      > + // Find zero-length chunk (the end of the body)
> >      > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o
> >     += cl;
> >      > + if (cl == 0) break; // No zero-len chunk, buffer more data
> >      > + if (cl < 0) {
> >      > + mg_error(c, "Invalid chunk");
> >      > + break;
> >      > + }
> >      > +
> >      > + // Zero chunk found. Second pass: strip + relocate
> >      > + o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
> >      > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
> >      > + memmove(s + hm.body.len, s + o + pl, (size_t) dl);
> >      > + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t)
> >     dl;
> >      > + if (dl == 0) break;
> >      > + }
> >      > + ofs += (size_t) (n + o);
> >      > + } else { // Normal, non-chunked data
> >      > + size_t len = c->recv.len - ofs - (size_t) n;
> >      > + if (hm.body.len > len) break; // Buffer more data
> >      > + ofs += (size_t) n + hm.body.len;
> >      > + }
> >      > +
> >      > + if (c->is_accepted) c->is_resp = 1; // Start generating response
> >      > + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
> >      > + }
> >      > + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete
> >     processed data
> >      > + }
> >      > + (void) ev_data;
> >      > +}
> >      > +
> >      > +static void mg_hfn(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + if (ev == MG_EV_HTTP_MSG) {
> >      > + struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> >      > + if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
> >      > + mg_http_reply(c, 200, "", "ok\n");
> >      > + c->is_draining = 1;
> >      > + c->data[0] = 'X';
> >      > + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
> >      > + int level = (int) mg_json_get_long(hm->body, "$.level",
> >     MG_LL_DEBUG);
> >      > + mg_log_set(level);
> >      > + mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
> >      > + } else {
> >      > + mg_http_reply(c, 200, "", "hi\n");
> >      > + }
> >      > + } else if (ev == MG_EV_CLOSE) {
> >      > + if (c->data[0] == 'X') *(bool *) c->fn_data = true;
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_hello(const char *url) {
> >      > + struct mg_mgr mgr;
> >      > + bool done = false;
> >      > + mg_mgr_init(&mgr);
> >      > + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
> >      > + while (done == false) mg_mgr_poll(&mgr, 100);
> >      > + mg_mgr_free(&mgr);
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> >      > + if (c != NULL) c->pfn = http_cb;
> >      > + return c;
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
> >     char *url,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> >      > + if (c != NULL) c->pfn = http_cb;
> >      > + return c;
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/iobuf.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +static size_t roundup(size_t size, size_t align) {
> >      > + return align == 0 ? size : (size + align - 1) / align * align;
> >      > +}
> >      > +
> >      > +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> >      > + int ok = 1;
> >      > + new_size = roundup(new_size, io->align);
> >      > + if (new_size == 0) {
> >      > + mg_bzero(io->buf, io->size);
> >      > + free(io->buf);
> >      > + io->buf = NULL;
> >      > + io->len = io->size = 0;
> >      > + } else if (new_size != io->size) {
> >      > + // NOTE(lsm): do not use realloc here. Use calloc/free only, to
> >     ease the
> >      > + // porting to some obscure platforms like FreeRTOS
> >      > + void *p = calloc(1, new_size);
> >      > + if (p != NULL) {
> >      > + size_t len = new_size < io->len ? new_size : io->len;
> >      > + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> >      > + mg_bzero(io->buf, io->size);
> >      > + free(io->buf);
> >      > + io->buf = (unsigned char *) p;
> >      > + io->size = new_size;
> >      > + } else {
> >      > + ok = 0;
> >      > + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
> >     new_size));
> >      > + }
> >      > + }
> >      > + return ok;
> >      > +}
> >      > +
> >      > +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> >      > + io->buf = NULL;
> >      > + io->align = align;
> >      > + io->size = io->len = 0;
> >      > + return mg_iobuf_resize(io, size);
> >      > +}
> >      > +
> >      > +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
> >     *buf,
> >      > + size_t len) {
> >      > + size_t new_size = roundup(io->len + len, io->align);
> >      > + mg_iobuf_resize(io, new_size); // Attempt to resize
> >      > + if (new_size != io->size) len = 0; // Resize failure, append
> >     nothing
> >      > + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
> >     io->len - ofs);
> >      > + if (buf != NULL) memmove(io->buf + ofs, buf, len);
> >      > + if (ofs > io->len) io->len += ofs - io->len;
> >      > + io->len += len;
> >      > + return len;
> >      > +}
> >      > +
> >      > +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> >      > + if (ofs > io->len) ofs = io->len;
> >      > + if (ofs + len > io->len) len = io->len - ofs;
> >      > + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
> >     io->len - ofs - len);
> >      > + if (io->buf) mg_bzero(io->buf + io->len - len, len);
> >      > + io->len -= len;
> >      > + return len;
> >      > +}
> >      > +
> >      > +void mg_iobuf_free(struct mg_iobuf *io) {
> >      > + mg_iobuf_resize(io, 0);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/json.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +static const char *escapeseq(int esc) {
> >      > + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> >      > +}
> >      > +
> >      > +static char json_esc(int c, int esc) {
> >      > + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> >      > + for (p = esc1; *p != '\0'; p++) {
> >      > + if (*p == c) return esc2[p - esc1];
> >      > + }
> >      > + return 0;
> >      > +}
> >      > +
> >      > +static int mg_pass_string(const char *s, int len) {
> >      > + int i;
> >      > + for (i = 0; i < len; i++) {
> >      > + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> >      > + i++;
> >      > + } else if (s[i] == '\0') {
> >      > + return MG_JSON_INVALID;
> >      > + } else if (s[i] == '"') {
> >      > + return i;
> >      > + }
> >      > + }
> >      > + return MG_JSON_INVALID;
> >      > +}
> >      > +
> >      > +static double mg_atod(const char *p, int len, int *numlen) {
> >      > + double d = 0.0;
> >      > + int i = 0, sign = 1;
> >      > +
> >      > + // Sign
> >      > + if (i < len && *p == '-') {
> >      > + sign = -1, i++;
> >      > + } else if (i < len && *p == '+') {
> >      > + i++;
> >      > + }
> >      > +
> >      > + // Decimal
> >      > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> >      > + d *= 10.0;
> >      > + d += p[i] - '0';
> >      > + }
> >      > + d *= sign;
> >      > +
> >      > + // Fractional
> >      > + if (i < len && p[i] == '.') {
> >      > + double frac = 0.0, base = 0.1;
> >      > + i++;
> >      > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> >      > + frac += base * (p[i] - '0');
> >      > + base /= 10.0;
> >      > + }
> >      > + d += frac * sign;
> >      > + }
> >      > +
> >      > + // Exponential
> >      > + if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> >      > + int j, exp = 0, minus = 0;
> >      > + i++;
> >      > + if (i < len && p[i] == '-') minus = 1, i++;
> >      > + if (i < len && p[i] == '+') i++;
> >      > + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> >      > + exp = exp * 10 + (p[i++] - '0');
> >      > + if (minus) exp = -exp;
> >      > + for (j = 0; j < exp; j++) d *= 10.0;
> >      > + for (j = 0; j < -exp; j++) d /= 10.0;
> >      > + }
> >      > +
> >      > + if (numlen != NULL) *numlen = i;
> >      > + return d;
> >      > +}
> >      > +
> >      > +// Iterate over object or array elements
> >      > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
> >     *key,
> >      > + struct mg_str *val) {
> >      > + if (ofs >= obj.len) {
> >      > + ofs = 0; // Out of boundaries, stop scanning
> >      > + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
> >      > + ofs = 0; // Not an array or object, stop
> >      > + } else {
> >      > + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
> >      > + if (ofs == 0) ofs++, sub.buf++, sub.len--;
> >      > + if (*obj.buf == '[') { // Iterate over an array
> >      > + int n = 0, o = mg_json_get(sub, "$", &n);
> >      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> >      > + ofs = 0; // Error parsing key, stop scanning
> >      > + } else {
> >      > + if (key) *key = mg_str_n(NULL, 0);
> >      > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> >      > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> >      > + }
> >      > + } else { // Iterate over an object
> >      > + int n = 0, o = mg_json_get(sub, "$", &n);
> >      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> >      > + ofs = 0; // Error parsing key, stop scanning
> >      > + } else {
> >      > + if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
> >      > + sub.buf += o + n, sub.len -= (size_t) (o + n);
> >      > + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
> >      > + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
> >      > + n = 0, o = mg_json_get(sub, "$", &n);
> >      > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
> >      > + ofs = 0; // Error parsing value, stop scanning
> >      > + } else {
> >      > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
> >      > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
> >      > + }
> >      > + }
> >      > + }
> >      > + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
> >      > + while (ofs && ofs < obj.len &&
> >      > + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
> >      > + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
> >      > + ofs++;
> >      > + }
> >      > + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
> >      > + if (ofs > obj.len) ofs = 0;
> >      > + }
> >      > + return ofs;
> >      > +}
> >      > +
> >      > +int mg_json_get(struct mg_str json, const char *path, int
> >     *toklen) {
> >      > + const char *s = json.buf;
> >      > + int len = (int) json.len;
> >      > + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
> >     S_VALUE;
> >      > + unsigned char nesting[MG_JSON_MAX_DEPTH];
> >      > + int i = 0; // Current offset in `s`
> >      > + int j = 0; // Offset in `s` we're looking for (return value)
> >      > + int depth = 0; // Current depth (nesting level)
> >      > + int ed = 0; // Expected depth
> >      > + int pos = 1; // Current position in `path`
> >      > + int ci = -1, ei = -1; // Current and expected index in array
> >      > +
> >      > + if (toklen) *toklen = 0;
> >      > + if (path[0] != '$') return MG_JSON_INVALID;
> >      > +
> >      > +#define MG_CHECKRET(x) \
> >      > + do { \
> >      > + if (depth == ed && path[pos] == '\0' && ci == ei) { \
> >      > + if (toklen) *toklen = i - j + 1; \
> >      > + return j; \
> >      > + } \
> >      > + } while (0)
> >      > +
> >      > +// In the ascii table, the distance between `[` and `]` is 2.
> >      > +// Ditto for `{` and `}`. Hence +2 in the code below.
> >      > +#define MG_EOO(x) \
> >      > + do { \
> >      > + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
> >      > + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> >      > + depth--; \
> >      > + MG_CHECKRET(x); \
> >      > + } while (0)
> >      > +
> >      > + for (i = 0; i < len; i++) {
> >      > + unsigned char c = ((unsigned char *) s)[i];
> >      > + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> >      > + switch (expecting) {
> >      > + case S_VALUE:
> >      > + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
> >     ci, ei);
> >      > + if (depth == ed) j = i;
> >      > + if (c == '{') {
> >      > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> >      > + if (depth == ed && path[pos] == '.' && ci == ei) {
> >      > + // If we start the object, reset array indices
> >      > + ed++, pos++, ci = ei = -1;
> >      > + }
> >      > + nesting[depth++] = c;
> >      > + expecting = S_KEY;
> >      > + break;
> >      > + } else if (c == '[') {
> >      > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> >      > + if (depth == ed && path[pos] == '[' && ei == ci) {
> >      > + ed++, pos++, ci = 0;
> >      > + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> >      > + ei *= 10;
> >      > + ei += path[pos] - '0';
> >      > + }
> >      > + if (path[pos] != 0) pos++;
> >      > + }
> >      > + nesting[depth++] = c;
> >      > + break;
> >      > + } else if (c == ']' && depth > 0) { // Empty array
> >      > + MG_EOO(']');
> >      > + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
> >     == 0) {
> >      > + i += 3;
> >      > + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
> >     == 0) {
> >      > + i += 3;
> >      > + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
> >     == 0) {
> >      > + i += 4;
> >      > + } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> >      > + int numlen = 0;
> >      > + mg_atod(&s[i], len - i, &numlen);
> >      > + i += numlen - 1;
> >      > + } else if (c == '"') {
> >      > + int n = mg_pass_string(&s[i + 1], len - i - 1);
> >      > + if (n < 0) return n;
> >      > + i += n + 1;
> >      > + } else {
> >      > + return MG_JSON_INVALID;
> >      > + }
> >      > + MG_CHECKRET('V');
> >      > + if (depth == ed && ei >= 0) ci++;
> >      > + expecting = S_COMMA_OR_EOO;
> >      > + break;
> >      > +
> >      > + case S_KEY:
> >      > + if (c == '"') {
> >      > + int n = mg_pass_string(&s[i + 1], len - i - 1);
> >      > + if (n < 0) return n;
> >      > + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> >      > + if (depth < ed) return MG_JSON_NOT_FOUND;
> >      > + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> >      > + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos,
> >     path, n,
> >      > + // &s[i + 1], n, depth, ed, ci, ei);
> >      > + // NOTE(cpq): in the check sequence below is important.
> >      > + // strncmp() must go first: it fails fast if the remaining length
> >      > + // of the path is smaller than `n`.
> >      > + if (depth == ed && path[pos - 1] == '.' &&
> >      > + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> >      > + (path[pos + n] == '\0' || path[pos + n] == '.' ||
> >      > + path[pos + n] == '[')) {
> >      > + pos += n;
> >      > + }
> >      > + i += n + 1;
> >      > + expecting = S_COLON;
> >      > + } else if (c == '}') { // Empty object
> >      > + MG_EOO('}');
> >      > + expecting = S_COMMA_OR_EOO;
> >      > + if (depth == ed && ei >= 0) ci++;
> >      > + } else {
> >      > + return MG_JSON_INVALID;
> >      > + }
> >      > + break;
> >      > +
> >      > + case S_COLON:
> >      > + if (c == ':') {
> >      > + expecting = S_VALUE;
> >      > + } else {
> >      > + return MG_JSON_INVALID;
> >      > + }
> >      > + break;
> >      > +
> >      > + case S_COMMA_OR_EOO:
> >      > + if (depth <= 0) {
> >      > + return MG_JSON_INVALID;
> >      > + } else if (c == ',') {
> >      > + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> >      > + } else if (c == ']' || c == '}') {
> >      > + if (depth == ed && c == '}' && path[pos - 1] == '.')
> >      > + return MG_JSON_NOT_FOUND;
> >      > + if (depth == ed && c == ']' && path[pos - 1] == ',')
> >      > + return MG_JSON_NOT_FOUND;
> >      > + MG_EOO('O');
> >      > + if (depth == ed && ei >= 0) ci++;
> >      > + } else {
> >      > + return MG_JSON_INVALID;
> >      > + }
> >      > + break;
> >      > + }
> >      > + }
> >      > + return MG_JSON_NOT_FOUND;
> >      > +}
> >      > +
> >      > +struct mg_str mg_json_get_tok(struct mg_str json, const char
> >     *path) {
> >      > + int len = 0, ofs = mg_json_get(json, path, &len);
> >      > + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
> >      > + (size_t) (len < 0 ? 0 : len));
> >      > +}
> >      > +
> >      > +bool mg_json_get_num(struct mg_str json, const char *path,
> >     double *v) {
> >      > + int n, toklen, found = 0;
> >      > + if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> >      > + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <=
> >     '9'))) {
> >      > + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
> >      > + found = 1;
> >      > + }
> >      > + return found;
> >      > +}
> >      > +
> >      > +bool mg_json_get_bool(struct mg_str json, const char *path, bool
> >     *v) {
> >      > + int found = 0, off = mg_json_get(json, path, NULL);
> >      > + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
> >      > + if (v != NULL) *v = json.buf[off] == 't';
> >      > + found = 1;
> >      > + }
> >      > + return found;
> >      > +}
> >      > +
> >      > +bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
> >      > + size_t i, j;
> >      > + for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
> >      > + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
> >      > + // \uXXXX escape. We process simple one-byte chars \u00xx
> >     within ASCII
> >      > + // range. More complex chars would require dragging in a UTF8
> >     library,
> >      > + // which is too much for us
> >      > + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
> >      > + sizeof(uint8_t)) == false)
> >      > + return false;
> >      > + i += 5;
> >      > + } else if (s.buf[i] == '\\' && i + 1 < s.len) {
> >      > + char c = json_esc(s.buf[i + 1], 0);
> >      > + if (c == 0) return false;
> >      > + to[j] = c;
> >      > + i++;
> >      > + } else {
> >      > + to[j] = s.buf[i];
> >      > + }
> >      > + }
> >      > + if (j >= n) return false;
> >      > + if (n > 0) to[j] = '\0';
> >      > + return true;
> >      > +}
> >      > +
> >      > +char *mg_json_get_str(struct mg_str json, const char *path) {
> >      > + char *result = NULL;
> >      > + int len = 0, off = mg_json_get(json, path, &len);
> >      > + if (off >= 0 && len > 1 && json.buf[off] == '"') {
> >      > + if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> >      > + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len -
> >     2)),
> >      > + result, (size_t) len)) {
> >      > + free(result);
> >      > + result = NULL;
> >      > + }
> >      > + }
> >      > + return result;
> >      > +}
> >      > +
> >      > +char *mg_json_get_b64(struct mg_str json, const char *path, int
> >     *slen) {
> >      > + char *result = NULL;
> >      > + int len = 0, off = mg_json_get(json, path, &len);
> >      > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> >      > + (result = (char *) calloc(1, (size_t) len)) != NULL) {
> >      > + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len -
> >     2), result,
> >      > + (size_t) len);
> >      > + if (slen != NULL) *slen = (int) k;
> >      > + }
> >      > + return result;
> >      > +}
> >      > +
> >      > +char *mg_json_get_hex(struct mg_str json, const char *path, int
> >     *slen) {
> >      > + char *result = NULL;
> >      > + int len = 0, off = mg_json_get(json, path, &len);
> >      > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
> >      > + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> >      > + int i;
> >      > + for (i = 0; i < len - 2; i += 2) {
> >      > + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16,
> >     &result[i >> 1],
> >      > + sizeof(uint8_t));
> >      > + }
> >      > + result[len / 2 - 1] = '\0';
> >      > + if (slen != NULL) *slen = len / 2 - 1;
> >      > + }
> >      > + return result;
> >      > +}
> >      > +
> >      > +long mg_json_get_long(struct mg_str json, const char *path, long
> >     dflt) {
> >      > + double dv;
> >      > + long result = dflt;
> >      > + if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> >      > + return result;
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/log.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +int mg_log_level = MG_LL_INFO;
> >      > +static mg_pfn_t s_log_func = mg_pfn_stdout;
> >      > +static void *s_log_func_param = NULL;
> >      > +
> >      > +void mg_log_set_fn(mg_pfn_t fn, void *param) {
> >      > + s_log_func = fn;
> >      > + s_log_func_param = param;
> >      > +}
> >      > +
> >      > +static void logc(unsigned char c) {
> >      > + s_log_func((char) c, s_log_func_param);
> >      > +}
> >      > +
> >      > +static void logs(const char *buf, size_t len) {
> >      > + size_t i;
> >      > + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> >      > +}
> >      > +
> >      > +#if MG_ENABLE_CUSTOM_LOG
> >      > +// Let user define their own mg_log_prefix() and mg_log()
> >      > +#else
> >      > +void mg_log_prefix(int level, const char *file, int line, const
> >     char *fname) {
> >      > + const char *p = strrchr(file, '/');
> >      > + char buf[41];
> >      > + size_t n;
> >      > + if (p == NULL) p = strrchr(file, '\\');
> >      > + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s",
> >     mg_millis(), level,
> >      > + p == NULL ? file : p + 1, line, fname);
> >      > + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> >      > + while (n < sizeof(buf)) buf[n++] = ' ';
> >      > + logs(buf, n - 1);
> >      > +}
> >      > +
> >      > +void mg_log(const char *fmt, ...) {
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> >      > + va_end(ap);
> >      > + logs("\r\n", 2);
> >      > +}
> >      > +#endif
> >      > +
> >      > +static unsigned char nibble(unsigned c) {
> >      > + return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> >      > +}
> >      > +
> >      > +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> >      > +void mg_hexdump(const void *buf, size_t len) {
> >      > + const unsigned char *p = (const unsigned char *) buf;
> >      > + unsigned char ascii[16], alen = 0;
> >      > + size_t i;
> >      > + for (i = 0; i < len; i++) {
> >      > + if ((i % 16) == 0) {
> >      > + // Print buffered ascii chars
> >      > + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
> >     alen = 0;
> >      > + // Print hex address, then \t
> >      > + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> >      > + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
> >      > + }
> >      > + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
> >     nibbles, e.g. c5
> >      > + logc(' '); // Space after hex number
> >      > + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
> >     buf
> >      > + }
> >      > + while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
> >      > + logs(" ", 2), logs((char *) ascii, 16), logc('\n');
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/md5.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +// This code implements the MD5 message-digest algorithm.
> >      > +// The algorithm is due to Ron Rivest. This code was
> >      > +// written by Colin Plumb in 1993, no copyright is claimed.
> >      > +// This code is in the public domain; do with it what you wish.
> >      > +//
> >      > +// Equivalent code is available from RSA Data Security, Inc.
> >      > +// This code has been tested against that, and is equivalent,
> >      > +// except that you don't need to include two pages of legalese
> >      > +// with every copy.
> >      > +//
> >      > +// To compute the message digest of a chunk of bytes, declare an
> >      > +// MD5Context structure, pass it to MD5Init, call MD5Update as
> >      > +// needed on buffers full of bytes, and then call MD5Final, which
> >      > +// will fill a supplied 16-byte array with the digest.
> >      > +
> >      > +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> >      > +
> >      > +static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> >      > + if (MG_BIG_ENDIAN) {
> >      > + do {
> >      > + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> >      > + ((unsigned) buf[1] << 8 | buf[0]);
> >      > + *(uint32_t *) buf = t;
> >      > + buf += 4;
> >      > + } while (--longs);
> >      > + } else {
> >      > + (void) buf, (void) longs; // Little endian. Do nothing
> >      > + }
> >      > +}
> >      > +
> >      > +#define F1(x, y, z) (z ^ (x & (y ^ z)))
> >      > +#define F2(x, y, z) F1(z, x, y)
> >      > +#define F3(x, y, z) (x ^ y ^ z)
> >      > +#define F4(x, y, z) (y ^ (x | ~z))
> >      > +
> >      > +#define MD5STEP(f, w, x, y, z, data, s) \
> >      > + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> >      > +
> >      > +/*
> >      > + * Start MD5 accumulation. Set bit count to 0 and buffer to
> >     mysterious
> >      > + * initialization constants.
> >      > + */
> >      > +void mg_md5_init(mg_md5_ctx *ctx) {
> >      > + ctx->buf[0] = 0x67452301;
> >      > + ctx->buf[1] = 0xefcdab89;
> >      > + ctx->buf[2] = 0x98badcfe;
> >      > + ctx->buf[3] = 0x10325476;
> >      > +
> >      > + ctx->bits[0] = 0;
> >      > + ctx->bits[1] = 0;
> >      > +}
> >      > +
> >      > +static void mg_md5_transform(uint32_t buf[4], uint32_t const
> >     in[16]) {
> >      > + uint32_t a, b, c, d;
> >      > +
> >      > + a = buf[0];
> >      > + b = buf[1];
> >      > + c = buf[2];
> >      > + d = buf[3];
> >      > +
> >      > + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> >      > + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> >      > + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> >      > + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> >      > + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> >      > + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> >      > + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> >      > + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> >      > + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> >      > + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> >      > + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> >      > + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> >      > + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> >      > + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> >      > + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> >      > + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> >      > +
> >      > + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> >      > + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> >      > + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> >      > + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> >      > + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> >      > + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> >      > + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> >      > + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> >      > + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> >      > + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> >      > + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> >      > + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> >      > + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> >      > + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> >      > + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> >      > + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> >      > +
> >      > + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> >      > + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> >      > + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> >      > + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> >      > + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> >      > + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> >      > + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> >      > + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> >      > + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> >      > + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> >      > + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> >      > + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> >      > + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> >      > + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> >      > + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> >      > + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> >      > +
> >      > + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> >      > + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> >      > + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> >      > + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> >      > + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> >      > + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> >      > + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> >      > + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> >      > + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> >      > + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> >      > + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> >      > + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> >      > + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> >      > + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> >      > + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> >      > + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> >      > +
> >      > + buf[0] += a;
> >      > + buf[1] += b;
> >      > + buf[2] += c;
> >      > + buf[3] += d;
> >      > +}
> >      > +
> >      > +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
> >     size_t len) {
> >      > + uint32_t t;
> >      > +
> >      > + t = ctx->bits[0];
> >      > + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
> >     ctx->bits[1]++;
> >      > + ctx->bits[1] += (uint32_t) len >> 29;
> >      > +
> >      > + t = (t >> 3) & 0x3f;
> >      > +
> >      > + if (t) {
> >      > + unsigned char *p = (unsigned char *) ctx->in + t;
> >      > +
> >      > + t = 64 - t;
> >      > + if (len < t) {
> >      > + memcpy(p, buf, len);
> >      > + return;
> >      > + }
> >      > + memcpy(p, buf, t);
> >      > + mg_byte_reverse(ctx->in, 16);
> >      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > + buf += t;
> >      > + len -= t;
> >      > + }
> >      > +
> >      > + while (len >= 64) {
> >      > + memcpy(ctx->in, buf, 64);
> >      > + mg_byte_reverse(ctx->in, 16);
> >      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > + buf += 64;
> >      > + len -= 64;
> >      > + }
> >      > +
> >      > + memcpy(ctx->in, buf, len);
> >      > +}
> >      > +
> >      > +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> >      > + unsigned count;
> >      > + unsigned char *p;
> >      > + uint32_t *a;
> >      > +
> >      > + count = (ctx->bits[0] >> 3) & 0x3F;
> >      > +
> >      > + p = ctx->in + count;
> >      > + *p++ = 0x80;
> >      > + count = 64 - 1 - count;
> >      > + if (count < 8) {
> >      > + memset(p, 0, count);
> >      > + mg_byte_reverse(ctx->in, 16);
> >      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > + memset(ctx->in, 0, 56);
> >      > + } else {
> >      > + memset(p, 0, count - 8);
> >      > + }
> >      > + mg_byte_reverse(ctx->in, 14);
> >      > +
> >      > + a = (uint32_t *) ctx->in;
> >      > + a[14] = ctx->bits[0];
> >      > + a[15] = ctx->bits[1];
> >      > +
> >      > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > + mg_byte_reverse((unsigned char *) ctx->buf, 4);
> >      > + memcpy(digest, ctx->buf, 16);
> >      > + memset((char *) ctx, 0, sizeof(*ctx));
> >      > +}
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/mqtt.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#define MQTT_CLEAN_SESSION 0x02
> >      > +#define MQTT_HAS_WILL 0x04
> >      > +#define MQTT_WILL_RETAIN 0x20
> >      > +#define MQTT_HAS_PASSWORD 0x40
> >      > +#define MQTT_HAS_USER_NAME 0x80
> >      > +
> >      > +struct mg_mqtt_pmap {
> >      > + uint8_t id;
> >      > + uint8_t type;
> >      > +};
> >      > +
> >      > +static const struct mg_mqtt_pmap s_prop_map[] = {
> >      > + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> >      > + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> >      > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
> >      > + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
> >      > + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
> >      > + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
> >      > + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
> >      > + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
> >      > + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> >      > + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
> >      > + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
> >      > + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
> >      > + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
> >      > + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE,
> >     MQTT_PROP_TYPE_BYTE},
> >      > + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
> >      > +
> >      > +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
> >     uint8_t flags,
> >      > + uint32_t len) {
> >      > + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> >      > + buf[0] = (uint8_t) ((cmd << 4) | flags);
> >      > + do {
> >      > + *vlen = len % 0x80;
> >      > + len /= 0x80;
> >      > + if (len > 0) *vlen |= 0x80;
> >      > + vlen++;
> >      > + } while (len > 0 && vlen < &buf[sizeof(buf)]);
> >      > + mg_send(c, buf, (size_t) (vlen - buf));
> >      > +}
> >      > +
> >      > +static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> >      > + mg_send(c, &value, sizeof(value));
> >      > +}
> >      > +
> >      > +static void mg_send_u32(struct mg_connection *c, uint32_t value) {
> >      > + mg_send(c, &value, sizeof(value));
> >      > +}
> >      > +
> >      > +static uint8_t varint_size(size_t length) {
> >      > + uint8_t bytes_needed = 0;
> >      > + do {
> >      > + bytes_needed++;
> >      > + length /= 0x80;
> >      > + } while (length > 0);
> >      > + return bytes_needed;
> >      > +}
> >      > +
> >      > +static size_t encode_varint(uint8_t *buf, size_t value) {
> >      > + size_t len = 0;
> >      > +
> >      > + do {
> >      > + uint8_t b = (uint8_t) (value % 128);
> >      > + value /= 128;
> >      > + if (value > 0) b |= 0x80;
> >      > + buf[len++] = b;
> >      > + } while (value > 0);
> >      > +
> >      > + return len;
> >      > +}
> >      > +
> >      > +static size_t decode_varint(const uint8_t *buf, size_t len,
> >     size_t *value) {
> >      > + size_t multiplier = 1, offset;
> >      > + *value = 0;
> >      > +
> >      > + for (offset = 0; offset < 4 && offset < len; offset++) {
> >      > + uint8_t encoded_byte = buf[offset];
> >      > + *value += (encoded_byte & 0x7f) * multiplier;
> >      > + multiplier *= 128;
> >      > +
> >      > + if ((encoded_byte & 0x80) == 0) return offset + 1;
> >      > + }
> >      > +
> >      > + return 0;
> >      > +}
> >      > +
> >      > +static int mqtt_prop_type_by_id(uint8_t prop_id) {
> >      > + size_t i, num_properties = sizeof(s_prop_map) /
> >     sizeof(s_prop_map[0]);
> >      > + for (i = 0; i < num_properties; ++i) {
> >      > + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
> >      > + }
> >      > + return -1; // Property ID not found
> >      > +}
> >      > +
> >      > +// Returns the size of the properties section, without the
> >      > +// size of the content's length
> >      > +static size_t get_properties_length(struct mg_mqtt_prop *props,
> >     size_t count) {
> >      > + size_t i, size = 0;
> >      > + for (i = 0; i < count; i++) {
> >      > + size++; // identifier
> >      > + switch (mqtt_prop_type_by_id(props[i].id)) {
> >      > + case MQTT_PROP_TYPE_STRING_PAIR:
> >      > + size += (uint32_t) (props[i].val.len + props[i].key.len +
> >      > + 2 * sizeof(uint16_t));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_STRING:
> >      > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BINARY_DATA:
> >      > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_VARIABLE_INT:
> >      > + size += varint_size((uint32_t) props[i].iv);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_INT:
> >      > + size += (uint32_t) sizeof(uint32_t);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_SHORT:
> >      > + size += (uint32_t) sizeof(uint16_t);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BYTE:
> >      > + size += (uint32_t) sizeof(uint8_t);
> >      > + break;
> >      > + default:
> >      > + return size; // cannot parse further down
> >      > + }
> >      > + }
> >      > +
> >      > + return size;
> >      > +}
> >      > +
> >      > +// returns the entire size of the properties section, including the
> >      > +// size of the variable length of the content
> >      > +static size_t get_props_size(struct mg_mqtt_prop *props, size_t
> >     count) {
> >      > + size_t size = get_properties_length(props, count);
> >      > + size += varint_size(size);
> >      > + return size;
> >      > +}
> >      > +
> >      > +static void mg_send_mqtt_properties(struct mg_connection *c,
> >      > + struct mg_mqtt_prop *props, size_t nprops) {
> >      > + size_t total_size = get_properties_length(props, nprops);
> >      > + uint8_t buf_v[4] = {0, 0, 0, 0};
> >      > + uint8_t buf[4] = {0, 0, 0, 0};
> >      > + size_t i, len = encode_varint(buf, total_size);
> >      > +
> >      > + mg_send(c, buf, (size_t) len);
> >      > + for (i = 0; i < nprops; i++) {
> >      > + mg_send(c, &props[i].id, sizeof(props[i].id));
> >      > + switch (mqtt_prop_type_by_id(props[i].id)) {
> >      > + case MQTT_PROP_TYPE_STRING_PAIR:
> >      > + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
> >      > + mg_send(c, props[i].key.buf, props[i].key.len);
> >      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> >      > + mg_send(c, props[i].val.buf, props[i].val.len);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BYTE:
> >      > + mg_send(c, &props[i].iv, sizeof(uint8_t));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_SHORT:
> >      > + mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_INT:
> >      > + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
> >      > + break;
> >      > + case MQTT_PROP_TYPE_STRING:
> >      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> >      > + mg_send(c, props[i].val.buf, props[i].val.len);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BINARY_DATA:
> >      > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
> >      > + mg_send(c, props[i].val.buf, props[i].val.len);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_VARIABLE_INT:
> >      > + len = encode_varint(buf_v, props[i].iv);
> >      > + mg_send(c, buf_v, (size_t) len);
> >      > + break;
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct
> >     mg_mqtt_prop *prop,
> >      > + size_t ofs) {
> >      > + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
> >      > + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
> >      > + size_t new_pos = ofs, len;
> >      > + prop->id = i[0];
> >      > +
> >      > + if (ofs >= msg->dgram.len || ofs >= msg->props_start +
> >     msg->props_size)
> >      > + return 0;
> >      > + i++, new_pos++;
> >      > +
> >      > + switch (mqtt_prop_type_by_id(prop->id)) {
> >      > + case MQTT_PROP_TYPE_STRING_PAIR:
> >      > + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> >      > + prop->key.buf = (char *) i + 2;
> >      > + i += 2 + prop->key.len;
> >      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> >      > + prop->val.buf = (char *) i + 2;
> >      > + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BYTE:
> >      > + prop->iv = (uint8_t) i[0];
> >      > + new_pos++;
> >      > + break;
> >      > + case MQTT_PROP_TYPE_SHORT:
> >      > + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> >      > + new_pos += sizeof(uint16_t);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_INT:
> >      > + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
> >      > + ((uint32_t) i[2] << 8) | i[3];
> >      > + new_pos += sizeof(uint32_t);
> >      > + break;
> >      > + case MQTT_PROP_TYPE_STRING:
> >      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> >      > + prop->val.buf = (char *) i + 2;
> >      > + new_pos += 2 + prop->val.len;
> >      > + break;
> >      > + case MQTT_PROP_TYPE_BINARY_DATA:
> >      > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
> >      > + prop->val.buf = (char *) i + 2;
> >      > + new_pos += 2 + prop->val.len;
> >      > + break;
> >      > + case MQTT_PROP_TYPE_VARIABLE_INT:
> >      > + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
> >      > + new_pos = (!len) ? 0 : new_pos + len;
> >      > + break;
> >      > + default:
> >      > + new_pos = 0;
> >      > + }
> >      > +
> >      > + return new_pos;
> >      > +}
> >      > +
> >      > +void mg_mqtt_login(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts) {
> >      > + char client_id[21];
> >      > + struct mg_str cid = opts->client_id;
> >      > + size_t total_len = 7 + 1 + 2 + 2;
> >      > + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> >      > +
> >      > + if (cid.len == 0) {
> >      > + mg_random_str(client_id, sizeof(client_id) - 1);
> >      > + client_id[sizeof(client_id) - 1] = '\0';
> >      > + cid = mg_str(client_id);
> >      > + }
> >      > +
> >      > + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
> >     (3.1.1)
> >      > + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
> >      > + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags
> >      > + if (opts->user.len > 0) {
> >      > + total_len += 2 + (uint32_t) opts->user.len;
> >      > + hdr[7] |= MQTT_HAS_USER_NAME;
> >      > + }
> >      > + if (opts->pass.len > 0) {
> >      > + total_len += 2 + (uint32_t) opts->pass.len;
> >      > + hdr[7] |= MQTT_HAS_PASSWORD;
> >      > + }
> >      > + if (opts->topic.len > 0) { // allow zero-length msgs,
> >     message.len is size_t
> >      > + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t)
> >     opts->message.len;
> >      > + hdr[7] |= MQTT_HAS_WILL;
> >      > + }
> >      > + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> >      > + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
> >      > + total_len += (uint32_t) cid.len;
> >      > + if (c->is_mqtt5) {
> >      > + total_len += get_props_size(opts->props, opts->num_props);
> >      > + if (hdr[7] & MQTT_HAS_WILL)
> >      > + total_len += get_props_size(opts->will_props,
> >     opts->num_will_props);
> >      > + }
> >      > +
> >      > + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
> >      > + mg_send(c, hdr, sizeof(hdr));
> >      > + // keepalive == 0 means "do not disconnect us!"
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> >      > +
> >      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
> >     opts->num_props);
> >      > +
> >      > + mg_send_u16(c, mg_htons((uint16_t) cid.len));
> >      > + mg_send(c, cid.buf, cid.len);
> >      > +
> >      > + if (hdr[7] & MQTT_HAS_WILL) {
> >      > + if (c->is_mqtt5)
> >      > + mg_send_mqtt_properties(c, opts->will_props,
> >     opts->num_will_props);
> >      > +
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> >      > + mg_send(c, opts->topic.buf, opts->topic.len);
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
> >      > + mg_send(c, opts->message.buf, opts->message.len);
> >      > + }
> >      > + if (opts->user.len > 0) {
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> >      > + mg_send(c, opts->user.buf, opts->user.len);
> >      > + }
> >      > + if (opts->pass.len > 0) {
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> >      > + mg_send(c, opts->pass.buf, opts->pass.len);
> >      > + }
> >      > +}
> >      > +
> >      > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts) {
> >      > + uint16_t id = opts->retransmit_id;
> >      > + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) |
> >     (opts->retain ? 1 : 0));
> >      > + size_t len = 2 + opts->topic.len + opts->message.len;
> >      > + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
> >      > + (char *) opts->topic.buf, (int) opts->message.len,
> >      > + (char *) opts->message.buf));
> >      > + if (opts->qos > 0) len += 2;
> >      > + if (c->is_mqtt5) len += get_props_size(opts->props,
> >     opts->num_props);
> >      > +
> >      > + if (opts->qos > 0 && id != 0) flags |= 1 << 3;
> >      > + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> >      > + mg_send(c, opts->topic.buf, opts->topic.len);
> >      > + if (opts->qos > 0) { // need to send 'id' field
> >      > + if (id == 0) { // generate new one if not resending
> >      > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> >      > + id = c->mgr->mqtt_id;
> >      > + }
> >      > + mg_send_u16(c, mg_htons(id));
> >      > + }
> >      > +
> >      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
> >     opts->num_props);
> >      > +
> >      > + if (opts->message.len > 0) mg_send(c, opts->message.buf,
> >     opts->message.len);
> >      > + return id;
> >      > +}
> >      > +
> >      > +void mg_mqtt_sub(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts) {
> >      > + uint8_t qos_ = opts->qos & 3;
> >      > + size_t plen = c->is_mqtt5 ? get_props_size(opts->props,
> >     opts->num_props) : 0;
> >      > + size_t len = 2 + opts->topic.len + 2 + 1 + plen;
> >      > +
> >      > + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
> >      > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> >      > + mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> >      > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
> >     opts->num_props);
> >      > +
> >      > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
> >      > + mg_send(c, opts->topic.buf, opts->topic.len);
> >      > + mg_send(c, &qos_, sizeof(qos_));
> >      > +}
> >      > +
> >      > +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> >      > + struct mg_mqtt_message *m) {
> >      > + uint8_t lc = 0, *p, *end;
> >      > + uint32_t n = 0, len_len = 0;
> >      > +
> >      > + memset(m, 0, sizeof(*m));
> >      > + m->dgram.buf = (char *) buf;
> >      > + if (len < 2) return MQTT_INCOMPLETE;
> >      > + m->cmd = (uint8_t) (buf[0] >> 4);
> >      > + m->qos = (buf[0] >> 1) & 3;
> >      > +
> >      > + n = len_len = 0;
> >      > + p = (uint8_t *) buf + 1;
> >      > + while ((size_t) (p - buf) < len) {
> >      > + lc = *((uint8_t *) p++);
> >      > + n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> >      > + len_len++;
> >      > + if (!(lc & 0x80)) break;
> >      > + if (len_len >= 4) return MQTT_MALFORMED;
> >      > + }
> >      > + end = p + n;
> >      > + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> >      > + m->dgram.len = (size_t) (end - buf);
> >      > +
> >      > + switch (m->cmd) {
> >      > + case MQTT_CMD_CONNACK:
> >      > + if (end - p < 2) return MQTT_MALFORMED;
> >      > + m->ack = p[1];
> >      > + break;
> >      > + case MQTT_CMD_PUBACK:
> >      > + case MQTT_CMD_PUBREC:
> >      > + case MQTT_CMD_PUBREL:
> >      > + case MQTT_CMD_PUBCOMP:
> >      > + case MQTT_CMD_SUBSCRIBE:
> >      > + case MQTT_CMD_SUBACK:
> >      > + case MQTT_CMD_UNSUBSCRIBE:
> >      > + case MQTT_CMD_UNSUBACK:
> >      > + if (p + 2 > end) return MQTT_MALFORMED;
> >      > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > + p += 2;
> >      > + break;
> >      > + case MQTT_CMD_PUBLISH: {
> >      > + if (p + 2 > end) return MQTT_MALFORMED;
> >      > + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > + m->topic.buf = (char *) p + 2;
> >      > + p += 2 + m->topic.len;
> >      > + if (p > end) return MQTT_MALFORMED;
> >      > + if (m->qos > 0) {
> >      > + if (p + 2 > end) return MQTT_MALFORMED;
> >      > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > + p += 2;
> >      > + }
> >      > + if (p > end) return MQTT_MALFORMED;
> >      > + if (version == 5 && p + 2 < end) {
> >      > + len_len =
> >      > + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
> >      > + if (!len_len) return MQTT_MALFORMED;
> >      > + m->props_start = (size_t) (p + len_len - buf);
> >      > + p += len_len + m->props_size;
> >      > + }
> >      > + if (p > end) return MQTT_MALFORMED;
> >      > + m->data.buf = (char *) p;
> >      > + m->data.len = (size_t) (end - p);
> >      > + break;
> >      > + }
> >      > + default:
> >      > + break;
> >      > + }
> >      > + return MQTT_OK;
> >      > +}
> >      > +
> >      > +static void mqtt_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + if (ev == MG_EV_READ) {
> >      > + for (;;) {
> >      > + uint8_t version = c->is_mqtt5 ? 5 : 4;
> >      > + struct mg_mqtt_message mm;
> >      > + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> >      > + if (rc == MQTT_MALFORMED) {
> >      > + MG_ERROR(("%lu MQTT malformed message", c->id));
> >      > + c->is_closing = 1;
> >      > + break;
> >      > + } else if (rc == MQTT_OK) {
> >      > + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> >      > + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
> >      > + switch (mm.cmd) {
> >      > + case MQTT_CMD_CONNACK:
> >      > + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> >      > + if (mm.ack == 0) {
> >      > + MG_DEBUG(("%lu Connected", c->id));
> >      > + } else {
> >      > + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> >      > + c->is_closing = 1;
> >      > + }
> >      > + break;
> >      > + case MQTT_CMD_PUBLISH: {
> >      > + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> >      > + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
> >      > + if (mm.qos > 0) {
> >      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
> >      > + uint32_t remaining_len = sizeof(id);
> >      > + if (c->is_mqtt5) remaining_len += 2; // 3.4.2
> >      > +
> >      > + mg_mqtt_send_header(
> >      > + c,
> >      > + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
> >      > + 0, remaining_len);
> >      > + mg_send(c, &id, sizeof(id));
> >      > +
> >      > + if (c->is_mqtt5) {
> >      > + uint16_t zero = 0;
> >      > + mg_send(c, &zero, sizeof(zero));
> >      > + }
> >      > + }
> >      > + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff
> >      > + break;
> >      > + }
> >      > + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable
> >     header rc
> >      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
> >      > + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1
> >      > + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
> >      > + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2
> >      > + break;
> >      > + }
> >      > + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable
> >     header rc
> >      > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
> >      > + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1
> >      > + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
> >      > + mg_send(c, &id, sizeof(id));
> >      > + break;
> >      > + }
> >      > + }
> >      > + mg_call(c, MG_EV_MQTT_CMD, &mm);
> >      > + mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> >      > + } else {
> >      > + break;
> >      > + }
> >      > + }
> >      > + }
> >      > + (void) ev_data;
> >      > +}
> >      > +
> >      > +void mg_mqtt_ping(struct mg_connection *nc) {
> >      > + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> >      > +}
> >      > +
> >      > +void mg_mqtt_pong(struct mg_connection *nc) {
> >      > + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> >      > +}
> >      > +
> >      > +void mg_mqtt_disconnect(struct mg_connection *c,
> >      > + const struct mg_mqtt_opts *opts) {
> >      > + size_t len = 0;
> >      > + if (c->is_mqtt5) len = 1 + get_props_size(opts->props,
> >     opts->num_props);
> >      > + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
> >      > +
> >      > + if (c->is_mqtt5) {
> >      > + uint8_t zero = 0;
> >      > + mg_send(c, &zero, sizeof(zero)); // reason code
> >      > + mg_send_mqtt_properties(c, opts->props, opts->num_props);
> >      > + }
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > + const struct mg_mqtt_opts *opts,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> >      > + if (c != NULL) {
> >      > + struct mg_mqtt_opts empty;
> >      > + memset(&empty, 0, sizeof(empty));
> >      > + mg_mqtt_login(c, opts == NULL ? &empty : opts);
> >      > + c->pfn = mqtt_cb;
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
> >     char *url,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> >      > + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> >      > + return c;
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/net.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +size_t mg_vprintf(struct mg_connection *c, const char *fmt,
> >     va_list *ap) {
> >      > + size_t old = c->send.len;
> >      > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> >      > + return c->send.len - old;
> >      > +}
> >      > +
> >      > +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> >      > + size_t len = 0;
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + len = mg_vprintf(c, fmt, &ap);
> >      > + va_end(ap);
> >      > + return len;
> >      > +}
> >      > +
> >      > +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> >      > + uint32_t localhost = mg_htonl(0x7f000001);
> >      > + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
> >      > + memcpy(addr->ip, &localhost, sizeof(uint32_t));
> >      > + addr->is_ip6 = false;
> >      > + return true;
> >      > +}
> >      > +
> >      > +static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> >      > + if (str.len > 0) return false;
> >      > + memset(addr->ip, 0, sizeof(addr->ip));
> >      > + addr->is_ip6 = false;
> >      > + return true;
> >      > +}
> >      > +
> >      > +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> >      > + uint8_t data[4] = {0, 0, 0, 0};
> >      > + size_t i, num_dots = 0;
> >      > + for (i = 0; i < str.len; i++) {
> >      > + if (str.buf[i] >= '0' && str.buf[i] <= '9') {
> >      > + int octet = data[num_dots] * 10 + (str.buf[i] - '0');
> >      > + if (octet > 255) return false;
> >      > + data[num_dots] = (uint8_t) octet;
> >      > + } else if (str.buf[i] == '.') {
> >      > + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return
> >     false;
> >      > + num_dots++;
> >      > + } else {
> >      > + return false;
> >      > + }
> >      > + }
> >      > + if (num_dots != 3 || str.buf[i - 1] == '.') return false;
> >      > + memcpy(&addr->ip, data, sizeof(data));
> >      > + addr->is_ip6 = false;
> >      > + return true;
> >      > +}
> >      > +
> >      > +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> >      > + int i;
> >      > + uint32_t ipv4;
> >      > + if (str.len < 14) return false;
> >      > + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] !=
> >     ':') return false;
> >      > + for (i = 2; i < 6; i++) {
> >      > + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
> >      > + }
> >      > + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
> >      > + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return
> >     false;
> >      > + memcpy(&ipv4, addr->ip, sizeof(ipv4));
> >      > + memset(addr->ip, 0, sizeof(addr->ip));
> >      > + addr->ip[10] = addr->ip[11] = 255;
> >      > + memcpy(&addr->ip[12], &ipv4, 4);
> >      > + addr->is_ip6 = true;
> >      > + return true;
> >      > +}
> >      > +
> >      > +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> >      > + size_t i, j = 0, n = 0, dc = 42;
> >      > + addr->scope_id = 0;
> >      > + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
> >      > + if (mg_v4mapped(str, addr)) return true;
> >      > + for (i = 0; i < str.len; i++) {
> >      > + if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
> >      > + (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
> >      > + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
> >      > + unsigned long val; // TODO(): This loops on chars, refactor
> >      > + if (i > j + 3) return false;
> >      > + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1),
> >     &str.buf[j]));
> >      > + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val,
> >     sizeof(val));
> >      > + addr->ip[n] = (uint8_t) ((val >> 8) & 255);
> >      > + addr->ip[n + 1] = (uint8_t) (val & 255);
> >      > + } else if (str.buf[i] == ':') {
> >      > + j = i + 1;
> >      > + if (i > 0 && str.buf[i - 1] == ':') {
> >      > + dc = n; // Double colon
> >      > + if (i > 1 && str.buf[i - 2] == ':') return false;
> >      > + } else if (i > 0) {
> >      > + n += 2;
> >      > + }
> >      > + if (n > 14) return false;
> >      > + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing ::
> >      > + } else if (str.buf[i] == '%') { // Scope ID, last in string
> >      > + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i -
> >     1), 10,
> >      > + &addr->scope_id, sizeof(uint8_t));
> >      > + } else {
> >      > + return false;
> >      > + }
> >      > + }
> >      > + if (n < 14 && dc == 42) return false;
> >      > + if (n < 14) {
> >      > + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
> >      > + memset(&addr->ip[dc], 0, 14 - n);
> >      > + }
> >      > +
> >      > + addr->is_ip6 = true;
> >      > + return true;
> >      > +}
> >      > +
> >      > +bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> >      > + // MG_INFO(("[%.*s]", (int) str.len, str.buf));
> >      > + return mg_atone(str, addr) || mg_atonl(str, addr) ||
> >     mg_aton4(str, addr) ||
> >      > + mg_aton6(str, addr);
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> >      > + struct mg_connection *c =
> >      > + (struct mg_connection *) calloc(1, sizeof(*c) +
> >     mgr->extraconnsize);
> >      > + if (c != NULL) {
> >      > + c->mgr = mgr;
> >      > + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
> >      > + c->id = ++mgr->nextid;
> >      > + MG_PROF_INIT(c);
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +void mg_close_conn(struct mg_connection *c) {
> >      > + mg_resolve_cancel(c); // Close any pending DNS query
> >      > + LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> >      > + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> >      > + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> >      > + // Order of operations is important. `MG_EV_CLOSE` event must
> >     be fired
> >      > + // before we deallocate received data, see #1331
> >      > + mg_call(c, MG_EV_CLOSE, NULL);
> >      > + MG_DEBUG(("%lu %ld closed", c->id, c->fd));
> >      > + MG_PROF_DUMP(c);
> >      > + MG_PROF_FREE(c);
> >      > +
> >      > + mg_tls_free(c);
> >      > + mg_iobuf_free(&c->recv);
> >      > + mg_iobuf_free(&c->send);
> >      > + mg_iobuf_free(&c->rtls);
> >      > + mg_bzero((unsigned char *) c, sizeof(*c));
> >      > + free(c);
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
> >     *url,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = NULL;
> >      > + if (url == NULL || url[0] == '\0') {
> >      > + MG_ERROR(("null url"));
> >      > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > + MG_ERROR(("OOM"));
> >      > + } else {
> >      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > + c->is_udp = (strncmp(url, "udp:", 4) == 0);
> >      > + c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> >      > + c->fn = fn;
> >      > + c->is_client = true;
> >      > + c->fn_data = fn_data;
> >      > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> >      > + mg_call(c, MG_EV_OPEN, (void *) url);
> >      > + mg_resolve(c, url);
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
> >     *url,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = NULL;
> >      > + if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > + MG_ERROR(("OOM %s", url));
> >      > + } else if (!mg_open_listener(c, url)) {
> >      > + MG_ERROR(("Failed: %s, errno %d", url, errno));
> >      > + MG_PROF_FREE(c);
> >      > + free(c);
> >      > + c = NULL;
> >      > + } else {
> >      > + c->is_listening = 1;
> >      > + c->is_udp = strncmp(url, "udp:", 4) == 0;
> >      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > + c->fn = fn;
> >      > + c->fn_data = fn_data;
> >      > + mg_call(c, MG_EV_OPEN, NULL);
> >      > + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must
> >      > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> >      > + mg_event_handler_t fn, void *fn_data) {
> >      > + struct mg_connection *c = mg_alloc_conn(mgr);
> >      > + if (c != NULL) {
> >      > + c->fd = (void *) (size_t) fd;
> >      > + c->fn = fn;
> >      > + c->fn_data = fn_data;
> >      > + MG_EPOLL_ADD(c);
> >      > + mg_call(c, MG_EV_OPEN, NULL);
> >      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
> >     milliseconds,
> >      > + unsigned flags, void (*fn)(void *), void *arg) {
> >      > + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> >      > + if (t != NULL) {
> >      > + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> >      > + t->id = mgr->timerid++;
> >      > + }
> >      > + return t;
> >      > +}
> >      > +
> >      > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > + if (c->rtls.len == 0) return MG_IO_WAIT;
> >      > + if (len > c->rtls.len) len = c->rtls.len;
> >      > + memcpy(buf, c->rtls.buf, len);
> >      > + mg_iobuf_del(&c->rtls, 0, len);
> >      > + return (long) len;
> >      > +}
> >      > +
> >      > +void mg_mgr_free(struct mg_mgr *mgr) {
> >      > + struct mg_connection *c;
> >      > + struct mg_timer *tmp, *t = mgr->timers;
> >      > + while (t != NULL) tmp = t->next, free(t), t = tmp;
> >      > + mgr->timers = NULL; // Important. Next call to poll won't touch
> >     timers
> >      > + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> >      > + mg_mgr_poll(mgr, 0);
> >      > +#if MG_ENABLE_FREERTOS_TCP
> >      > + FreeRTOS_DeleteSocketSet(mgr->ss);
> >      > +#endif
> >      > + MG_DEBUG(("All connections closed"));
> >      > +#if MG_ENABLE_EPOLL
> >      > + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> >      > +#endif
> >      > + mg_tls_ctx_free(mgr);
> >      > +}
> >      > +
> >      > +void mg_mgr_init(struct mg_mgr *mgr) {
> >      > + memset(mgr, 0, sizeof(*mgr));
> >      > +#if MG_ENABLE_EPOLL
> >      > + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
> >      > + MG_ERROR(("epoll_create1 errno %d", errno));
> >      > +#else
> >      > + mgr->epoll_fd = -1;
> >      > +#endif
> >      > +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > + // clang-format off
> >      > + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> >      > + // clang-format on
> >      > +#elif MG_ENABLE_FREERTOS_TCP
> >      > + mgr->ss = FreeRTOS_CreateSocketSet();
> >      > +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> >      > + // Ignore SIGPIPE signal, so if client cancels the request, it
> >      > + // won't kill the whole process.
> >      > + signal(SIGPIPE, SIG_IGN);
> >      > +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
> >      > + MG_TCPIP_DRIVER_INIT(mgr);
> >      > +#endif
> >      > + mgr->pipe = MG_INVALID_SOCKET;
> >      > + mgr->dnstimeout = 3000;
> >      > + mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
> >      > + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> >      > + mg_tls_ctx_init(mgr);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/net_builtin.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> >      > +#define MG_EPHEMERAL_PORT_BASE 32768
> >      > +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> >      > +
> >      > +#ifndef MIP_TCP_KEEPALIVE_MS
> >      > +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
> >      > +#endif
> >      > +
> >      > +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
> >      > +#define MIP_TCP_ARP_MS 100 // Timeout for ARP response
> >      > +#define MIP_TCP_SYN_MS 15000 // Timeout for connection
> >     establishment
> >      > +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection
> >      > +#define MIP_TCP_WIN 6000 // TCP window size
> >      > +
> >      > +struct connstate {
> >      > + uint32_t seq, ack; // TCP seq/ack counters
> >      > + uint64_t timer; // TCP keep-alive / ACK timer
> >      > + uint32_t acked; // Last ACK-ed number
> >      > + size_t unacked; // Not acked bytes
> >      > + uint8_t mac[6]; // Peer MAC address
> >      > + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
> >      > +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
> >     send keepalive
> >      > +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
> >     soon
> >      > +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response
> >      > +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
> >      > +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating
> >     the connection
> >      > + uint8_t tmiss; // Number of keep-alive misses
> >      > + struct mg_iobuf raw; // For TLS only. Incoming raw data
> >      > +};
> >      > +
> >      > +#pragma pack(push, 1)
> >      > +
> >      > +struct lcp {
> >      > + uint8_t addr, ctrl, proto[2], code, id, len[2];
> >      > +};
> >      > +
> >      > +struct eth {
> >      > + uint8_t dst[6]; // Destination MAC address
> >      > + uint8_t src[6]; // Source MAC address
> >      > + uint16_t type; // Ethernet type
> >      > +};
> >      > +
> >      > +struct ip {
> >      > + uint8_t ver; // Version
> >      > + uint8_t tos; // Unused
> >      > + uint16_t len; // Length
> >      > + uint16_t id; // Unused
> >      > + uint16_t frag; // Fragmentation
> >      > +#define IP_FRAG_OFFSET_MSK 0xFF1F
> >      > +#define IP_MORE_FRAGS_MSK 0x20
> >      > + uint8_t ttl; // Time to live
> >      > + uint8_t proto; // Upper level protocol
> >      > + uint16_t csum; // Checksum
> >      > + uint32_t src; // Source IP
> >      > + uint32_t dst; // Destination IP
> >      > +};
> >      > +
> >      > +struct ip6 {
> >      > + uint8_t ver; // Version
> >      > + uint8_t opts[3]; // Options
> >      > + uint16_t len; // Length
> >      > + uint8_t proto; // Upper level protocol
> >      > + uint8_t ttl; // Time to live
> >      > + uint8_t src[16]; // Source IP
> >      > + uint8_t dst[16]; // Destination IP
> >      > +};
> >      > +
> >      > +struct icmp {
> >      > + uint8_t type;
> >      > + uint8_t code;
> >      > + uint16_t csum;
> >      > +};
> >      > +
> >      > +struct arp {
> >      > + uint16_t fmt; // Format of hardware address
> >      > + uint16_t pro; // Format of protocol address
> >      > + uint8_t hlen; // Length of hardware address
> >      > + uint8_t plen; // Length of protocol address
> >      > + uint16_t op; // Operation
> >      > + uint8_t sha[6]; // Sender hardware address
> >      > + uint32_t spa; // Sender protocol address
> >      > + uint8_t tha[6]; // Target hardware address
> >      > + uint32_t tpa; // Target protocol address
> >      > +};
> >      > +
> >      > +struct tcp {
> >      > + uint16_t sport; // Source port
> >      > + uint16_t dport; // Destination port
> >      > + uint32_t seq; // Sequence number
> >      > + uint32_t ack; // Acknowledgement number
> >      > + uint8_t off; // Data offset
> >      > + uint8_t flags; // TCP flags
> >      > +#define TH_FIN 0x01
> >      > +#define TH_SYN 0x02
> >      > +#define TH_RST 0x04
> >      > +#define TH_PUSH 0x08
> >      > +#define TH_ACK 0x10
> >      > +#define TH_URG 0x20
> >      > +#define TH_ECE 0x40
> >      > +#define TH_CWR 0x80
> >      > + uint16_t win; // Window
> >      > + uint16_t csum; // Checksum
> >      > + uint16_t urp; // Urgent pointer
> >      > +};
> >      > +
> >      > +struct udp {
> >      > + uint16_t sport; // Source port
> >      > + uint16_t dport; // Destination port
> >      > + uint16_t len; // UDP length
> >      > + uint16_t csum; // UDP checksum
> >      > +};
> >      > +
> >      > +struct dhcp {
> >      > + uint8_t op, htype, hlen, hops;
> >      > + uint32_t xid;
> >      > + uint16_t secs, flags;
> >      > + uint32_t ciaddr, yiaddr, siaddr, giaddr;
> >      > + uint8_t hwaddr[208];
> >      > + uint32_t magic;
> >      > + uint8_t options[32];
> >      > +};
> >      > +
> >      > +#pragma pack(pop)
> >      > +
> >      > +struct pkt {
> >      > + struct mg_str raw; // Raw packet data
> >      > + struct mg_str pay; // Payload data
> >      > + struct eth *eth;
> >      > + struct llc *llc;
> >      > + struct arp *arp;
> >      > + struct ip *ip;
> >      > + struct ip6 *ip6;
> >      > + struct icmp *icmp;
> >      > + struct tcp *tcp;
> >      > + struct udp *udp;
> >      > + struct dhcp *dhcp;
> >      > +};
> >      > +
> >      > +static void send_syn(struct mg_connection *c);
> >      > +
> >      > +static void mkpay(struct pkt *pkt, void *p) {
> >      > + pkt->pay =
> >      > + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] -
> >     (char *) p));
> >      > +}
> >      > +
> >      > +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> >      > + size_t i;
> >      > + const uint8_t *p = (const uint8_t *) buf;
> >      > + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t)
> >     (p[i] << 8);
> >      > + return sum;
> >      > +}
> >      > +
> >      > +static uint16_t csumfin(uint32_t sum) {
> >      > + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> >      > + return mg_htons(~sum & 0xffff);
> >      > +}
> >      > +
> >      > +static uint16_t ipcsum(const void *buf, size_t len) {
> >      > + uint32_t sum = csumup(0, buf, len);
> >      > + return csumfin(sum);
> >      > +}
> >      > +
> >      > +static void settmout(struct mg_connection *c, uint8_t type) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS
> >      > + : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
> >      > + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
> >      > + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
> >      > + : MIP_TCP_KEEPALIVE_MS;
> >      > + s->timer = ifp->now + n;
> >      > + s->ttype = type;
> >      > + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> >      > +}
> >      > +
> >      > +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
> >      > + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
> >      > + if (n == len) ifp->nsent++;
> >      > + return n;
> >      > +}
> >      > +
> >      > +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
> >      > + struct eth *eth = (struct eth *) ifp->tx.buf;
> >      > + struct arp *arp = (struct arp *) (eth + 1);
> >      > + memset(eth->dst, 255, sizeof(eth->dst));
> >      > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
> >      > + eth->type = mg_htons(0x806);
> >      > + memset(arp, 0, sizeof(*arp));
> >      > + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> >      > + arp->plen = 4;
> >      > + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> >      > + memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> >      > + ether_output(ifp, PDIFF(eth, arp + 1));
> >      > +}
> >      > +
> >      > +static void onstatechange(struct mg_tcpip_if *ifp) {
> >      > + if (ifp->state == MG_TCPIP_STATE_READY) {
> >      > + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
> >      > + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
> >      > + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
> >      > + arp_ask(ifp, ifp->gw);
> >      > + } else if (ifp->state == MG_TCPIP_STATE_UP) {
> >      > + MG_ERROR(("Link up"));
> >      > + srand((unsigned int) mg_millis());
> >      > + } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
> >      > + MG_ERROR(("Link down"));
> >      > + }
> >      > +}
> >      > +
> >      > +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> >      > + uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
> >      > + size_t plen) {
> >      > + struct eth *eth = (struct eth *) ifp->tx.buf;
> >      > + struct ip *ip = (struct ip *) (eth + 1);
> >      > + memcpy(eth->dst, mac_dst, sizeof(eth->dst));
> >      > + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC
> >      > + eth->type = mg_htons(0x800);
> >      > + memset(ip, 0, sizeof(*ip));
> >      > + ip->ver = 0x45; // Version 4, header length 5 words
> >      > + ip->frag = 0x40; // Don't fragment
> >      > + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> >      > + ip->ttl = 64;
> >      > + ip->proto = proto;
> >      > + ip->src = ip_src;
> >      > + ip->dst = ip_dst;
> >      > + ip->csum = ipcsum(ip, sizeof(*ip));
> >      > + return ip;
> >      > +}
> >      > +
> >      > +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> >     uint32_t ip_src,
> >      > + uint16_t sport, uint32_t ip_dst, uint16_t dport,
> >      > + const void *buf, size_t len) {
> >      > + struct ip *ip =
> >      > + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
> >      > + struct udp *udp = (struct udp *) (ip + 1);
> >      > + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> >      > + udp->sport = sport;
> >      > + udp->dport = dport;
> >      > + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> >      > + udp->csum = 0;
> >      > + uint32_t cs = csumup(0, udp, sizeof(*udp));
> >      > + cs = csumup(cs, buf, len);
> >      > + cs = csumup(cs, &ip->src, sizeof(ip->src));
> >      > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> >      > + cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> >      > + udp->csum = csumfin(cs);
> >      > + memmove(udp + 1, buf, len);
> >      > + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> >      > + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
> >     sizeof(*udp) + len);
> >      > +}
> >      > +
> >      > +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
> >     uint32_t ip_src,
> >      > + uint32_t ip_dst, uint8_t *opts, size_t optslen,
> >      > + bool ciaddr) {
> >      > + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
> >     <https://datatracker.ietf.org/doc/html/rfc2132#section-9.6>
> >      > + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> >      > + dhcp.magic = mg_htonl(0x63825363);
> >      > + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> >      > + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> >      > + memcpy(&dhcp.options, opts, optslen);
> >      > + if (ciaddr) dhcp.ciaddr = ip_src;
> >      > + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst,
> >     mg_htons(67), &dhcp,
> >      > + sizeof(dhcp));
> >      > +}
> >      > +
> >      > +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> >      > +
> >      > +// RFC-2131 #4.3.6, #4.4.1
> >      > +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp,
> >     uint32_t ip_req,
> >      > + uint32_t ip_srv) {
> >      > + uint8_t opts[] = {
> >      > + 53, 1, 3, // Type: DHCP request
> >      > + 55, 2, 1, 3, // GW and mask
> >      > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
> >      > + 54, 4, 0, 0, 0, 0, // DHCP server ID
> >      > + 50, 4, 0, 0, 0, 0, // Requested IP
> >      > + 255 // End of options
> >      > + };
> >      > + memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
> >      > + memcpy(opts + 20, &ip_req, sizeof(ip_req));
> >      > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
> >     sizeof(opts), false);
> >      > + MG_DEBUG(("DHCP req sent"));
> >      > +}
> >      > +
> >      > +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
> >      > +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t
> >     *mac_dst,
> >      > + uint32_t ip_src, uint32_t ip_dst) {
> >      > + uint8_t opts[] = {
> >      > + 53, 1, 3, // Type: DHCP request
> >      > + 255 // End of options
> >      > + };
> >      > + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
> >      > + MG_DEBUG(("DHCP req sent"));
> >      > +}
> >      > +
> >      > +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
> >      > + uint8_t opts[] = {
> >      > + 53, 1, 1, // Type: DHCP discover
> >      > + 55, 2, 1, 3, // Parameters: ip, mask
> >      > + 255 // End of options
> >      > + };
> >      > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
> >     sizeof(opts), false);
> >      > + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac,
> >     ifp->mac));
> >      > +}
> >      > +
> >      > +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
> >     pkt *pkt,
> >      > + bool lsn) {
> >      > + struct mg_connection *c = NULL;
> >      > + for (c = mgr->conns; c != NULL; c = c->next) {
> >      > + if (c->is_arplooking && pkt->arp &&
> >      > + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
> >      > + break;
> >      > + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
> >     break;
> >      > + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> >      > + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> >      > + break;
> >      > + }
> >      > + return c;
> >      > +}
> >      > +
> >      > +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> >      > + // ARP request. Make a response, then send
> >      > + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op),
> >     mg_print_ip4,
> >      > + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
> >      > + struct eth *eth = (struct eth *) ifp->tx.buf;
> >      > + struct arp *arp = (struct arp *) (eth + 1);
> >      > + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> >      > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
> >      > + eth->type = mg_htons(0x806);
> >      > + *arp = *pkt->arp;
> >      > + arp->op = mg_htons(2);
> >      > + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> >      > + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> >      > + arp->tpa = pkt->arp->spa;
> >      > + arp->spa = ifp->ip;
> >      > + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa,
> >     mg_print_mac,
> >      > + &ifp->mac));
> >      > + ether_output(ifp, PDIFF(eth, arp + 1));
> >      > + } else if (pkt->arp->op == mg_htons(2)) {
> >      > + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
> >     0) return;
> >      > + if (pkt->arp->spa == ifp->gw) {
> >      > + // Got response for the GW ARP request. Set ifp->gwmac
> >      > + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
> >      > + } else {
> >      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> >      > + if (c != NULL && c->is_arplooking) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
> >      > + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4,
> >     c->rem.ip,
> >      > + mg_print_mac, s->mac));
> >      > + c->is_arplooking = 0;
> >      > + send_syn(c);
> >      > + settmout(c, MIP_TTYPE_SYN);
> >      > + }
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + // MG_DEBUG(("ICMP %d", (int) len));
> >      > + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
> >     ifp->ip) {
> >      > + size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
> >     sizeof(struct icmp);
> >      > + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> >      > + if (plen > space) plen = space;
> >      > + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip,
> >     pkt->ip->src,
> >      > + sizeof(struct icmp) + plen);
> >      > + struct icmp *icmp = (struct icmp *) (ip + 1);
> >      > + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
> >      > + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX
> >      > + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> >      > + ether_output(ifp, hlen + plen);
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt
> >     *pkt) {
> >      > + uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
> >      > + uint8_t msgtype = 0, state = ifp->state;
> >      > + // perform size check first, then access fields
> >      > + uint8_t *p = pkt->dhcp->options,
> >      > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> >      > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> >      > + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2,
> >     sizeof(pkt->dhcp->xid))) return;
> >      > + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9
> >      > + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
> >     Mask
> >      > + memcpy(&mask, p + 2, sizeof(mask));
> >      > + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
> >     { // GW
> >      > + memcpy(&gw, p + 2, sizeof(gw));
> >      > + ip = pkt->dhcp->yiaddr;
> >      > + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
> >      > + memcpy(&lease, p + 2, sizeof(lease));
> >      > + lease = mg_ntohl(lease);
> >      > + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type
> >      > + msgtype = p[2];
> >      > + }
> >      > + p += p[1] + 2;
> >      > + }
> >      > + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
> >      > + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP
> >      > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
> >      > + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP &&
> >     ip && gw &&
> >      > + lease) { // DHCPOFFER
> >      > + // select IP, (4.4.1) (fallback to IP source addr on foul play)
> >      > + tx_dhcp_request_sel(ifp, ip,
> >      > + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
> >      > + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
> >      > + } else if (msgtype == 5) { // DHCPACK
> >      > + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { //
> >     got an IP
> >      > + ifp->lease_expire = ifp->now + lease * 1000;
> >      > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
> >     1000));
> >      > + // assume DHCP server = router until ARP resolves
> >      > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> >      > + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> >      > + ifp->state = MG_TCPIP_STATE_READY; // BOUND state
> >      > + uint64_t rand;
> >      > + mg_random(&rand, sizeof(rand));
> >      > + srand((unsigned int) (rand + mg_millis()));
> >      > + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip)
> >     { // renew
> >      > + ifp->lease_expire = ifp->now + lease * 1000;
> >      > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
> >     1000));
> >      > + } // TODO(): accept provided T1/T2 and store server IP for
> >     renewal (4.4)
> >      > + }
> >      > + if (ifp->state != state) onstatechange(ifp);
> >      > +}
> >      > +
> >      > +// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> >      > +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt
> >     *pkt) {
> >      > + uint8_t op = 0, *p = pkt->dhcp->options,
> >      > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
> >      > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> >      > + // struct dhcp *req = pkt->dhcp;
> >      > + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> >      > + res.yiaddr = ifp->ip;
> >      > + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
> >      > + while (p + 1 < end && p[0] != 255) { // Parse options
> >      > + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
> >      > + op = p[2];
> >      > + }
> >      > + p += p[1] + 2;
> >      > + }
> >      > + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
> >      > + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
> >     DHCP ACK
> >      > + uint8_t opts[] = {
> >      > + 53, 1, msg, // Message type
> >      > + 1, 4, 0, 0, 0, 0, // Subnet mask
> >      > + 54, 4, 0, 0, 0, 0, // Server ID
> >      > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
> >      > + 51, 4, 255, 255, 255, 255, // Lease time
> >      > + 255 // End of options
> >      > + };
> >      > + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> >      > + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> >      > + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> >      > + memcpy(&res.options, opts, sizeof(opts));
> >      > + res.magic = pkt->dhcp->magic;
> >      > + res.xid = pkt->dhcp->xid;
> >      > + if (ifp->enable_get_gateway) {
> >      > + ifp->gw = res.yiaddr;
> >      > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
> >      > + }
> >      > + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
> >      > + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> >      > + if (c == NULL) {
> >      > + // No UDP listener on this port. Should send ICMP, but keep
> >     silent.
> >      > + } else {
> >      > + c->rem.port = pkt->udp->sport;
> >      > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> >      > + if (c->recv.len >= MG_MAX_RECV_SIZE) {
> >      > + mg_error(c, "max_recv_buf_size reached");
> >      > + } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> >      > + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> >      > + mg_error(c, "oom");
> >      > + } else {
> >      > + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
> >      > + c->recv.len += pkt->pay.len;
> >      > + mg_call(c, MG_EV_READ, &pkt->pay.len);
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac,
> >     uint32_t dst_ip,
> >      > + uint8_t flags, uint16_t sport, uint16_t dport,
> >      > + uint32_t seq, uint32_t ack, const void *buf, size_t len) {
> >      > +#if 0
> >      > + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460,
> >     SACK permitted
> >      > + if (flags & TH_SYN) {
> >      > + // Handshake? Set MSS
> >      > + buf = opts;
> >      > + len = sizeof(opts);
> >      > + }
> >      > +#endif
> >      > + struct ip *ip =
> >      > + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
> >      > + struct tcp *tcp = (struct tcp *) (ip + 1);
> >      > + memset(tcp, 0, sizeof(*tcp));
> >      > + if (buf != NULL && len) memmove(tcp + 1, buf, len);
> >      > + tcp->sport = sport;
> >      > + tcp->dport = dport;
> >      > + tcp->seq = seq;
> >      > + tcp->ack = ack;
> >      > + tcp->flags = flags;
> >      > + tcp->win = mg_htons(MIP_TCP_WIN);
> >      > + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> >      > + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header
> >     size 28 bytes
> >      > +
> >      > + uint32_t cs = 0;
> >      > + uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> >      > + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
> >     (n & 255)};
> >      > + cs = csumup(cs, tcp, n);
> >      > + cs = csumup(cs, &ip->src, sizeof(ip->src));
> >      > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> >      > + cs = csumup(cs, pseudo, sizeof(pseudo));
> >      > + tcp->csum = csumfin(cs);
> >      > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4,
> >     &ip->src,
> >      > + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
> >      > + mg_ntohs(tcp->dport), tcp->flags, len));
> >      > + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
> >      > + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
> >      > +}
> >      > +
> >      > +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
> >      > + uint8_t flags, uint32_t seq, const void *buf,
> >      > + size_t len) {
> >      > + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> >      > + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags,
> >     pkt->tcp->dport,
> >      > + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
> >      > + buf, len);
> >      > +}
> >      > +
> >      > +static struct mg_connection *accept_conn(struct mg_connection *lsn,
> >      > + struct pkt *pkt) {
> >      > + struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> >      > + if (c == NULL) {
> >      > + MG_ERROR(("OOM"));
> >      > + return NULL;
> >      > + }
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
> >     mg_ntohl(pkt->tcp->seq);
> >      > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
> >      > + settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
> >      > + c->rem.port = pkt->tcp->sport;
> >      > + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
> >      > + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> >      > + c->is_accepted = 1;
> >      > + c->is_hexdumping = lsn->is_hexdumping;
> >      > + c->pfn = lsn->pfn;
> >      > + c->loc = lsn->loc;
> >      > + c->pfn_data = lsn->pfn_data;
> >      > + c->fn = lsn->fn;
> >      > + c->fn_data = lsn->fn_data;
> >      > + mg_call(c, MG_EV_OPEN, NULL);
> >      > + mg_call(c, MG_EV_ACCEPT, NULL);
> >      > + return c;
> >      > +}
> >      > +
> >      > +static size_t trim_len(struct mg_connection *c, size_t len) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60,
> >     udp_h_len = 8;
> >      > + size_t max_headers_len =
> >      > + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len :
> >     tcp_max_h_len);
> >      > + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len
> >     - eth_h_len;
> >      > +
> >      > + // If the frame exceeds the available buffer, trim the length
> >      > + if (len + max_headers_len > ifp->tx.len) {
> >      > + len = ifp->tx.len - max_headers_len;
> >      > + }
> >      > + // Ensure the MTU isn't lower than the minimum allowed value
> >      > + if (ifp->mtu < min_mtu) {
> >      > + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
> >      > + ifp->mtu = (uint16_t) min_mtu;
> >      > + }
> >      > + // If the total packet size exceeds the MTU, trim the length
> >      > + if (len + max_headers_len - eth_h_len > ifp->mtu) {
> >      > + len = ifp->mtu - max_headers_len + eth_h_len;
> >      > + if (c->is_udp) {
> >      > + MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
> >      > + }
> >      > + }
> >      > +
> >      > + return len;
> >      > +}
> >      > +
> >      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + uint32_t dst_ip = *(uint32_t *) c->rem.ip;
> >      > + len = trim_len(c, len);
> >      > + if (c->is_udp) {
> >      > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port,
> >     buf, len);
> >      > + } else {
> >      > + size_t sent =
> >      > + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port,
> >     c->rem.port,
> >      > + mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
> >      > + if (sent == 0) {
> >      > + return MG_IO_WAIT;
> >      > + } else if (sent == (size_t) -1) {
> >      > + return MG_IO_ERR;
> >      > + } else {
> >      > + s->seq += (uint32_t) len;
> >      > + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > + }
> >      > + }
> >      > + return (long) len;
> >      > +}
> >      > +
> >      > +static void handle_tls_recv(struct mg_connection *c, struct
> >     mg_iobuf *io) {
> >      > + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> >      > + if (n == MG_IO_ERR) {
> >      > + mg_error(c, "TLS recv error");
> >      > + } else if (n > 0) {
> >      > + // Decrypted successfully - trigger MG_EV_READ
> >      > + io->len += (size_t) n;
> >      > + mg_call(c, MG_EV_READ, &n);
> >      > + }
> >      > +}
> >      > +
> >      > +static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
> >      > + uint32_t seq = mg_ntohl(pkt->tcp->seq);
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + if (pkt->tcp->flags & TH_FIN) {
> >      > + // If we initiated the closure, we reply with ACK upon
> >     receiving FIN
> >      > + // If we didn't initiate it, we reply with FIN as part of the
> >     normal TCP
> >      > + // closure process
> >      > + uint8_t flags = TH_ACK;
> >      > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
> >      > + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
> >      > + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
> >      > + s->seq++; // Yes. Increment our SEQ
> >      > + } else { // Otherwise,
> >      > + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
> >      > + }
> >      > + } else {
> >      > + flags |= TH_FIN;
> >      > + c->is_draining = 1;
> >      > + settmout(c, MIP_TTYPE_FIN);
> >      > + }
> >      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
> >      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
> >     "", 0);
> >      > + } else if (pkt->pay.len == 0) {
> >      > + // TODO(cpq): handle this peer's ACK
> >      > + } else if (seq != s->ack) {
> >      > + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) +
> >     pkt->pay.len);
> >      > + if (s->ack == ack) {
> >      > + MG_VERBOSE(("ignoring duplicate pkt"));
> >      > + } else {
> >      > + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
> >      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
> >     TH_ACK,
> >      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
> >      > + 0);
> >      > + }
> >      > + } else if (io->size - io->len < pkt->pay.len &&
> >      > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> >      > + mg_error(c, "oom");
> >      > + } else {
> >      > + // Copy TCP payload into the IO buffer. If the connection is
> >     plain text,
> >      > + // we copy to c->recv. If the connection is TLS, this data is
> >     encrypted,
> >      > + // therefore we copy that encrypted data to the c->rtls
> >     iobuffer instead,
> >      > + // and then call mg_tls_recv() to decrypt it. NOTE:
> >     mg_tls_recv() will
> >      > + // call back mg_io_recv() which grabs raw data from c->rtls
> >      > + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
> >      > + io->len += pkt->pay.len;
> >      > +
> >      > + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
> >     s->ack));
> >      > + // Advance ACK counter
> >      > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> >      > + s->unacked += pkt->pay.len;
> >      > + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
> >      > + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
> >      > + // Send ACK immediately
> >      > + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
> >      > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
> >     TH_ACK,
> >      > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
> >     NULL,
> >      > + 0);
> >      > + s->unacked = 0;
> >      > + s->acked = s->ack;
> >      > + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c,
> >     MIP_TTYPE_KEEPALIVE);
> >      > + } else {
> >      > + // if not already running, setup a timer to send an ACK later
> >      > + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> >      > + }
> >      > +
> >      > + if (c->is_tls && c->is_tls_hs) {
> >      > + mg_tls_handshake(c);
> >      > + } else if (c->is_tls) {
> >      > + // TLS connection. Make room for decrypted data in c->recv
> >      > + io = &c->recv;
> >      > + if (io->size - io->len < pkt->pay.len &&
> >      > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> >      > + mg_error(c, "oom");
> >      > + } else {
> >      > + // Decrypt data directly into c->recv
> >      > + handle_tls_recv(c, io);
> >      > + }
> >      > + } else {
> >      > + // Plain text connection, data is already in c->recv, trigger
> >      > + // MG_EV_READ
> >      > + mg_call(c, MG_EV_READ, &pkt->pay.len);
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> >      > + struct connstate *s = c == NULL ? NULL : (struct connstate *)
> >     (c + 1);
> >      > +#if 0
> >      > + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
> >     pkt->pay.len));
> >      > +#endif
> >      > + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN
> >     | TH_ACK)) {
> >      > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
> >     mg_ntohl(pkt->tcp->seq) + 1;
> >      > + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > + c->is_connecting = 0; // Client connected
> >      > + settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > + mg_call(c, MG_EV_CONNECT, NULL); // Let user know
> >      > + } else if (c != NULL && c->is_connecting && pkt->tcp->flags !=
> >     TH_ACK) {
> >      > + // mg_hexdump(pkt->raw.buf, pkt->raw.len);
> >      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > + } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> >      > + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> >      > + } else if (c != NULL) {
> >      > +#if 0
> >      > + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
> >      > + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> >      > + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> >      > + mg_hexdump(pkt->pay.buf, pkt->pay.len);
> >      > +#endif
> >      > + s->tmiss = 0; // Reset missed keep-alive counter
> >      > + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
> >      > + settmout(c,
> >      > + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
> >      > + read_conn(c, pkt); // Override timer with ACK timeout if needed
> >      > + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> >      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > + } else if (pkt->tcp->flags & TH_RST) {
> >      > + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> >      > + // ignore RST if not connected
> >      > + } else if (pkt->tcp->flags & TH_SYN) {
> >      > + // Use peer's source port as ISN, in order to recognise the
> >     handshake
> >      > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> >      > + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> >      > + } else if (pkt->tcp->flags & TH_FIN) {
> >      > + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
> >     + 1U) {
> >      > + accept_conn(c, pkt);
> >      > + } else if (!c->is_accepted) { // no peer
> >      > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > + } else {
> >      > + // MG_VERBOSE(("dropped silently.."));
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag &
> >     IP_FRAG_OFFSET_MSK) {
> >      > + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
> >      > + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
> >      > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> >      > + if (c) mg_error(c, "Received fragmented packet");
> >      > + } else if (pkt->ip->proto == 1) {
> >      > + pkt->icmp = (struct icmp *) (pkt->ip + 1);
> >      > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> >      > + mkpay(pkt, pkt->icmp + 1);
> >      > + rx_icmp(ifp, pkt);
> >      > + } else if (pkt->ip->proto == 17) {
> >      > + pkt->udp = (struct udp *) (pkt->ip + 1);
> >      > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
> >      > + mkpay(pkt, pkt->udp + 1);
> >      > + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4,
> >     &pkt->ip->src,
> >      > + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
> >      > + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
> >      > + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
> >      > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> >      > + mkpay(pkt, pkt->dhcp + 1);
> >      > + rx_dhcp_client(ifp, pkt);
> >      > + } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
> >     mg_htons(67)) {
> >      > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> >      > + mkpay(pkt, pkt->dhcp + 1);
> >      > + rx_dhcp_server(ifp, pkt);
> >      > + } else {
> >      > + rx_udp(ifp, pkt);
> >      > + }
> >      > + } else if (pkt->ip->proto == 6) {
> >      > + pkt->tcp = (struct tcp *) (pkt->ip + 1);
> >      > + if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> >      > + mkpay(pkt, pkt->tcp + 1);
> >      > + uint16_t iplen = mg_ntohs(pkt->ip->len);
> >      > + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
> >      >> 4) * 4U));
> >      > + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> >      > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4,
> >     &pkt->ip->src,
> >      > + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
> >      > + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
> >      > + rx_tcp(ifp, pkt);
> >      > + }
> >      > +}
> >      > +
> >      > +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
> >      > + // MG_DEBUG(("IP %d", (int) len));
> >      > + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> >      > + pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> >      > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> >      > + mkpay(pkt, pkt->icmp + 1);
> >      > + rx_icmp(ifp, pkt);
> >      > + } else if (pkt->ip6->proto == 17) {
> >      > + pkt->udp = (struct udp *) (pkt->ip6 + 1);
> >      > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
> >      > + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
> >      > + // mg_htons(udp->dport)));
> >      > + mkpay(pkt, pkt->udp + 1);
> >      > + }
> >      > +}
> >      > +
> >      > +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf,
> >     size_t len) {
> >      > + struct pkt pkt;
> >      > + memset(&pkt, 0, sizeof(pkt));
> >      > + pkt.raw.buf = (char *) buf;
> >      > + pkt.raw.len = len;
> >      > + pkt.eth = (struct eth *) buf;
> >      > + // mg_hexdump(buf, len > 16 ? 16: len);
> >      > + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
> >      > + if (ifp->enable_mac_check &&
> >      > + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> >      > + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
> >      > + return;
> >      > + if (ifp->enable_crc32_check && len > 4) {
> >      > + len -= 4; // TODO(scaprile): check on bigendian
> >      > + uint32_t crc = mg_crc32(0, (const char *) buf, len);
> >      > + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc)))
> >     return;
> >      > + }
> >      > + if (pkt.eth->type == mg_htons(0x806)) {
> >      > + pkt.arp = (struct arp *) (pkt.eth + 1);
> >      > + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
> >     // Truncated
> >      > + rx_arp(ifp, &pkt);
> >      > + } else if (pkt.eth->type == mg_htons(0x86dd)) {
> >      > + pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> >      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
> >     // Truncated
> >      > + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
> >      > + mkpay(&pkt, pkt.ip6 + 1);
> >      > + rx_ip6(ifp, &pkt);
> >      > + } else if (pkt.eth->type == mg_htons(0x800)) {
> >      > + pkt.ip = (struct ip *) (pkt.eth + 1);
> >      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
> >     // Truncated
> >      > + // Truncate frame to what IP header tells us
> >      > + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
> >     pkt.raw.len) {
> >      > + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> >      > + }
> >      > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
> >     // Truncated
> >      > + if ((pkt.ip->ver >> 4) != 4) return; // Not IP
> >      > + mkpay(&pkt, pkt.ip + 1);
> >      > + rx_ip(ifp, &pkt);
> >      > + } else {
> >      > + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
> >      > + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ?
> >     32 : len);
> >      > + }
> >      > +}
> >      > +
> >      > +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
> >      > + struct mg_connection *c;
> >      > + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
> >     1000, now);
> >      > + ifp->now = now;
> >      > +
> >      > +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> >      > + if (expired_1000ms) {
> >      > + const char *names[] = {"down", "up", "req", "ready"};
> >      > + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
> >      > + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
> >      > + ifp->ndrop, ifp->nerr));
> >      > + }
> >      > +#endif
> >      > + // Handle physical interface up/down status
> >      > + if (expired_1000ms && ifp->driver->up) {
> >      > + bool up = ifp->driver->up(ifp);
> >      > + bool current = ifp->state != MG_TCPIP_STATE_DOWN;
> >      > + if (up != current) {
> >      > + ifp->state = up == false ? MG_TCPIP_STATE_DOWN
> >      > + : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
> >      > + : MG_TCPIP_STATE_READY;
> >      > + if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> >      > + onstatechange(ifp);
> >      > + }
> >      > + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is
> >     down"));
> >      > + }
> >      > + if (ifp->state == MG_TCPIP_STATE_DOWN) return;
> >      > +
> >      > + // DHCP RFC-2131 (4.4)
> >      > + if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
> >      > + tx_dhcp_discover(ifp); // INIT (4.4.1)
> >      > + } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
> >      > + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
> >      > + if (ifp->now >= ifp->lease_expire) {
> >      > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired,
> >     release IP
> >      > + onstatechange(ifp);
> >      > + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
> >      > + ((ifp->now / 1000) % 60) == 0) {
> >      > + // hack: 30 min before deadline, try to rebind (4.3.6) every min
> >      > + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip,
> >     0xffffffff);
> >      > + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
> >      > + }
> >      > +
> >      > + // Read data from the network
> >      > + if (ifp->driver->rx != NULL) { // Polling driver. We must call it
> >      > + size_t len =
> >      > + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
> >      > + if (len > 0) {
> >      > + ifp->nrecv++;
> >      > + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
> >      > + }
> >      > + } else { // Interrupt-based driver. Fills recv queue itself
> >      > + char *buf;
> >      > + size_t len = mg_queue_next(&ifp->recv_queue, &buf);
> >      > + if (len > 0) {
> >      > + mg_tcpip_rx(ifp, buf, len);
> >      > + mg_queue_del(&ifp->recv_queue, len);
> >      > + }
> >      > + }
> >      > +
> >      > + // Process timeouts
> >      > + for (c = ifp->mgr->conns; c != NULL; c = c->next) {
> >      > + if (c->is_udp || c->is_listening || c->is_resolving) continue;
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + if (now > s->timer) {
> >      > + if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
> >      > + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
> >      > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> >      > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> >      > + s->acked = s->ack;
> >      > + } else if (s->ttype == MIP_TTYPE_ARP) {
> >      > + mg_error(c, "ARP timeout");
> >      > + } else if (s->ttype == MIP_TTYPE_SYN) {
> >      > + mg_error(c, "Connection timeout");
> >      > + } else if (s->ttype == MIP_TTYPE_FIN) {
> >      > + c->is_closing = 1;
> >      > + continue;
> >      > + } else {
> >      > + if (s->tmiss++ > 2) {
> >      > + mg_error(c, "keepalive");
> >      > + } else {
> >      > + MG_VERBOSE(("%lu keepalive", c->id));
> >      > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
> >      > + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
> >      > + }
> >      > + }
> >      > +
> >      > + settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +// This function executes in interrupt context, thus it should
> >     copy data
> >      > +// somewhere fast. Note that newlib's malloc is not thread safe,
> >     thus use
> >      > +// our lock-free queue with preallocated buffer to copy data and
> >     return asap
> >      > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
> >     *ifp) {
> >      > + char *p;
> >      > + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
> >      > + memcpy(p, buf, len);
> >      > + mg_queue_add(&ifp->recv_queue, len);
> >      > + ifp->nrecv++;
> >      > + } else {
> >      > + ifp->ndrop++;
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
> >      > + // If MAC address is not set, make a random one
> >      > + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
> >      > + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
> >      > + ifp->mac[0] = 0x02; // Locally administered, unicast
> >      > + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
> >      > + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac,
> >     ifp->mac));
> >      > + }
> >      > +
> >      > + if (ifp->driver->init && !ifp->driver->init(ifp)) {
> >      > + MG_ERROR(("driver init failed"));
> >      > + } else {
> >      > + size_t framesize = 1540;
> >      > + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len =
> >     framesize;
> >      > + if (ifp->recv_queue.size == 0)
> >      > + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
> >      > + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
> >      > + ifp->timer_1000ms = mg_millis();
> >      > + mgr->priv = ifp;
> >      > + ifp->mgr = mgr;
> >      > + ifp->mtu = MG_TCPIP_MTU_DEFAULT;
> >      > + mgr->extraconnsize = sizeof(struct connstate);
> >      > + if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> >      > + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
> >      > + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to
> >     65535
> >      > + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
> >      > + // MG_EPHEMERAL_PORT_BASE to 65535
> >      > + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL)
> >     MG_ERROR(("OOM"));
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_tcpip_free(struct mg_tcpip_if *ifp) {
> >      > + free(ifp->recv_queue.buf);
> >      > + free(ifp->tx.buf);
> >      > +}
> >      > +
> >      > +static void send_syn(struct mg_connection *c) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port,
> >     isn, 0, NULL,
> >      > + 0);
> >      > +}
> >      > +
> >      > +void mg_connect_resolved(struct mg_connection *c) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + c->is_resolving = 0;
> >      > + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport =
> >     MG_EPHEMERAL_PORT_BASE;
> >      > + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
> >      > + c->loc.port = mg_htons(ifp->eport++);
> >      > + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc,
> >     mg_print_ip_port,
> >      > + &c->rem));
> >      > + mg_call(c, MG_EV_RESOLVE, NULL);
> >      > + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip |
> >     ~ifp->mask))) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast
> >      > + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip &
> >     ifp->mask))) {
> >      > + // If we're in the same LAN, fire an ARP lookup.
> >      > + MG_DEBUG(("%lu ARP lookup...", c->id));
> >      > + arp_ask(ifp, rem_ip);
> >      > + settmout(c, MIP_TTYPE_ARP);
> >      > + c->is_arplooking = 1;
> >      > + c->is_connecting = 1;
> >      > + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
> >      > + struct connstate *s = (struct connstate *) (c + 1); // 224 to
> >     239, E0 to EF
> >      > + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group
> >      > + memcpy(s->mac, mcastp, 3);
> >      > + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb
> >      > + s->mac[3] &= 0x7F;
> >      > + } else {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
> >      > + if (c->is_udp) {
> >      > + mg_call(c, MG_EV_CONNECT, NULL);
> >      > + } else {
> >      > + send_syn(c);
> >      > + settmout(c, MIP_TTYPE_SYN);
> >      > + c->is_connecting = 1;
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +bool mg_open_listener(struct mg_connection *c, const char *url) {
> >      > + c->loc.port = mg_htons(mg_url_port(url));
> >      > + return true;
> >      > +}
> >      > +
> >      > +static void write_conn(struct mg_connection *c) {
> >      > + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> >      > + : mg_io_send(c, c->send.buf, c->send.len);
> >      > + if (len == MG_IO_ERR) {
> >      > + mg_error(c, "tx err");
> >      > + } else if (len > 0) {
> >      > + mg_iobuf_del(&c->send, 0, (size_t) len);
> >      > + mg_call(c, MG_EV_WRITE, &len);
> >      > + }
> >      > +}
> >      > +
> >      > +static void init_closure(struct mg_connection *c) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + if (c->is_udp == false && c->is_listening == false &&
> >      > + c->is_connecting == false) { // For TCP conns,
> >      > + struct mg_tcpip_if *ifp =
> >      > + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port,
> >     c->rem.port,
> >      > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> >      > + settmout(c, MIP_TTYPE_FIN);
> >      > + }
> >      > +}
> >      > +
> >      > +static void close_conn(struct mg_connection *c) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + mg_iobuf_free(&s->raw); // For TLS connections, release raw data
> >      > + mg_close_conn(c);
> >      > +}
> >      > +
> >      > +static bool can_write(struct mg_connection *c) {
> >      > + return c->is_connecting == 0 && c->is_resolving == 0 &&
> >     c->send.len > 0 &&
> >      > + c->is_tls_hs == 0 && c->is_arplooking == 0;
> >      > +}
> >      > +
> >      > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
> >      > + struct mg_connection *c, *tmp;
> >      > + uint64_t now = mg_millis();
> >      > + mg_timer_poll(&mgr->timers, now);
> >      > + if (ifp == NULL || ifp->driver == NULL) return;
> >      > + mg_tcpip_poll(ifp, now);
> >      > + for (c = mgr->conns; c != NULL; c = tmp) {
> >      > + tmp = c->next;
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + mg_call(c, MG_EV_POLL, &now);
> >      > + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> >      > + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> >      > + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> >      > + if (c->is_tls && mg_tls_pending(c) > 0)
> >      > + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
> >      > + if (can_write(c)) write_conn(c);
> >      > + if (c->is_draining && c->send.len == 0 && s->ttype !=
> >     MIP_TTYPE_FIN)
> >      > + init_closure(c);
> >      > + if (c->is_closing) close_conn(c);
> >      > + }
> >      > + (void) ms;
> >      > +}
> >      > +
> >      > +bool mg_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
> >      > + bool res = false;
> >      > + uint32_t rem_ip;
> >      > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
> >      > + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
> >      > + mg_error(c, "net down");
> >      > + } else if (c->is_udp) {
> >      > + struct connstate *s = (struct connstate *) (c + 1);
> >      > + len = trim_len(c, len); // Trimming length if necessary
> >      > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port,
> >     buf, len);
> >      > + res = true;
> >      > + } else {
> >      > + res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> >      > + }
> >      > + return res;
> >      > +}
> >      > +#endif // MG_ENABLE_TCPIP
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/ota_dummy.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +#if MG_OTA == MG_OTA_NONE
> >      > +bool mg_ota_begin(size_t new_firmware_size) {
> >      > + (void) new_firmware_size;
> >      > + return true;
> >      > +}
> >      > +bool mg_ota_write(const void *buf, size_t len) {
> >      > + (void) buf, (void) len;
> >      > + return true;
> >      > +}
> >      > +bool mg_ota_end(void) {
> >      > + return true;
> >      > +}
> >      > +bool mg_ota_commit(void) {
> >      > + return true;
> >      > +}
> >      > +bool mg_ota_rollback(void) {
> >      > + return true;
> >      > +}
> >      > +int mg_ota_status(int fw) {
> >      > + (void) fw;
> >      > + return 0;
> >      > +}
> >      > +uint32_t mg_ota_crc32(int fw) {
> >      > + (void) fw;
> >      > + return 0;
> >      > +}
> >      > +uint32_t mg_ota_timestamp(int fw) {
> >      > + (void) fw;
> >      > + return 0;
> >      > +}
> >      > +size_t mg_ota_size(int fw) {
> >      > + (void) fw;
> >      > + return 0;
> >      > +}
> >      > +MG_IRAM void mg_ota_boot(void) {
> >      > +}
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/ota_esp32.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
> >      > +
> >      > +static const esp_partition_t *s_ota_update_partition;
> >      > +static esp_ota_handle_t s_ota_update_handle;
> >      > +static bool s_ota_success;
> >      > +
> >      > +// Those empty macros do nothing, but mark places in the code
> >     which could
> >      > +// potentially trigger a watchdog reboot due to the log flash
> >     erase operation
> >      > +#define disable_wdt()
> >      > +#define enable_wdt()
> >      > +
> >      > +bool mg_ota_begin(size_t new_firmware_size) {
> >      > + if (s_ota_update_partition != NULL) {
> >      > + MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
> >      > + return false;
> >      > + } else {
> >      > + s_ota_success = false;
> >      > + disable_wdt();
> >      > + s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
> >      > + esp_err_t err = esp_ota_begin(s_ota_update_partition,
> >     new_firmware_size,
> >      > + &s_ota_update_handle);
> >      > + enable_wdt();
> >      > + MG_DEBUG(("esp_ota_begin(): %d", err));
> >      > + s_ota_success = (err == ESP_OK);
> >      > + }
> >      > + return s_ota_success;
> >      > +}
> >      > +
> >      > +bool mg_ota_write(const void *buf, size_t len) {
> >      > + disable_wdt();
> >      > + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
> >      > + enable_wdt();
> >      > + MG_INFO(("esp_ota_write(): %d", err));
> >      > + s_ota_success = err == ESP_OK;
> >      > + return s_ota_success;
> >      > +}
> >      > +
> >      > +bool mg_ota_end(void) {
> >      > + esp_err_t err = esp_ota_end(s_ota_update_handle);
> >      > + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
> >      > + if (s_ota_success && err == ESP_OK) {
> >      > + err = esp_ota_set_boot_partition(s_ota_update_partition);
> >      > + s_ota_success = (err == ESP_OK);
> >      > + }
> >      > + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
> >      > + s_ota_update_partition = NULL;
> >      > + return s_ota_success;
> >      > +}
> >      > +
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/ota_flash.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +// This OTA implementation uses the internal flash API outlined
> >     in device.h
> >      > +// It splits flash into 2 equal partitions, and stores OTA
> >     status in the
> >      > +// last sector of the partition.
> >      > +
> >      > +#if MG_OTA == MG_OTA_FLASH
> >      > +
> >      > +#define MG_OTADATA_KEY 0xb07afed0
> >      > +
> >      > +static char *s_addr; // Current address to write to
> >      > +static size_t s_size; // Firmware size to flash. In-progress
> >     indicator
> >      > +static uint32_t s_crc32; // Firmware checksum
> >      > +
> >      > +struct mg_otadata {
> >      > + uint32_t crc32, size, timestamp, status;
> >      > +};
> >      > +
> >      > +bool mg_ota_begin(size_t new_firmware_size) {
> >      > + bool ok = false;
> >      > + if (s_size) {
> >      > + MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
> >      > + } else {
> >      > + size_t half = mg_flash_size() / 2, max = half -
> >     mg_flash_sector_size();
> >      > + s_crc32 = 0;
> >      > + s_addr = (char *) mg_flash_start() + half;
> >      > + MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
> >      > + if (new_firmware_size < max) {
> >      > + ok = true;
> >      > + s_size = new_firmware_size;
> >      > + MG_INFO(("Starting OTA, firmware size %lu", s_size));
> >      > + } else {
> >      > + MG_ERROR(("Firmware %lu is too big to fit %lu",
> >     new_firmware_size, max));
> >      > + }
> >      > + }
> >      > + return ok;
> >      > +}
> >      > +
> >      > +bool mg_ota_write(const void *buf, size_t len) {
> >      > + bool ok = false;
> >      > + if (s_size == 0) {
> >      > + MG_ERROR(("OTA is not started, call mg_ota_begin()"));
> >      > + } else {
> >      > + size_t align = mg_flash_write_align();
> >      > + size_t len_aligned_down = MG_ROUND_DOWN(len, align);
> >      > + if (len_aligned_down) ok = mg_flash_write(s_addr, buf,
> >     len_aligned_down);
> >      > + if (len_aligned_down < len) {
> >      > + size_t left = len - len_aligned_down;
> >      > + char tmp[align];
> >      > + memset(tmp, 0xff, sizeof(tmp));
> >      > + memcpy(tmp, (char *) buf + len_aligned_down, left);
> >      > + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
> >      > + }
> >      > + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
> >      > + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
> >      > + s_addr += len;
> >      > + }
> >      > + return ok;
> >      > +}
> >      > +
> >      > +MG_IRAM static uint32_t mg_fwkey(int fw) {
> >      > + uint32_t key = MG_OTADATA_KEY + fw;
> >      > + int bank = mg_flash_bank();
> >      > + if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
> >      > + if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
> >      > + return key;
> >      > +}
> >      > +
> >      > +bool mg_ota_end(void) {
> >      > + char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
> >      > + bool ok = false;
> >      > + if (s_size) {
> >      > + size_t size = s_addr - base;
> >      > + uint32_t crc32 = mg_crc32(0, base, s_size);
> >      > + if (size == s_size && crc32 == s_crc32) {
> >      > + uint32_t now = (uint32_t) (mg_now() / 1000);
> >      > + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
> >      > + uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
> >      > + ok = mg_flash_save(NULL, key, &od, sizeof(od));
> >      > + }
> >      > + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32,
> >     crc32, s_size,
> >      > + size, ok ? "ok" : "fail"));
> >      > + s_size = 0;
> >      > + if (ok) ok = mg_flash_swap_bank();
> >      > + }
> >      > + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
> >      > + return ok;
> >      > +}
> >      > +
> >      > +MG_IRAM static struct mg_otadata mg_otadata(int fw) {
> >      > + uint32_t key = mg_fwkey(fw);
> >      > + struct mg_otadata od = {};
> >      > + MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ?
> >     "curr" : "prev"));
> >      > + mg_flash_load(NULL, key, &od, sizeof(od));
> >      > + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw,
> >     bank, key));
> >      > + // mg_hexdump(&od, sizeof(od));
> >      > + return od;
> >      > +}
> >      > +
> >      > +int mg_ota_status(int fw) {
> >      > + struct mg_otadata od = mg_otadata(fw);
> >      > + return od.status;
> >      > +}
> >      > +uint32_t mg_ota_crc32(int fw) {
> >      > + struct mg_otadata od = mg_otadata(fw);
> >      > + return od.crc32;
> >      > +}
> >      > +uint32_t mg_ota_timestamp(int fw) {
> >      > + struct mg_otadata od = mg_otadata(fw);
> >      > + return od.timestamp;
> >      > +}
> >      > +size_t mg_ota_size(int fw) {
> >      > + struct mg_otadata od = mg_otadata(fw);
> >      > + return od.size;
> >      > +}
> >      > +
> >      > +MG_IRAM bool mg_ota_commit(void) {
> >      > + bool ok = true;
> >      > + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
> >      > + if (od.status != MG_OTA_COMMITTED) {
> >      > + od.status = MG_OTA_COMMITTED;
> >      > + MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
> >      > + ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od,
> >     sizeof(od));
> >      > + }
> >      > + return ok;
> >      > +}
> >      > +
> >      > +bool mg_ota_rollback(void) {
> >      > + MG_DEBUG(("Rolling firmware back"));
> >      > + if (mg_flash_bank() == 0) {
> >      > + // No dual bank support. Mark previous firmware as FIRST_BOOT
> >      > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> >      > + prev.status = MG_OTA_FIRST_BOOT;
> >      > + return mg_flash_save(NULL, MG_OTADATA_KEY +
> >     MG_FIRMWARE_PREVIOUS, &prev,
> >      > + sizeof(prev));
> >      > + } else {
> >      > + return mg_flash_swap_bank();
> >      > + }
> >      > +}
> >      > +
> >      > +MG_IRAM void mg_ota_boot(void) {
> >      > + MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
> >      > + struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
> >      > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
> >      > +
> >      > + if (curr.status == MG_OTA_FIRST_BOOT) {
> >      > + if (prev.status == MG_OTA_UNAVAILABLE) {
> >      > + MG_INFO(("Setting previous firmware state to committed"));
> >      > + prev.status = MG_OTA_COMMITTED;
> >      > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev,
> >     sizeof(prev));
> >      > + }
> >      > + curr.status = MG_OTA_UNCOMMITTED;
> >      > + MG_INFO(("First boot, setting status to UNCOMMITTED"));
> >      > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr,
> >     sizeof(curr));
> >      > + } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank()
> >     == 0) {
> >      > + // Swap paritions. Pray power does not disappear
> >      > + size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
> >      > + char *partition1 = mg_flash_start();
> >      > + char *partition2 = mg_flash_start() + fs / 2;
> >      > + size_t ofs, max = fs / 2 - ss; // Set swap size to the whole
> >     partition
> >      > +
> >      > + if (curr.status != MG_OTA_UNAVAILABLE &&
> >      > + prev.status != MG_OTA_UNAVAILABLE) {
> >      > + // We know exact sizes of both firmwares.
> >      > + // Shrink swap size to the MAX(firmware1, firmware2)
> >      > + size_t sz = curr.size > prev.size ? curr.size : prev.size;
> >      > + if (sz > 0 && sz < max) max = sz;
> >      > + }
> >      > +
> >      > + // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
> >      > + prev.status = MG_OTA_UNCOMMITTED;
> >      > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
> >      > + sizeof(prev));
> >      > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
> >      > + sizeof(curr));
> >      > +
> >      > + MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max
> >     / ss));
> >      > + MG_INFO(("Do NOT power off..."));
> >      > + mg_log_level = MG_LL_NONE;
> >      > +
> >      > + // We use the last sector of partition2 for OTA data/config
> >     storage
> >      > + // Therefore we can use last sector of partition1 for swapping
> >      > + char *tmpsector = partition1 + fs / 2 - ss; // Last sector of
> >     partition1
> >      > + (void) tmpsector;
> >      > + for (ofs = 0; ofs < max; ofs += ss) {
> >      > + // mg_flash_erase(tmpsector);
> >      > + mg_flash_write(tmpsector, partition1 + ofs, ss);
> >      > + // mg_flash_erase(partition1 + ofs);
> >      > + mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
> >      > + // mg_flash_erase(partition2 + ofs);
> >      > + mg_flash_write(partition2 + ofs, tmpsector, ss);
> >      > + }
> >      > + mg_device_reset();
> >      > + }
> >      > +}
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/printf.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt,
> >     va_list *ap) {
> >      > + size_t len = mg_snprintf(NULL, 0, fmt, ap);
> >      > + char *buf;
> >      > + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
> >      > + len = 0; // Nah. Not enough space
> >      > + } else {
> >      > + len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
> >      > + mg_queue_add(q, len);
> >      > + }
> >      > + return len;
> >      > +}
> >      > +
> >      > +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
> >      > + va_list ap;
> >      > + size_t len;
> >      > + va_start(ap, fmt);
> >      > + len = mg_queue_vprintf(q, fmt, &ap);
> >      > + va_end(ap);
> >      > + return len;
> >      > +}
> >      > +
> >      > +static void mg_pfn_iobuf_private(char ch, void *param, bool
> >     expand) {
> >      > + struct mg_iobuf *io = (struct mg_iobuf *) param;
> >      > + if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
> >     io->len + 2);
> >      > + if (io->len + 2 <= io->size) {
> >      > + io->buf[io->len++] = (uint8_t) ch;
> >      > + io->buf[io->len] = 0;
> >      > + } else if (io->len < io->size) {
> >      > + io->buf[io->len++] = 0; // Guarantee to 0-terminate
> >      > + }
> >      > +}
> >      > +
> >      > +static void mg_putchar_iobuf_static(char ch, void *param) {
> >      > + mg_pfn_iobuf_private(ch, param, false);
> >      > +}
> >      > +
> >      > +void mg_pfn_iobuf(char ch, void *param) {
> >      > + mg_pfn_iobuf_private(ch, param, true);
> >      > +}
> >      > +
> >      > +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
> >     va_list *ap) {
> >      > + struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
> >      > + size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
> >      > + if (n < len) buf[n] = '\0';
> >      > + return n;
> >      > +}
> >      > +
> >      > +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
> >      > + va_list ap;
> >      > + size_t n;
> >      > + va_start(ap, fmt);
> >      > + n = mg_vsnprintf(buf, len, fmt, &ap);
> >      > + va_end(ap);
> >      > + return n;
> >      > +}
> >      > +
> >      > +char *mg_vmprintf(const char *fmt, va_list *ap) {
> >      > + struct mg_iobuf io = {0, 0, 0, 256};
> >      > + mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
> >      > + return (char *) io.buf;
> >      > +}
> >      > +
> >      > +char *mg_mprintf(const char *fmt, ...) {
> >      > + char *s;
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + s = mg_vmprintf(fmt, &ap);
> >      > + va_end(ap);
> >      > + return s;
> >      > +}
> >      > +
> >      > +void mg_pfn_stdout(char c, void *param) {
> >      > + putchar(c);
> >      > + (void) param;
> >      > +}
> >      > +
> >      > +static size_t print_ip4(void (*out)(char, void *), void *arg,
> >     uint8_t *p) {
> >      > + return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2],
> >     p[3]);
> >      > +}
> >      > +
> >      > +static size_t print_ip6(void (*out)(char, void *), void *arg,
> >     uint16_t *p) {
> >      > + return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]",
> >     mg_ntohs(p[0]),
> >      > + mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
> >      > + mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
> >      > + mg_ntohs(p[7]));
> >      > +}
> >      > +
> >      > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + uint8_t *p = va_arg(*ap, uint8_t *);
> >      > + return print_ip4(out, arg, p);
> >      > +}
> >      > +
> >      > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + uint16_t *p = va_arg(*ap, uint16_t *);
> >      > + return print_ip6(out, arg, p);
> >      > +}
> >      > +
> >      > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
> >     *ap) {
> >      > + struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
> >      > + if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *)
> >     addr->ip);
> >      > + return print_ip4(out, arg, (uint8_t *) &addr->ip);
> >      > +}
> >      > +
> >      > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + struct mg_addr *a = va_arg(*ap, struct mg_addr *);
> >      > + return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a,
> >     mg_ntohs(a->port));
> >      > +}
> >      > +
> >      > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + uint8_t *p = va_arg(*ap, uint8_t *);
> >      > + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x",
> >     p[0], p[1], p[2],
> >      > + p[3], p[4], p[5]);
> >      > +}
> >      > +
> >      > +static char mg_esc(int c, bool esc) {
> >      > + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
> >      > + for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
> >      > + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
> >      > + }
> >      > + return 0;
> >      > +}
> >      > +
> >      > +static char mg_escape(int c) {
> >      > + return mg_esc(c, true);
> >      > +}
> >      > +
> >      > +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
> >      > + size_t len) {
> >      > + size_t i = 0, extra = 0;
> >      > + for (i = 0; i < len && buf[i] != '\0'; i++) {
> >      > + char c = mg_escape(buf[i]);
> >      > + if (c) {
> >      > + out('\\', ptr), out(c, ptr), extra++;
> >      > + } else {
> >      > + out(buf[i], ptr);
> >      > + }
> >      > + }
> >      > + return i + extra;
> >      > +}
> >      > +
> >      > +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t
> >     *buf,
> >      > + size_t len) {
> >      > + size_t i, j, n = 0;
> >      > + const char *t =
> >      > +
> >     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> >      > + for (i = 0; i < len; i += 3) {
> >      > + uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
> >      > + c3 = i + 2 < len ? buf[i + 2] : 0;
> >      > + char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
> >     '='};
> >      > + if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
> >      > + if (i + 2 < len) tmp[3] = t[c3 & 63];
> >      > + for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j],
> >     arg);
> >      > + n += j;
> >      > + }
> >      > + return n;
> >      > +}
> >      > +
> >      > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + size_t bl = (size_t) va_arg(*ap, int);
> >      > + uint8_t *p = va_arg(*ap, uint8_t *);
> >      > + const char *hex = "0123456789abcdef";
> >      > + size_t j;
> >      > + for (j = 0; j < bl; j++) {
> >      > + out(hex[(p[j] >> 4) & 0x0F], arg);
> >      > + out(hex[p[j] & 0x0F], arg);
> >      > + }
> >      > + return 2 * bl;
> >      > +}
> >      > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + size_t len = (size_t) va_arg(*ap, int);
> >      > + uint8_t *buf = va_arg(*ap, uint8_t *);
> >      > + return bcpy(out, arg, buf, len);
> >      > +}
> >      > +
> >      > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
> >     va_list *ap) {
> >      > + size_t len = (size_t) va_arg(*ap, int);
> >      > + char *p = va_arg(*ap, char *);
> >      > + if (len == 0) len = p == NULL ? 0 : strlen(p);
> >      > + return qcpy(out, arg, p, len);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/queue.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +#if (defined(__GNUC__) && (__GNUC__ > 4) || \
> >      > + (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >=
> >     1)) || \
> >      > + defined(__clang__)
> >      > +#define MG_MEMORY_BARRIER() __sync_synchronize()
> >      > +#elif defined(_MSC_VER) && _MSC_VER >= 1700
> >      > +#define MG_MEMORY_BARRIER() MemoryBarrier()
> >      > +#elif !defined(MG_MEMORY_BARRIER)
> >      > +#define MG_MEMORY_BARRIER()
> >      > +#endif
> >      > +
> >      > +// Every message in a queue is prepended by a 32-bit message
> >     length (ML).
> >      > +// If ML is 0, then it is the end, and reader must wrap to the
> >     beginning.
> >      > +//
> >      > +// Queue when q->tail <= q->head:
> >      > +// |----- free -----| ML | message1 | ML | message2 | ----- free
> >     ------|
> >      > +// ^ ^ ^ ^
> >      > +// buf tail head len
> >      > +//
> >      > +// Queue when q->tail > q->head:
> >      > +// | ML | message2 |----- free ------| ML | message1 | 0 |----
> >     free ----|
> >      > +// ^ ^ ^ ^
> >      > +// buf head tail len
> >      > +
> >      > +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
> >      > + q->size = size;
> >      > + q->buf = buf;
> >      > + q->head = q->tail = 0;
> >      > +}
> >      > +
> >      > +static size_t mg_queue_read_len(struct mg_queue *q) {
> >      > + uint32_t n = 0;
> >      > + MG_MEMORY_BARRIER();
> >      > + memcpy(&n, q->buf + q->tail, sizeof(n));
> >      > + assert(q->tail + n + sizeof(n) <= q->size);
> >      > + return n;
> >      > +}
> >      > +
> >      > +static void mg_queue_write_len(struct mg_queue *q, size_t len) {
> >      > + uint32_t n = (uint32_t) len;
> >      > + memcpy(q->buf + q->head, &n, sizeof(n));
> >      > + MG_MEMORY_BARRIER();
> >      > +}
> >      > +
> >      > +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
> >      > + size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0
> >     marker
> >      > + if (q->head >= q->tail && q->head + len + hs <= q->size) {
> >      > + space = q->size - q->head - hs; // There is enough space
> >      > + } else if (q->head >= q->tail && q->tail > hs) {
> >      > + mg_queue_write_len(q, 0); // Not enough space ahead
> >      > + q->head = 0; // Wrap head to the beginning
> >      > + }
> >      > + if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
> >      > + if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
> >      > + return space;
> >      > +}
> >      > +
> >      > +size_t mg_queue_next(struct mg_queue *q, char **buf) {
> >      > + size_t len = 0;
> >      > + if (q->tail != q->head) {
> >      > + len = mg_queue_read_len(q);
> >      > + if (len == 0) { // Zero (head wrapped) ?
> >      > + q->tail = 0; // Reset tail to the start
> >      > + if (q->head > q->tail) len = mg_queue_read_len(q); // Read again
> >      > + }
> >      > + }
> >      > + if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
> >      > + assert(q->tail + len <= q->size);
> >      > + return len;
> >      > +}
> >      > +
> >      > +void mg_queue_add(struct mg_queue *q, size_t len) {
> >      > + assert(len > 0);
> >      > + mg_queue_write_len(q, len);
> >      > + assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
> >      > + q->head += len + sizeof(uint32_t);
> >      > +}
> >      > +
> >      > +void mg_queue_del(struct mg_queue *q, size_t len) {
> >      > + q->tail += len + sizeof(uint32_t);
> >      > + assert(q->tail + sizeof(uint32_t) <= q->size);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/rpc.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> >      > + void (*fn)(struct mg_rpc_req *), void *fn_data) {
> >      > + struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> >      > + if (rpc != NULL) {
> >      > + rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
> >      > + rpc->method.len = method.len;
> >      > + rpc->fn = fn;
> >      > + rpc->fn_data = fn_data;
> >      > + rpc->next = *head, *head = rpc;
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
> >     mg_rpc_req *)) {
> >      > + struct mg_rpc *r;
> >      > + while ((r = *head) != NULL) {
> >      > + if (r->fn == fn || fn == NULL) {
> >      > + *head = r->next;
> >      > + free((void *) r->method.buf);
> >      > + free(r);
> >      > + } else {
> >      > + head = &(*head)->next;
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
> >     method) {
> >      > + struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> >      > + while (h != NULL && !mg_match(method, h->method, NULL)) h =
> >     h->next;
> >      > + if (h != NULL) {
> >      > + r->rpc = h;
> >      > + h->fn(r);
> >      > + } else {
> >      > + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
> >     method.buf);
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_rpc_process(struct mg_rpc_req *r) {
> >      > + int len, off = mg_json_get(r->frame, "$.method", &len);
> >      > + if (off > 0 && r->frame.buf[off] == '"') {
> >      > + struct mg_str method = mg_str_n(&r->frame.buf[off + 1],
> >     (size_t) len - 2);
> >      > + mg_rpc_call(r, method);
> >      > + } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> >      > + (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> >      > + mg_rpc_call(r, mg_str("")); // JSON response! call "" method
> >     handler
> >      > + } else {
> >      > + mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
> >      > + r->frame.buf); // Invalid
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
> >     *ap) {
> >      > + int len, off = mg_json_get(r->frame, "$.id", &len);
> >      > + if (off > 0) {
> >      > + mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc,
> >     0, "id", len,
> >      > + &r->frame.buf[off], mg_print_esc, 0, "result");
> >      > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> >      > + mg_xprintf(r->pfn, r->pfn_data, "}");
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + mg_rpc_vok(r, fmt, &ap);
> >      > + va_end(ap);
> >      > +}
> >      > +
> >      > +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
> >     *fmt, va_list *ap) {
> >      > + int len, off = mg_json_get(r->frame, "$.id", &len);
> >      > + mg_xprintf(r->pfn, r->pfn_data, "{");
> >      > + if (off > 0) {
> >      > + mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0,
> >     "id", len,
> >      > + &r->frame.buf[off]);
> >      > + }
> >      > + mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc,
> >     0, "error",
> >      > + mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
> >      > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> >      > + mg_xprintf(r->pfn, r->pfn_data, "}}");
> >      > +}
> >      > +
> >      > +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
> >     ...) {
> >      > + va_list ap;
> >      > + va_start(ap, fmt);
> >      > + mg_rpc_verr(r, code, fmt, &ap);
> >      > + va_end(ap);
> >      > +}
> >      > +
> >      > +static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
> >     va_list *ap) {
> >      > + struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
> >     **);
> >      > + size_t len = 0;
> >      > + for (h = *head; h != NULL; h = h->next) {
> >      > + if (h->method.len == 0) continue; // Ignore response handler
> >      > + len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
> >      > + mg_print_esc, (int) h->method.len, h->method.buf);
> >      > + }
> >      > + return len;
> >      > +}
> >      > +
> >      > +void mg_rpc_list(struct mg_rpc_req *r) {
> >      > + mg_rpc_ok(r, "[%M]", print_methods, r->head);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/sha1.c"
> >      > +#endif
> >      > +/* Copyright(c) By Steve Reid <st...@edmweb.com> */
> >      > +/* 100% Public Domain */
> >      > +
> >      > +
> >      > +
> >      > +union char64long16 {
> >      > + unsigned char c[64];
> >      > + uint32_t l[16];
> >      > +};
> >      > +
> >      > +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
> >     - (bits))))
> >      > +
> >      > +static uint32_t blk0(union char64long16 *block, int i) {
> >      > + if (MG_BIG_ENDIAN) {
> >      > + } else {
> >      > + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> >      > + (rol(block->l[i], 8) & 0x00FF00FF);
> >      > + }
> >      > + return block->l[i];
> >      > +}
> >      > +
> >      > +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
> >     define R0~R4) */
> >      > +#undef blk
> >      > +#undef R0
> >      > +#undef R1
> >      > +#undef R2
> >      > +#undef R3
> >      > +#undef R4
> >      > +
> >      > +#define blk(i) \
> >      > + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
> >     8) & 15] ^ \
> >      > + block->l[(i + 2) & 15] ^ block->l[i & 15], \
> >      > + 1))
> >      > +#define R0(v, w, x, y, z, i) \
> >      > + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
> >     5); \
> >      > + w = rol(w, 30);
> >      > +#define R1(v, w, x, y, z, i) \
> >      > + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> >      > + w = rol(w, 30);
> >      > +#define R2(v, w, x, y, z, i) \
> >      > + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> >      > + w = rol(w, 30);
> >      > +#define R3(v, w, x, y, z, i) \
> >      > + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
> >     5); \
> >      > + w = rol(w, 30);
> >      > +#define R4(v, w, x, y, z, i) \
> >      > + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> >      > + w = rol(w, 30);
> >      > +
> >      > +static void mg_sha1_transform(uint32_t state[5],
> >      > + const unsigned char *buffer) {
> >      > + uint32_t a, b, c, d, e;
> >      > + union char64long16 block[1];
> >      > +
> >      > + memcpy(block, buffer, 64);
> >      > + a = state[0];
> >      > + b = state[1];
> >      > + c = state[2];
> >      > + d = state[3];
> >      > + e = state[4];
> >      > + R0(a, b, c, d, e, 0);
> >      > + R0(e, a, b, c, d, 1);
> >      > + R0(d, e, a, b, c, 2);
> >      > + R0(c, d, e, a, b, 3);
> >      > + R0(b, c, d, e, a, 4);
> >      > + R0(a, b, c, d, e, 5);
> >      > + R0(e, a, b, c, d, 6);
> >      > + R0(d, e, a, b, c, 7);
> >      > + R0(c, d, e, a, b, 8);
> >      > + R0(b, c, d, e, a, 9);
> >      > + R0(a, b, c, d, e, 10);
> >      > + R0(e, a, b, c, d, 11);
> >      > + R0(d, e, a, b, c, 12);
> >      > + R0(c, d, e, a, b, 13);
> >      > + R0(b, c, d, e, a, 14);
> >      > + R0(a, b, c, d, e, 15);
> >      > + R1(e, a, b, c, d, 16);
> >      > + R1(d, e, a, b, c, 17);
> >      > + R1(c, d, e, a, b, 18);
> >      > + R1(b, c, d, e, a, 19);
> >      > + R2(a, b, c, d, e, 20);
> >      > + R2(e, a, b, c, d, 21);
> >      > + R2(d, e, a, b, c, 22);
> >      > + R2(c, d, e, a, b, 23);
> >      > + R2(b, c, d, e, a, 24);
> >      > + R2(a, b, c, d, e, 25);
> >      > + R2(e, a, b, c, d, 26);
> >      > + R2(d, e, a, b, c, 27);
> >      > + R2(c, d, e, a, b, 28);
> >      > + R2(b, c, d, e, a, 29);
> >      > + R2(a, b, c, d, e, 30);
> >      > + R2(e, a, b, c, d, 31);
> >      > + R2(d, e, a, b, c, 32);
> >      > + R2(c, d, e, a, b, 33);
> >      > + R2(b, c, d, e, a, 34);
> >      > + R2(a, b, c, d, e, 35);
> >      > + R2(e, a, b, c, d, 36);
> >      > + R2(d, e, a, b, c, 37);
> >      > + R2(c, d, e, a, b, 38);
> >      > + R2(b, c, d, e, a, 39);
> >      > + R3(a, b, c, d, e, 40);
> >      > + R3(e, a, b, c, d, 41);
> >      > + R3(d, e, a, b, c, 42);
> >      > + R3(c, d, e, a, b, 43);
> >      > + R3(b, c, d, e, a, 44);
> >      > + R3(a, b, c, d, e, 45);
> >      > + R3(e, a, b, c, d, 46);
> >      > + R3(d, e, a, b, c, 47);
> >      > + R3(c, d, e, a, b, 48);
> >      > + R3(b, c, d, e, a, 49);
> >      > + R3(a, b, c, d, e, 50);
> >      > + R3(e, a, b, c, d, 51);
> >      > + R3(d, e, a, b, c, 52);
> >      > + R3(c, d, e, a, b, 53);
> >      > + R3(b, c, d, e, a, 54);
> >      > + R3(a, b, c, d, e, 55);
> >      > + R3(e, a, b, c, d, 56);
> >      > + R3(d, e, a, b, c, 57);
> >      > + R3(c, d, e, a, b, 58);
> >      > + R3(b, c, d, e, a, 59);
> >      > + R4(a, b, c, d, e, 60);
> >      > + R4(e, a, b, c, d, 61);
> >      > + R4(d, e, a, b, c, 62);
> >      > + R4(c, d, e, a, b, 63);
> >      > + R4(b, c, d, e, a, 64);
> >      > + R4(a, b, c, d, e, 65);
> >      > + R4(e, a, b, c, d, 66);
> >      > + R4(d, e, a, b, c, 67);
> >      > + R4(c, d, e, a, b, 68);
> >      > + R4(b, c, d, e, a, 69);
> >      > + R4(a, b, c, d, e, 70);
> >      > + R4(e, a, b, c, d, 71);
> >      > + R4(d, e, a, b, c, 72);
> >      > + R4(c, d, e, a, b, 73);
> >      > + R4(b, c, d, e, a, 74);
> >      > + R4(a, b, c, d, e, 75);
> >      > + R4(e, a, b, c, d, 76);
> >      > + R4(d, e, a, b, c, 77);
> >      > + R4(c, d, e, a, b, 78);
> >      > + R4(b, c, d, e, a, 79);
> >      > + state[0] += a;
> >      > + state[1] += b;
> >      > + state[2] += c;
> >      > + state[3] += d;
> >      > + state[4] += e;
> >      > + /* Erase working structures. The order of operations is important,
> >      > + * used to ensure that compiler doesn't optimize those out. */
> >      > + memset(block, 0, sizeof(block));
> >      > + a = b = c = d = e = 0;
> >      > + (void) a;
> >      > + (void) b;
> >      > + (void) c;
> >      > + (void) d;
> >      > + (void) e;
> >      > +}
> >      > +
> >      > +void mg_sha1_init(mg_sha1_ctx *context) {
> >      > + context->state[0] = 0x67452301;
> >      > + context->state[1] = 0xEFCDAB89;
> >      > + context->state[2] = 0x98BADCFE;
> >      > + context->state[3] = 0x10325476;
> >      > + context->state[4] = 0xC3D2E1F0;
> >      > + context->count[0] = context->count[1] = 0;
> >      > +}
> >      > +
> >      > +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
> >     *data,
> >      > + size_t len) {
> >      > + size_t i, j;
> >      > +
> >      > + j = context->count[0];
> >      > + if ((context->count[0] += (uint32_t) len << 3) < j)
> >     context->count[1]++;
> >      > + context->count[1] += (uint32_t) (len >> 29);
> >      > + j = (j >> 3) & 63;
> >      > + if ((j + len) > 63) {
> >      > + memcpy(&context->buffer[j], data, (i = 64 - j));
> >      > + mg_sha1_transform(context->state, context->buffer);
> >      > + for (; i + 63 < len; i += 64) {
> >      > + mg_sha1_transform(context->state, &data[i]);
> >      > + }
> >      > + j = 0;
> >      > + } else
> >      > + i = 0;
> >      > + memcpy(&context->buffer[j], &data[i], len - i);
> >      > +}
> >      > +
> >      > +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
> >     *context) {
> >      > + unsigned i;
> >      > + unsigned char finalcount[8], c;
> >      > +
> >      > + for (i = 0; i < 8; i++) {
> >      > + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
> >     1)] >>
> >      > + ((3 - (i & 3)) * 8)) &
> >      > + 255);
> >      > + }
> >      > + c = 0200;
> >      > + mg_sha1_update(context, &c, 1);
> >      > + while ((context->count[0] & 504) != 448) {
> >      > + c = 0000;
> >      > + mg_sha1_update(context, &c, 1);
> >      > + }
> >      > + mg_sha1_update(context, finalcount, 8);
> >      > + for (i = 0; i < 20; i++) {
> >      > + digest[i] =
> >      > + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
> >     8)) & 255);
> >      > + }
> >      > + memset(context, '\0', sizeof(*context));
> >      > + memset(&finalcount, '\0', sizeof(finalcount));
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/sha256.c"
> >      > +#endif
> >      > +// https://github.com/B-Con/crypto-algorithms
> >     <https://github.com/B-Con/crypto-algorithms>
> >      > +// Author: Brad Conte (brad AT bradconte.com
> >     <http://bradconte.com>)
> >      > +// Disclaimer: This code is presented "as is" without any
> >     guarantees.
> >      > +// Details: Defines the API for the corresponding SHA1
> >     implementation.
> >      > +// Copyright: public domain
> >      > +
> >      > +
> >      > +
> >      > +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
> >      > +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
> >      > +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
> >      > +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
> >      > +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
> >      > +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
> >      > +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
> >      > +
> >      > +static const uint32_t mg_sha256_k[64] = {
> >      > + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
> >     0x59f111f1,
> >      > + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
> >     0x550c7dc3,
> >      > + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1,
> >     0xefbe4786,
> >      > + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc,
> >     0x76f988da,
> >      > + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3,
> >     0xd5a79147,
> >      > + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
> >     0x53380d13,
> >      > + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1,
> >     0xa81a664b,
> >      > + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585,
> >     0x106aa070,
> >      > + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3,
> >     0x4ed8aa4a,
> >      > + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814,
> >     0x8cc70208,
> >      > + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
> >      > +
> >      > +void mg_sha256_init(mg_sha256_ctx *ctx) {
> >      > + ctx->len = 0;
> >      > + ctx->bits = 0;
> >      > + ctx->state[0] = 0x6a09e667;
> >      > + ctx->state[1] = 0xbb67ae85;
> >      > + ctx->state[2] = 0x3c6ef372;
> >      > + ctx->state[3] = 0xa54ff53a;
> >      > + ctx->state[4] = 0x510e527f;
> >      > + ctx->state[5] = 0x9b05688c;
> >      > + ctx->state[6] = 0x1f83d9ab;
> >      > + ctx->state[7] = 0x5be0cd19;
> >      > +}
> >      > +
> >      > +static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
> >      > + int i, j;
> >      > + uint32_t a, b, c, d, e, f, g, h;
> >      > + uint32_t m[64];
> >      > + for (i = 0, j = 0; i < 16; ++i, j += 4)
> >      > + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
> >      > + ((uint32_t) ctx->buffer[j + 1] << 16) |
> >      > + ((uint32_t) ctx->buffer[j + 2] << 8) |
> >      > + ((uint32_t) ctx->buffer[j + 3]));
> >      > + for (; i < 64; ++i)
> >      > + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
> >      > +
> >      > + a = ctx->state[0];
> >      > + b = ctx->state[1];
> >      > + c = ctx->state[2];
> >      > + d = ctx->state[3];
> >      > + e = ctx->state[4];
> >      > + f = ctx->state[5];
> >      > + g = ctx->state[6];
> >      > + h = ctx->state[7];
> >      > +
> >      > + for (i = 0; i < 64; ++i) {
> >      > + uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
> >      > + uint32_t t2 = ep0(a) + maj(a, b, c);
> >      > + h = g;
> >      > + g = f;
> >      > + f = e;
> >      > + e = d + t1;
> >      > + d = c;
> >      > + c = b;
> >      > + b = a;
> >      > + a = t1 + t2;
> >      > + }
> >      > +
> >      > + ctx->state[0] += a;
> >      > + ctx->state[1] += b;
> >      > + ctx->state[2] += c;
> >      > + ctx->state[3] += d;
> >      > + ctx->state[4] += e;
> >      > + ctx->state[5] += f;
> >      > + ctx->state[6] += g;
> >      > + ctx->state[7] += h;
> >      > +}
> >      > +
> >      > +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char
> >     *data,
> >      > + size_t len) {
> >      > + size_t i;
> >      > + for (i = 0; i < len; i++) {
> >      > + ctx->buffer[ctx->len] = data[i];
> >      > + if ((++ctx->len) == 64) {
> >      > + mg_sha256_chunk(ctx);
> >      > + ctx->bits += 512;
> >      > + ctx->len = 0;
> >      > + }
> >      > + }
> >      > +}
> >      > +
> >      > +// TODO: make final reusable (remove side effects)
> >      > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx
> >     *ctx) {
> >      > + uint32_t i = ctx->len;
> >      > + if (i < 56) {
> >      > + ctx->buffer[i++] = 0x80;
> >      > + while (i < 56) {
> >      > + ctx->buffer[i++] = 0x00;
> >      > + }
> >      > + } else {
> >      > + ctx->buffer[i++] = 0x80;
> >      > + while (i < 64) {
> >      > + ctx->buffer[i++] = 0x00;
> >      > + }
> >      > + mg_sha256_chunk(ctx);
> >      > + memset(ctx->buffer, 0, 56);
> >      > + }
> >      > +
> >      > + ctx->bits += ctx->len * 8;
> >      > + ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
> >      > + ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
> >      > + ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
> >      > + ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
> >      > + ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
> >      > + ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
> >      > + ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
> >      > + ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
> >      > + mg_sha256_chunk(ctx);
> >      > +
> >      > + for (i = 0; i < 4; ++i) {
> >      > + digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
> >      > + digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) &
> >     0xff);
> >      > + digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) &
> >     0xff);
> >      > + }
> >      > +}
> >      > +
> >      > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
> >     uint8_t *data,
> >      > + size_t datasz) {
> >      > + mg_sha256_ctx ctx;
> >      > + uint8_t k[64] = {0};
> >      > + uint8_t o_pad[64], i_pad[64];
> >      > + unsigned int i;
> >      > + memset(i_pad, 0x36, sizeof(i_pad));
> >      > + memset(o_pad, 0x5c, sizeof(o_pad));
> >      > + if (keysz < 64) {
> >      > + if (keysz > 0) memmove(k, key, keysz);
> >      > + } else {
> >      > + mg_sha256_init(&ctx);
> >      > + mg_sha256_update(&ctx, key, keysz);
> >      > + mg_sha256_final(k, &ctx);
> >      > + }
> >      > + for (i = 0; i < sizeof(k); i++) {
> >      > + i_pad[i] ^= k[i];
> >      > + o_pad[i] ^= k[i];
> >      > + }
> >      > + mg_sha256_init(&ctx);
> >      > + mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
> >      > + mg_sha256_update(&ctx, data, datasz);
> >      > + mg_sha256_final(dst, &ctx);
> >      > + mg_sha256_init(&ctx);
> >      > + mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
> >      > + mg_sha256_update(&ctx, dst, 32);
> >      > + mg_sha256_final(dst, &ctx);
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/sntp.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
> >      > +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
> >      > +
> >      > +static int64_t gettimestamp(const uint32_t *data) {
> >      > + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> >      > + if (sec) sec -= SNTP_TIME_OFFSET;
> >      > + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
> >     * 1000.0);
> >      > +}
> >      > +
> >      > +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> >      > + int64_t res = -1;
> >      > + int mode = len > 0 ? buf[0] & 7 : 0;
> >      > + int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> >      > + if (len < 48) {
> >      > + MG_ERROR(("%s", "corrupt packet"));
> >      > + } else if (mode != 4 && mode != 5) {
> >      > + MG_ERROR(("%s", "not a server reply"));
> >      > + } else if (buf[1] == 0) {
> >      > + MG_ERROR(("%s", "server sent a kiss of death"));
> >      > + } else if (version == 4 || version == 3) {
> >      > + // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> >      > + int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> >      > + int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> >      > + int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> >      > + int64_t t3 = (int64_t) mg_millis();
> >      > + int64_t delta = (t3 - t0) - (t2 - t1);
> >      > + MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
> >     delta));
> >      > + res = t2 + delta / 2;
> >      > + } else {
> >      > + MG_ERROR(("unexpected version: %d", version));
> >      > + }
> >      > + return res;
> >      > +}
> >      > +
> >      > +static void sntp_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > + if (ev == MG_EV_READ) {
> >      > + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> >      > + if (milliseconds > 0) {
> >      > + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id,
> >     milliseconds));
> >      > + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> >      > + MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> >      > + (unsigned) (milliseconds % 1000)));
> >      > + }
> >      > + mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
> >      > + } else if (ev == MG_EV_CONNECT) {
> >      > + mg_sntp_request(c);
> >      > + } else if (ev == MG_EV_CLOSE) {
> >      > + }
> >      > + (void) ev_data;
> >      > +}
> >      > +
> >      > +void mg_sntp_request(struct mg_connection *c) {
> >      > + if (c->is_resolving) {
> >      > + MG_ERROR(("%lu wait until resolved", c->id));
> >      > + } else {
> >      > + int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
> >      > + uint8_t buf[48] = {0};
> >      > + uint32_t *t = (uint32_t *) &buf[40];
> >      > + double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> >      > + buf[0] = (0 << 6) | (4 << 3) | 3;
> >      > + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> >      > + t[1] = mg_htonl((uint32_t) frac);
> >      > + mg_send(c, buf, sizeof(buf));
> >      > + }
> >      > +}
> >      > +
> >      > +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > + mg_event_handler_t fn, void *fnd) {
> >      > + struct mg_connection *c = NULL;
> >      > + if (url == NULL) url = "udp://time.google.com:123
> >     <http://time.google.com:123>";
> >      > + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> >      > + return c;
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/sock.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#if MG_ENABLE_SOCKET
> >      > +
> >      > +#ifndef closesocket
> >      > +#define closesocket(x) close(x)
> >      > +#endif
> >      > +
> >      > +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> >      > +#define S2PTR(s_) ((void *) (size_t) (s_))
> >      > +
> >      > +#ifndef MSG_NONBLOCKING
> >      > +#define MSG_NONBLOCKING 0
> >      > +#endif
> >      > +
> >      > +#ifndef AF_INET6
> >      > +#define AF_INET6 10
> >      > +#endif
> >      > +
> >      > +#ifndef MG_SOCK_ERR
> >      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
> >      > +#endif
> >      > +
> >      > +#ifndef MG_SOCK_INTR
> >      > +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET &&
> >     MG_SOCK_ERR(-1) == EINTR)
> >      > +#endif
> >      > +
> >      > +#ifndef MG_SOCK_PENDING
> >      > +#define MG_SOCK_PENDING(errcode) \
> >      > + (((errcode) < 0) && (errno == EINPROGRESS || errno ==
> >     EWOULDBLOCK))
> >      > +#endif
> >      > +
> >      > +#ifndef MG_SOCK_RESET
> >      > +#define MG_SOCK_RESET(errcode) \
> >      > + (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
> >      > +#endif
> >      > +
> >      > +union usa {
> >      > + struct sockaddr sa;
> >      > + struct sockaddr_in sin;
> >      > +#if MG_ENABLE_IPV6
> >      > + struct sockaddr_in6 sin6;
> >      > +#endif
> >      > +};
> >      > +
> >      > +static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> >      > + socklen_t len = sizeof(usa->sin);
> >      > + memset(usa, 0, sizeof(*usa));
> >      > + usa->sin.sin_family = AF_INET;
> >      > + usa->sin.sin_port = a->port;
> >      > + memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
> >      > +#if MG_ENABLE_IPV6
> >      > + if (a->is_ip6) {
> >      > + usa->sin.sin_family = AF_INET6;
> >      > + usa->sin6.sin6_port = a->port;
> >      > + usa->sin6.sin6_scope_id = a->scope_id;
> >      > + memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
> >      > + len = sizeof(usa->sin6);
> >      > + }
> >      > +#endif
> >      > + return len;
> >      > +}
> >      > +
> >      > +static void tomgaddr(union usa *usa, struct mg_addr *a, bool
> >     is_ip6) {
> >      > + a->is_ip6 = is_ip6;
> >      > + a->port = usa->sin.sin_port;
> >      > + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
> >      > +#if MG_ENABLE_IPV6
> >      > + if (is_ip6) {
> >      > + memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
> >      > + a->port = usa->sin6.sin6_port;
> >      > + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
> >      > + }
> >      > +#endif
> >      > +}
> >      > +
> >      > +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> >      > + union usa usa;
> >      > + socklen_t n = sizeof(usa);
> >      > + if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
> >      > + tomgaddr(&usa, addr, n != sizeof(usa.sin));
> >      > + }
> >      > +}
> >      > +
> >      > +static void iolog(struct mg_connection *c, char *buf, long n,
> >     bool r) {
> >      > + if (n == MG_IO_WAIT) {
> >      > + // Do nothing
> >      > + } else if (n <= 0) {
> >      > + c->is_closing = 1; // Termination. Don't call mg_error(): #1529
> >      > + } else if (n > 0) {
> >      > + if (c->is_hexdumping) {
> >      > + MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port,
> >     &c->loc,
> >      > + r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
> >      > + mg_hexdump(buf, (size_t) n);
> >      > + }
> >      > + if (r) {
> >      > + c->recv.len += (size_t) n;
> >      > + mg_call(c, MG_EV_READ, &n);
> >      > + } else {
> >      > + mg_iobuf_del(&c->send, 0, (size_t) n);
> >      > + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> >      > + if (c->send.len == 0) {
> >      > + MG_EPOLL_MOD(c, 0);
> >      > + }
> >      > + mg_call(c, MG_EV_WRITE, &n);
> >      > }
> >      > }
> >      > - return flags;
> >      > }
> >      >
> >      > -static int uri_to_path(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - const struct mg_http_serve_opts *opts, char *path,
> >      > - size_t path_size) {
> >      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > - struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p =
> >     {0, 0};
> >      > - while (mg_commalist(&s, &k, &v)) {
> >      > - if (v.len == 0) v = k, k = mg_str("/");
> >      > - if (hm->uri.len < k.len) continue;
> >      > - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
> >      > - u = k, p = v;
> >      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > + long n;
> >      > + if (c->is_udp) {
> >      > + union usa usa;
> >      > + socklen_t slen = tousa(&c->rem, &usa);
> >      > + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
> >     <http://usa.sa>, slen);
> >      > + if (n > 0) setlocaddr(FD(c), &c->loc);
> >      > + } else {
> >      > + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> >      > }
> >      > - return uri_to_path2(c, hm, fs, u, p, path, path_size);
> >      > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> >      > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> >      > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > }
> >      >
> >      > -void mg_http_serve_dir(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - const struct mg_http_serve_opts *opts) {
> >      > - char path[MG_PATH_MAX];
> >      > - const char *sp = opts->ssi_pattern;
> >      > - int flags = uri_to_path(c, hm, opts, path, sizeof(path));
> >      > - if (flags < 0) {
> >      > - // Do nothing: the response has already been sent by uri_to_path()
> >      > - } else if (flags & MG_FS_DIR) {
> >      > - listdir(c, hm, opts, path);
> >      > - } else if (flags && sp != NULL &&
> >      > - mg_globmatch(sp, strlen(sp), path, strlen(path))) {
> >      > - mg_http_serve_ssi(c, opts->root_dir, path);
> >      > +bool mg_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > + if (c->is_udp) {
> >      > + long n = mg_io_send(c, buf, len);
> >      > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
> >     c->send.len,
> >      > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> >      > + iolog(c, (char *) buf, n, false);
> >      > + return n > 0;
> >      > } else {
> >      > - mg_http_serve_file(c, hm, path, opts);
> >      > + return mg_iobuf_add(&c->send, c->send.len, buf, len);
> >      > }
> >      > }
> >      >
> >      > -static bool mg_is_url_safe(int c) {
> >      > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
> >      > - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
> >     == '~';
> >      > +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> >      > +#if defined(MG_CUSTOM_NONBLOCK)
> >      > + MG_CUSTOM_NONBLOCK(fd);
> >      > +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > + unsigned long on = 1;
> >      > + ioctlsocket(fd, FIONBIO, &on);
> >      > +#elif MG_ENABLE_RL
> >      > + unsigned long on = 1;
> >      > + ioctlsocket(fd, FIONBIO, &on);
> >      > +#elif MG_ENABLE_FREERTOS_TCP
> >      > + const BaseType_t off = 0;
> >      > + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
> >     != 0) (void) 0;
> >      > + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
> >     != 0) (void) 0;
> >      > +#elif MG_ENABLE_LWIP
> >      > + lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> >      > +#elif MG_ARCH == MG_ARCH_AZURERTOS
> >      > + fcntl(fd, F_SETFL, O_NONBLOCK);
> >      > +#elif MG_ARCH == MG_ARCH_TIRTOS
> >      > + int val = 0;
> >      > + setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> >      > + // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> >      > + int sz = sizeof(val);
> >      > + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> >      > + val /= 2; // set send low-water mark at half send buffer size
> >      > + setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> >      > +#else
> >      > + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
> >     Non-blocking mode
> >      > + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
> >      > +#endif
> >      > }
> >      >
> >      > -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
> >     len) {
> >      > - size_t i, n = 0;
> >      > - for (i = 0; i < sl; i++) {
> >      > - int c = *(unsigned char *) &s[i];
> >      > - if (n + 4 >= len) return 0;
> >      > - if (mg_is_url_safe(c)) {
> >      > - buf[n++] = s[i];
> >      > +bool mg_open_listener(struct mg_connection *c, const char *url) {
> >      > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> >      > + bool success = false;
> >      > + c->loc.port = mg_htons(mg_url_port(url));
> >      > + if (!mg_aton(mg_url_host(url), &c->loc)) {
> >      > + MG_ERROR(("invalid listening URL: %s", url));
> >      > + } else {
> >      > + union usa usa;
> >      > + socklen_t slen = tousa(&c->loc, &usa);
> >      > + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> >      > + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
> >     SOCK_STREAM;
> >      > + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> >      > + (void) on;
> >      > +
> >      > + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> >      > + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
> >      > +#if defined(SO_EXCLUSIVEADDRUSE)
> >      > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
> >      > + (char *) &on, sizeof(on))) != 0) {
> >      > + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> >      > + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on,
> >     MG_SOCK_ERR(rc)));
> >      > +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
> >      > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char
> >     *) &on,
> >      > + sizeof(on))) != 0) {
> >      > + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On
> >      > + // Windows, SO_REUSEADDR allows to bind a socket to a port
> >     without error
> >      > + // even if the port is already open by another program. This is
> >     not the
> >      > + // behavior SO_REUSEADDR was designed for, and leads to
> >     hard-to-track
> >      > + // failure scenarios.
> >      > + //
> >      > + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by
> >     defining
> >      > + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will
> >     compile but
> >      > + // won't work! (setsockopt will return EINVAL)
> >      > + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
> >      > +#endif
> >      > +#if MG_IPV6_V6ONLY
> >      > + // Bind only to the V6 address, not V4 address on this port
> >      > + } else if (c->loc.is_ip6 &&
> >      > + (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
> >      > + sizeof(on))) != 0) {
> >      > + // See #2089. Allow to bind v4 and v6 sockets on the same port
> >      > + MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
> >      > +#endif
> >      > + } else if ((rc = bind(fd, &usa.sa <http://usa.sa>, slen)) != 0) {
> >      > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> >      > + } else if ((type == SOCK_STREAM &&
> >      > + (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
> >      > + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> >      > + // In case port was set to 0, get the real port number
> >      > + MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
> >      > } else {
> >      > - buf[n++] = '%';
> >      > - mg_hex(&s[i], 1, &buf[n]);
> >      > - n += 2;
> >      > + setlocaddr(fd, &c->loc);
> >      > + mg_set_non_blocking_mode(fd);
> >      > + c->fd = S2PTR(fd);
> >      > + MG_EPOLL_ADD(c);
> >      > + success = true;
> >      > }
> >      > }
> >      > - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
> >     the destination
> >      > - if (len > 0) buf[len - 1] = '\0'; // Always.
> >      > - return n;
> >      > + if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> >      > + return success;
> >      > }
> >      >
> >      > -void mg_http_creds(struct mg_http_message *hm, char *user,
> >     size_t userlen,
> >      > - char *pass, size_t passlen) {
> >      > - struct mg_str *v = mg_http_get_header(hm, "Authorization");
> >      > - user[0] = pass[0] = '\0';
> >      > - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
> >      > - char buf[256];
> >      > - int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
> >      > - const char *p = (const char *) memchr(buf, ':', n > 0 ?
> >     (size_t) n : 0);
> >      > - if (p != NULL) {
> >      > - mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
> >      > - mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p +
> >     1);
> >      > - }
> >      > - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ",
> >     7) == 0) {
> >      > - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
> >      > - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
> >      > - struct mg_str t = mg_http_get_header_var(*v,
> >     mg_str_n("access_token", 12));
> >      > - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
> >     t.ptr);
> >      > +static long recv_raw(struct mg_connection *c, void *buf, size_t
> >     len) {
> >      > + long n = 0;
> >      > + if (c->is_udp) {
> >      > + union usa usa;
> >      > + socklen_t slen = tousa(&c->rem, &usa);
> >      > + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
> >     <http://usa.sa>, &slen);
> >      > + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
> >      > } else {
> >      > - mg_http_get_var(&hm->query, "access_token", pass, passlen);
> >      > + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> >      > }
> >      > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
> >      > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
> >      > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > }
> >      >
> >      > -static struct mg_str stripquotes(struct mg_str s) {
> >      > - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
> >      > - ? mg_str_n(s.ptr + 1, s.len - 2)
> >      > - : s;
> >      > +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
> >      > + bool res = false;
> >      > + if (io->len >= MG_MAX_RECV_SIZE) {
> >      > + mg_error(c, "MG_MAX_RECV_SIZE");
> >      > + } else if (io->size <= io->len &&
> >      > + !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
> >      > + mg_error(c, "OOM");
> >      > + } else {
> >      > + res = true;
> >      > + }
> >      > + return res;
> >      > }
> >      >
> >      > -struct mg_str mg_http_get_header_var(struct mg_str s, struct
> >     mg_str v) {
> >      > - size_t i;
> >      > - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
> >      > - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len)
> >     == 0) {
> >      > - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
> >      > - int q = p < x && *p == '"' ? 1 : 0;
> >      > - while (p < x &&
> >      > - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
> >      > - p++;
> >      > - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int)
> >     v.len,
> >      > - // v.ptr, (int) (p - b), b));
> >      > - return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
> >      > +// NOTE(lsm): do only one iteration of reads, cause some systems
> >      > +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
> >     no data
> >      > +static void read_conn(struct mg_connection *c) {
> >      > + if (ioalloc(c, &c->recv)) {
> >      > + char *buf = (char *) &c->recv.buf[c->recv.len];
> >      > + size_t len = c->recv.size - c->recv.len;
> >      > + long n = -1;
> >      > + if (c->is_tls) {
> >      > + if (!ioalloc(c, &c->rtls)) return;
> >      > + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
> >      > + c->rtls.size - c->rtls.len);
> >      > + if (n == MG_IO_ERR && c->rtls.len == 0) {
> >      > + // Close only if we have fully drained both raw (rtls) and TLS
> >     buffers
> >      > + c->is_closing = 1;
> >      > + } else {
> >      > + if (n > 0) c->rtls.len += (size_t) n;
> >      > + if (c->is_tls_hs) mg_tls_handshake(c);
> >      > + n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
> >      > + }
> >      > + } else {
> >      > + n = recv_raw(c, buf, len);
> >      > }
> >      > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
> >     c->send.len,
> >      > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
> >      > + iolog(c, buf, n, true);
> >      > }
> >      > - return mg_str_n(NULL, 0);
> >      > }
> >      >
> >      > -bool mg_http_match_uri(const struct mg_http_message *hm, const
> >     char *glob) {
> >      > - return mg_match(hm->uri, mg_str(glob), NULL);
> >      > +static void write_conn(struct mg_connection *c) {
> >      > + char *buf = (char *) c->send.buf;
> >      > + size_t len = c->send.len;
> >      > + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
> >     buf, len);
> >      > + MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d",
> >     c->id, c->fd,
> >      > + (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> >      > + (long) c->recv.size, n, MG_SOCK_ERR(n)));
> >      > + iolog(c, buf, n, false);
> >      > }
> >      >
> >      > -long mg_http_upload(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - struct mg_fs *fs, const char *path, size_t max_size) {
> >      > - char buf[20] = "0";
> >      > - long res = 0, offset;
> >      > - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
> >      > - offset = strtol(buf, NULL, 0);
> >      > - if (hm->body.len == 0) {
> >      > - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
> >      > +static void close_conn(struct mg_connection *c) {
> >      > + if (FD(c) != MG_INVALID_SOCKET) {
> >      > +#if MG_ENABLE_EPOLL
> >      > + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> >      > +#endif
> >      > + closesocket(FD(c));
> >      > +#if MG_ENABLE_FREERTOS_TCP
> >      > + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> >      > +#endif
> >      > + }
> >      > + mg_close_conn(c);
> >      > +}
> >      > +
> >      > +static void connect_conn(struct mg_connection *c) {
> >      > + union usa usa;
> >      > + socklen_t n = sizeof(usa);
> >      > + // Use getpeername() to test whether we have connected
> >      > + if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
> >      > + c->is_connecting = 0;
> >      > + setlocaddr(FD(c), &c->loc);
> >      > + mg_call(c, MG_EV_CONNECT, NULL);
> >      > + MG_EPOLL_MOD(c, 0);
> >      > + if (c->is_tls_hs) mg_tls_handshake(c);
> >      > } else {
> >      > - struct mg_fd *fd;
> >      > - size_t current_size = 0;
> >      > - MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len,
> >     offset));
> >      > - if (offset == 0) fs->rm(path); // If offset if 0, truncate file
> >      > - fs->st(path, &current_size, NULL);
> >      > - if (offset < 0) {
> >      > - mg_http_reply(c, 400, "", "offset required");
> >      > - res = -1;
> >      > - } else if (offset > 0 && current_size != (size_t) offset) {
> >      > - mg_http_reply(c, 400, "", "%s: offset mismatch", path);
> >      > - res = -2;
> >      > - } else if ((size_t) offset + hm->body.len > max_size) {
> >      > - mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
> >      > - (unsigned long) max_size);
> >      > - res = -3;
> >      > - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
> >      > - mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
> >      > - res = -4;
> >      > + mg_error(c, "socket error");
> >      > + }
> >      > +}
> >      > +
> >      > +static void setsockopts(struct mg_connection *c) {
> >      > +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> >      > + MG_ARCH == MG_ARCH_TIRTOS
> >      > + (void) c;
> >      > +#else
> >      > + int on = 1;
> >      > +#if !defined(SOL_TCP)
> >      > +#define SOL_TCP IPPROTO_TCP
> >      > +#endif
> >      > + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
> >     sizeof(on)) != 0)
> >      > + (void) 0;
> >      > + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
> >     sizeof(on)) !=
> >      > + 0)
> >      > + (void) 0;
> >      > +#endif
> >      > +}
> >      > +
> >      > +void mg_connect_resolved(struct mg_connection *c) {
> >      > + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> >      > + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
> >     resolved IP
> >      > + c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
> >      > + c->is_resolving = 0; // Clear resolving flag
> >      > + if (FD(c) == MG_INVALID_SOCKET) {
> >      > + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
> >      > + } else if (c->is_udp) {
> >      > + MG_EPOLL_ADD(c);
> >      > +#if MG_ARCH == MG_ARCH_TIRTOS
> >      > + union usa usa; // TI-RTOS NDK requires binding to receive on
> >     UDP sockets
> >      > + socklen_t slen = tousa(&c->loc, &usa);
> >      > + if ((rc = bind(c->fd, &usa.sa <http://usa.sa>, slen)) != 0)
> >      > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
> >      > +#endif
> >      > + setlocaddr(FD(c), &c->loc);
> >      > + mg_call(c, MG_EV_RESOLVE, NULL);
> >      > + mg_call(c, MG_EV_CONNECT, NULL);
> >      > + } else {
> >      > + union usa usa;
> >      > + socklen_t slen = tousa(&c->rem, &usa);
> >      > + mg_set_non_blocking_mode(FD(c));
> >      > + setsockopts(c);
> >      > + MG_EPOLL_ADD(c);
> >      > + mg_call(c, MG_EV_RESOLVE, NULL);
> >      > + rc = connect(FD(c), &usa.sa <http://usa.sa>, slen); // Attempt
> >     to connect
> >      > + if (rc == 0) { // Success
> >      > + setlocaddr(FD(c), &c->loc);
> >      > + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user
> >      > + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP
> >     handshake
> >      > + MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port,
> >     &c->rem));
> >      > + c->is_connecting = 1;
> >      > } else {
> >      > - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
> >      > - mg_fs_close(fd);
> >      > - mg_http_reply(c, 200, "", "%ld", res);
> >      > + mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
> >      > }
> >      > }
> >      > - return res;
> >      > -}
> >      > -
> >      > -int mg_http_status(const struct mg_http_message *hm) {
> >      > - return atoi(hm->uri.ptr);
> >      > -}
> >      > -
> >      > -// If a server sends data to the client using chunked encoding,
> >     Mongoose strips
> >      > -// off the chunking prefix (hex length and \r\n) and suffix
> >     (\r\n), appends the
> >      > -// stripped data to the body, and fires the MG_EV_HTTP_CHUNK
> >     event. When zero
> >      > -// chunk is received, we fire MG_EV_HTTP_MSG, and the body
> >     already has all
> >      > -// chunking prefixes/suffixes stripped.
> >      > -//
> >      > -// If a server sends data without chunked encoding, we also fire
> >     a series of
> >      > -// MG_EV_HTTP_CHUNK events for every received piece of data, and
> >     then we fire
> >      > -// MG_EV_HTTP_MSG event in the end.
> >      > -//
> >      > -// We track total processed length in the c->pfn_data, which is
> >     a void *
> >      > -// pointer: we store a size_t value there.
> >      > -static bool getchunk(struct mg_str s, size_t *prefixlen, size_t
> >     *datalen) {
> >      > - size_t i = 0, n;
> >      > - while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
> >      > - n = mg_unhexn(s.ptr, i);
> >      > - // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
> >      > - if (s.len < i + n + 4) return false; // Chunk not yet fully
> >     buffered
> >      > - if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
> >      > - if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n')
> >     return false;
> >      > - *prefixlen = i + 2;
> >      > - *datalen = n;
> >      > - return true;
> >      > }
> >      >
> >      > -static bool mg_is_chunked(struct mg_http_message *hm) {
> >      > - const char *needle = "chunked";
> >      > - struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
> >      > - return te != NULL && mg_vcasecmp(te, needle) == 0;
> >      > -}
> >      > -
> >      > -void mg_http_delete_chunk(struct mg_connection *c, struct
> >     mg_http_message *hm) {
> >      > - size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
> >      > - mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
> >      > - c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
> >      > -}
> >      > -
> >      > -static void deliver_chunked_chunks(struct mg_connection *c,
> >     size_t hlen,
> >      > - struct mg_http_message *hm, bool *next) {
> >      > - // | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
> >      > - // +------------------+--------------------------+----
> >      > - // | hlen | chunk1 | ......
> >      > - char *buf = (char *) &c->recv.buf[hlen], *p = buf;
> >      > - size_t len = c->recv.len - hlen;
> >      > - size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> >      > - size_t mark, pl, dl, del = 0, ofs = 0;
> >      > - bool last = false;
> >      > - if (processed <= len) len -= processed, buf += processed;
> >      > - while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl,
> >     &dl)) {
> >      > - size_t saved = c->recv.len;
> >      > - memmove(p + processed, buf + ofs + pl, dl);
> >      > - // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
> >      > - hm->chunk = mg_str_n(p + processed, dl);
> >      > - mg_call(c, MG_EV_HTTP_CHUNK, hm);
> >      > - ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
> >      > - processed += dl;
> >      > - if (c->recv.len != saved) processed -= dl, buf -= dl;
> >      > - // mg_hexdump(c->recv.buf, hlen + processed);
> >      > - last = (dl == 0);
> >      > - }
> >      > - mg_iobuf_del(&c->recv, hlen + processed, del);
> >      > - mark = ((size_t) c->pfn_data) & MG_DMARK;
> >      > - c->pfn_data = (void *) (processed | mark);
> >      > - if (last) {
> >      > - hm->body.len = processed;
> >      > - hm->message.len = hlen + processed;
> >      > - c->pfn_data = NULL;
> >      > - if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
> >      > - // MG_INFO(("LAST, mark: %lx", mark));
> >      > - // mg_hexdump(c->recv.buf, c->recv.len);
> >      > - }
> >      > +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> >      > + socklen_t *len) {
> >      > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> >      > + do {
> >      > + memset(usa, 0, sizeof(*usa));
> >      > + fd = accept(sock, &usa->sa, len);
> >      > + } while (MG_SOCK_INTR(fd));
> >      > + return fd;
> >      > }
> >      >
> >      > -static void deliver_normal_chunks(struct mg_connection *c,
> >     size_t hlen,
> >      > - struct mg_http_message *hm, bool *next) {
> >      > - size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
> >      > - size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
> >      > - hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len -
> >     hlen);
> >      > - if (processed <= hm->chunk.len && !deleted) {
> >      > - hm->chunk.len -= processed;
> >      > - hm->chunk.ptr += processed;
> >      > - }
> >      > - left = hm->body.len < processed ? 0 : hm->body.len - processed;
> >      > - if (hm->chunk.len > left) hm->chunk.len = left;
> >      > - if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
> >      > - processed += hm->chunk.len;
> >      > - deleted = ((size_t) c->pfn_data) & MG_DMARK; // Re-evaluate
> >     after user call
> >      > - if (processed >= hm->body.len) { // Last, 0-len chunk
> >      > - hm->chunk.len = 0; // Reset length
> >      > - mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
> >      > - c->pfn_data = NULL; // Reset processed counter
> >      > - if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen),
> >     *next = true;
> >      > +static void accept_conn(struct mg_mgr *mgr, struct mg_connection
> >     *lsn) {
> >      > + struct mg_connection *c = NULL;
> >      > + union usa usa;
> >      > + socklen_t sa_len = sizeof(usa);
> >      > + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> >      > + if (fd == MG_INVALID_SOCKET) {
> >      > +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
> >      > + // AzureRTOS, in non-block socket mode can mark listening
> >     socket readable
> >      > + // even it is not. See comment for 'select' func implementation in
> >      > + // nx_bsd.c That's not an error, just should try later
> >      > + if (errno != EAGAIN)
> >      > +#endif
> >      > + MG_ERROR(("%lu accept failed, errno %d", lsn->id,
> >     MG_SOCK_ERR(-1)));
> >      > +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> >      > + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
> >      > + } else if ((long) fd >= FD_SETSIZE) {
> >      > + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> >      > + closesocket(fd);
> >      > +#endif
> >      > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > + MG_ERROR(("%lu OOM", lsn->id));
> >      > + closesocket(fd);
> >      > } else {
> >      > - c->pfn_data = (void *) (processed | deleted); // if it is set
> >      > + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> >      > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > + c->fd = S2PTR(fd);
> >      > + MG_EPOLL_ADD(c);
> >      > + mg_set_non_blocking_mode(FD(c));
> >      > + setsockopts(c);
> >      > + c->is_accepted = 1;
> >      > + c->is_hexdumping = lsn->is_hexdumping;
> >      > + c->loc = lsn->loc;
> >      > + c->pfn = lsn->pfn;
> >      > + c->pfn_data = lsn->pfn_data;
> >      > + c->fn = lsn->fn;
> >      > + c->fn_data = lsn->fn_data;
> >      > + MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd,
> >     mg_print_ip_port,
> >      > + &c->rem, mg_print_ip_port, &c->loc));
> >      > + mg_call(c, MG_EV_OPEN, NULL);
> >      > + mg_call(c, MG_EV_ACCEPT, NULL);
> >      > }
> >      > }
> >      >
> >      > -static void http_cb(struct mg_connection *c, int ev, void *evd,
> >     void *fnd) {
> >      > - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
> >      > - struct mg_http_message hm;
> >      > - // mg_hexdump(c->recv.buf, c->recv.len);
> >      > - while (c->recv.buf != NULL && c->recv.len > 0) {
> >      > - bool next = false;
> >      > - int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> >      > - if (hlen < 0) {
> >      > - mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
> >      > - break;
> >      > - }
> >      > - if (c->is_resp) break; // Response is still generated
> >      > - if (hlen == 0) break; // Request is not buffered yet
> >      > - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
> >      > - hm.message.len = c->recv.len; // and closes now, deliver a MSG
> >      > - hm.body.len = hm.message.len - (size_t) (hm.body.ptr -
> >     hm.message.ptr);
> >      > - }
> >      > - if (mg_is_chunked(&hm)) {
> >      > - deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
> >      > - } else {
> >      > - deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
> >      > - }
> >      > - if (next) continue; // Chunks & request were deleted
> >      > - // Chunk events are delivered. If we have full body, deliver MSG
> >      > - if (c->recv.len < hm.message.len) break;
> >      > - if (c->is_accepted) c->is_resp = 1; // Start generating response
> >      > - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
> >      > - mg_iobuf_del(&c->recv, 0, hm.message.len);
> >      > - }
> >      > - }
> >      > - (void) evd, (void) fnd;
> >      > +static bool can_read(const struct mg_connection *c) {
> >      > + return c->is_full == false;
> >      > }
> >      >
> >      > -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> >      > - if (c != NULL) c->pfn = http_cb;
> >      > - return c;
> >      > +static bool can_write(const struct mg_connection *c) {
> >      > + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
> >      > }
> >      >
> >      > -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
> >     char *url,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> >      > - if (c != NULL) c->pfn = http_cb;
> >      > - return c;
> >      > +static bool skip_iotest(const struct mg_connection *c) {
> >      > + return (c->is_closing || c->is_resolving || FD(c) ==
> >     MG_INVALID_SOCKET) ||
> >      > + (can_read(c) == false && can_write(c) == false);
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/iobuf.c"
> >      > -#endif
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -// Not using memset for zeroing memory, cause it can be dropped
> >     by compiler
> >      > -// See https://github.com/cesanta/mongoose/pull/1265
> >     <https://github.com/cesanta/mongoose/pull/1265>
> >      > -static void zeromem(volatile unsigned char *buf, size_t len) {
> >      > - if (buf != NULL) {
> >      > - while (len--) *buf++ = 0;
> >      > +static void mg_iotest(struct mg_mgr *mgr, int ms) {
> >      > +#if MG_ENABLE_FREERTOS_TCP
> >      > + struct mg_connection *c;
> >      > + for (c = mgr->conns; c != NULL; c = c->next) {
> >      > + c->is_readable = c->is_writable = 0;
> >      > + if (skip_iotest(c)) continue;
> >      > + if (can_read(c))
> >      > + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> >      > + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> >      > + if (c->is_closing) ms = 1;
> >      > }
> >      > -}
> >      > -
> >      > -static size_t roundup(size_t size, size_t align) {
> >      > - return align == 0 ? size : (size + align - 1) / align * align;
> >      > -}
> >      > -
> >      > -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
> >      > - int ok = 1;
> >      > - new_size = roundup(new_size, io->align);
> >      > - if (new_size == 0) {
> >      > - zeromem(io->buf, io->size);
> >      > - free(io->buf);
> >      > - io->buf = NULL;
> >      > - io->len = io->size = 0;
> >      > - } else if (new_size != io->size) {
> >      > - // NOTE(lsm): do not use realloc here. Use calloc/free only, to
> >     ease the
> >      > - // porting to some obscure platforms like FreeRTOS
> >      > - void *p = calloc(1, new_size);
> >      > - if (p != NULL) {
> >      > - size_t len = new_size < io->len ? new_size : io->len;
> >      > - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
> >      > - zeromem(io->buf, io->size);
> >      > - free(io->buf);
> >      > - io->buf = (unsigned char *) p;
> >      > - io->size = new_size;
> >      > + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> >      > + for (c = mgr->conns; c != NULL; c = c->next) {
> >      > + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> >      > + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> >      > + c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> >      > + if (c->fd != MG_INVALID_SOCKET)
> >      > + FreeRTOS_FD_CLR(c->fd, mgr->ss,
> >      > + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> >      > + }
> >      > +#elif MG_ENABLE_EPOLL
> >      > + size_t max = 1;
> >      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > + c->is_readable = c->is_writable = 0;
> >      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1,
> >     c->is_readable = 1;
> >      > + if (can_write(c)) MG_EPOLL_MOD(c, 1);
> >      > + if (c->is_closing) ms = 1;
> >      > + max++;
> >      > + }
> >      > + struct epoll_event *evs = (struct epoll_event *) alloca(max *
> >     sizeof(evs[0]));
> >      > + int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> >      > + for (int i = 0; i < n; i++) {
> >      > + struct mg_connection *c = (struct mg_connection *)
> >     evs[i].data.ptr;
> >      > + if (evs[i].events & EPOLLERR) {
> >      > + mg_error(c, "socket error");
> >      > + } else if (c->is_readable == 0) {
> >      > + bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> >      > + bool wr = evs[i].events & EPOLLOUT;
> >      > + c->is_readable = can_read(c) && rd ? 1U : 0;
> >      > + c->is_writable = can_write(c) && wr ? 1U : 0;
> >      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> >      > + }
> >      > + }
> >      > + (void) skip_iotest;
> >      > +#elif MG_ENABLE_POLL
> >      > + nfds_t n = 0;
> >      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) n++;
> >      > + struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> >      > + memset(fds, 0, n * sizeof(fds[0]));
> >      > + n = 0;
> >      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > + c->is_readable = c->is_writable = 0;
> >      > + if (skip_iotest(c)) {
> >      > + // Socket not valid, ignore
> >      > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> >      > + ms = 1; // Don't wait if TLS is ready
> >      > } else {
> >      > - ok = 0;
> >      > - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
> >     new_size));
> >      > + fds[n].fd = FD(c);
> >      > + if (can_read(c)) fds[n].events |= POLLIN;
> >      > + if (can_write(c)) fds[n].events |= POLLOUT;
> >      > + if (c->is_closing) ms = 1;
> >      > + n++;
> >      > }
> >      > }
> >      > - return ok;
> >      > -}
> >      > -
> >      > -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
> >      > - io->buf = NULL;
> >      > - io->align = align;
> >      > - io->size = io->len = 0;
> >      > - return mg_iobuf_resize(io, size);
> >      > -}
> >      >
> >      > -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
> >     *buf,
> >      > - size_t len) {
> >      > - size_t new_size = roundup(io->len + len, io->align);
> >      > - mg_iobuf_resize(io, new_size); // Attempt to resize
> >      > - if (new_size != io->size) len = 0; // Resize failure, append
> >     nothing
> >      > - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
> >     io->len - ofs);
> >      > - if (buf != NULL) memmove(io->buf + ofs, buf, len);
> >      > - if (ofs > io->len) io->len += ofs - io->len;
> >      > - io->len += len;
> >      > - return len;
> >      > -}
> >      > + // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> >      > + if (poll(fds, n, ms) < 0) {
> >      > +#if MG_ARCH == MG_ARCH_WIN32
> >      > + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
> >      > +#endif
> >      > + memset(fds, 0, n * sizeof(fds[0]));
> >      > + }
> >      > + n = 0;
> >      > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > + if (skip_iotest(c)) {
> >      > + // Socket not valid, ignore
> >      > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
> >      > + c->is_readable = 1;
> >      > + } else {
> >      > + if (fds[n].revents & POLLERR) {
> >      > + mg_error(c, "socket error");
> >      > + } else {
> >      > + c->is_readable =
> >      > + (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> >      > + c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> >      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> >      > + }
> >      > + n++;
> >      > + }
> >      > + }
> >      > +#else
> >      > + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
> >     {0, 0}, *tvp;
> >      > + struct mg_connection *c;
> >      > + fd_set rset, wset, eset;
> >      > + MG_SOCKET_TYPE maxfd = 0;
> >      > + int rc;
> >      >
> >      > -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
> >      > - if (ofs > io->len) ofs = io->len;
> >      > - if (ofs + len > io->len) len = io->len - ofs;
> >      > - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
> >     io->len - ofs - len);
> >      > - if (io->buf) zeromem(io->buf + io->len - len, len);
> >      > - io->len -= len;
> >      > - return len;
> >      > -}
> >      > + FD_ZERO(&rset);
> >      > + FD_ZERO(&wset);
> >      > + FD_ZERO(&eset);
> >      > + tvp = ms < 0 ? NULL : &tv;
> >      > + for (c = mgr->conns; c != NULL; c = c->next) {
> >      > + c->is_readable = c->is_writable = 0;
> >      > + if (skip_iotest(c)) continue;
> >      > + FD_SET(FD(c), &eset);
> >      > + if (can_read(c)) FD_SET(FD(c), &rset);
> >      > + if (can_write(c)) FD_SET(FD(c), &wset);
> >      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
> >      > + if (FD(c) > maxfd) maxfd = FD(c);
> >      > + if (c->is_closing) ms = 1;
> >      > + }
> >      >
> >      > -void mg_iobuf_free(struct mg_iobuf *io) {
> >      > - mg_iobuf_resize(io, 0);
> >      > -}
> >      > + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) <
> >     0) {
> >      > +#if MG_ARCH == MG_ARCH_WIN32
> >      > + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
> >     sockets
> >      > +#else
> >      > + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
> >      > +#endif
> >      > + FD_ZERO(&rset);
> >      > + FD_ZERO(&wset);
> >      > + FD_ZERO(&eset);
> >      > + }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/json.c"
> >      > + for (c = mgr->conns; c != NULL; c = c->next) {
> >      > + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> >      > + mg_error(c, "socket error");
> >      > + } else {
> >      > + c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
> >     &rset);
> >      > + c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
> >     &wset);
> >      > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
> >      > + }
> >      > + }
> >      > #endif
> >      > +}
> >      >
> >      > +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
> >      > + socklen_t n = sizeof(usa[0].sin);
> >      > + bool success = false;
> >      >
> >      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > + (void) memset(&usa[0], 0, sizeof(usa[0]));
> >      > + usa[0].sin.sin_family = AF_INET;
> >      > + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
> >     127.0.0.1
> >      > + usa[1] = usa[0];
> >      >
> >      > -
> >      > -static const char *escapeseq(int esc) {
> >      > - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
> >      > + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
> >     MG_INVALID_SOCKET &&
> >      > + (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> >      > + bind(sp[0], &usa[0].sa, n) == 0 && //
> >      > + bind(sp[1], &usa[1].sa, n) == 0 && //
> >      > + getsockname(sp[0], &usa[0].sa, &n) == 0 && //
> >      > + getsockname(sp[1], &usa[1].sa, &n) == 0 && //
> >      > + connect(sp[0], &usa[1].sa, n) == 0 && //
> >      > + connect(sp[1], &usa[0].sa, n) == 0) { //
> >      > + success = true;
> >      > + }
> >      > + if (!success) {
> >      > + if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> >      > + if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> >      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > + }
> >      > + return success;
> >      > }
> >      >
> >      > -static char json_esc(int c, int esc) {
> >      > - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
> >      > - for (p = esc1; *p != '\0'; p++) {
> >      > - if (*p == c) return esc2[p - esc1];
> >      > +// mg_wakeup() event handler
> >      > +static void wufn(struct mg_connection *c, int ev, void *ev_data) {
> >      > + if (ev == MG_EV_READ) {
> >      > + unsigned long *id = (unsigned long *) c->recv.buf;
> >      > + // MG_INFO(("Got data"));
> >      > + // mg_hexdump(c->recv.buf, c->recv.len);
> >      > + if (c->recv.len >= sizeof(*id)) {
> >      > + struct mg_connection *t;
> >      > + for (t = c->mgr->conns; t != NULL; t = t->next) {
> >      > + if (t->id == *id) {
> >      > + struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
> >      > + c->recv.len - sizeof(*id));
> >      > + mg_call(t, MG_EV_WAKEUP, &data);
> >      > + }
> >      > + }
> >      > + }
> >      > + c->recv.len = 0; // Consume received data
> >      > + } else if (ev == MG_EV_CLOSE) {
> >      > + closesocket(c->mgr->pipe); // When we're closing, close the other
> >      > + c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too
> >      > }
> >      > - return 0;
> >      > + (void) ev_data;
> >      > }
> >      >
> >      > -static int mg_pass_string(const char *s, int len) {
> >      > - int i;
> >      > - for (i = 0; i < len; i++) {
> >      > - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
> >      > - i++;
> >      > - } else if (s[i] == '\0') {
> >      > - return MG_JSON_INVALID;
> >      > - } else if (s[i] == '"') {
> >      > - return i;
> >      > +bool mg_wakeup_init(struct mg_mgr *mgr) {
> >      > + bool ok = false;
> >      > + if (mgr->pipe == MG_INVALID_SOCKET) {
> >      > + union usa usa[2];
> >      > + MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> >      > + struct mg_connection *c = NULL;
> >      > + if (!mg_socketpair(sp, usa)) {
> >      > + MG_ERROR(("Cannot create socket pair"));
> >      > + } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) ==
> >     NULL) {
> >      > + closesocket(sp[0]);
> >      > + closesocket(sp[1]);
> >      > + sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > + } else {
> >      > + tomgaddr(&usa[0], &c->rem, false);
> >      > + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
> >     sp[0]));
> >      > + mgr->pipe = sp[0];
> >      > + ok = true;
> >      > }
> >      > }
> >      > - return MG_JSON_INVALID;
> >      > + return ok;
> >      > }
> >      >
> >      > -static double mg_atod(const char *p, int len, int *numlen) {
> >      > - double d = 0.0;
> >      > - int i = 0, sign = 1;
> >      > -
> >      > - // Sign
> >      > - if (i < len && *p == '-') {
> >      > - sign = -1, i++;
> >      > - } else if (i < len && *p == '+') {
> >      > - i++;
> >      > +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const
> >     void *buf,
> >      > + size_t len) {
> >      > + if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
> >      > + char *extended_buf = (char *) alloca(len + sizeof(conn_id));
> >      > + memcpy(extended_buf, &conn_id, sizeof(conn_id));
> >      > + memcpy(extended_buf + sizeof(conn_id), buf, len);
> >      > + send(mgr->pipe, extended_buf, len + sizeof(conn_id),
> >     MSG_NONBLOCKING);
> >      > + return true;
> >      > }
> >      > + return false;
> >      > +}
> >      >
> >      > - // Decimal
> >      > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> >      > - d *= 10.0;
> >      > - d += p[i] - '0';
> >      > - }
> >      > - d *= sign;
> >      > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> >      > + struct mg_connection *c, *tmp;
> >      > + uint64_t now;
> >      >
> >      > - // Fractional
> >      > - if (i < len && p[i] == '.') {
> >      > - double frac = 0.0, base = 0.1;
> >      > - i++;
> >      > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
> >      > - frac += base * (p[i] - '0');
> >      > - base /= 10.0;
> >      > + mg_iotest(mgr, ms);
> >      > + now = mg_millis();
> >      > + mg_timer_poll(&mgr->timers, now);
> >      > +
> >      > + for (c = mgr->conns; c != NULL; c = tmp) {
> >      > + bool is_resp = c->is_resp;
> >      > + tmp = c->next;
> >      > + mg_call(c, MG_EV_POLL, &now);
> >      > + if (is_resp && !c->is_resp) {
> >      > + long n = 0;
> >      > + mg_call(c, MG_EV_READ, &n);
> >      > + }
> >      > + MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
> >      > + c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
> >      > + c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
> >      > + c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
> >      > + c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
> >      > + if (c->is_resolving || c->is_closing) {
> >      > + // Do nothing
> >      > + } else if (c->is_listening && c->is_udp == 0) {
> >      > + if (c->is_readable) accept_conn(mgr, c);
> >      > + } else if (c->is_connecting) {
> >      > + if (c->is_readable || c->is_writable) connect_conn(c);
> >      > + //} else if (c->is_tls_hs) {
> >      > + // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> >      > + } else {
> >      > + if (c->is_readable) read_conn(c);
> >      > + if (c->is_writable) write_conn(c);
> >      > }
> >      > - d += frac * sign;
> >      > - }
> >      >
> >      > - // Exponential
> >      > - if (i < len && (p[i] == 'e' || p[i] == 'E')) {
> >      > - int j, exp = 0, minus = 0;
> >      > - i++;
> >      > - if (i < len && p[i] == '-') minus = 1, i++;
> >      > - if (i < len && p[i] == '+') i++;
> >      > - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
> >      > - exp = exp * 10 + (p[i++] - '0');
> >      > - if (minus) exp = -exp;
> >      > - for (j = 0; j < exp; j++) d *= 10.0;
> >      > - for (j = 0; j < -exp; j++) d /= 10.0;
> >      > + if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> >      > + if (c->is_closing) close_conn(c);
> >      > }
> >      > -
> >      > - if (numlen != NULL) *numlen = i;
> >      > - return d;
> >      > }
> >      > +#endif
> >      >
> >      > -int mg_json_get(struct mg_str json, const char *path, int
> >     *toklen) {
> >      > - const char *s = json.ptr;
> >      > - int len = (int) json.len;
> >      > - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
> >     S_VALUE;
> >      > - unsigned char nesting[MG_JSON_MAX_DEPTH];
> >      > - int i = 0; // Current offset in `s`
> >      > - int j = 0; // Offset in `s` we're looking for (return value)
> >      > - int depth = 0; // Current depth (nesting level)
> >      > - int ed = 0; // Expected depth
> >      > - int pos = 1; // Current position in `path`
> >      > - int ci = -1, ei = -1; // Current and expected index in array
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/ssi.c"
> >      > +#endif
> >      >
> >      > - if (toklen) *toklen = 0;
> >      > - if (path[0] != '$') return MG_JSON_INVALID;
> >      >
> >      > -#define MG_CHECKRET(x) \
> >      > - do { \
> >      > - if (depth == ed && path[pos] == '\0' && ci == ei) { \
> >      > - if (toklen) *toklen = i - j + 1; \
> >      > - return j; \
> >      > - } \
> >      > - } while (0)
> >      >
> >      > -// In the ascii table, the distance between `[` and `]` is 2.
> >      > -// Ditto for `{` and `}`. Hence +2 in the code below.
> >      > -#define MG_EOO(x) \
> >      > - do { \
> >      > - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
> >      > - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
> >      > - depth--; \
> >      > - MG_CHECKRET(x); \
> >      > - } while (0)
> >      >
> >      > - for (i = 0; i < len; i++) {
> >      > - unsigned char c = ((unsigned char *) s)[i];
> >      > - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
> >      > - switch (expecting) {
> >      > - case S_VALUE:
> >      > - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
> >     ci, ei);
> >      > - if (depth == ed) j = i;
> >      > - if (c == '{') {
> >      > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> >      > - if (depth == ed && path[pos] == '.' && ci == ei) {
> >      > - // If we start the object, reset array indices
> >      > - ed++, pos++, ci = ei = -1;
> >      > - }
> >      > - nesting[depth++] = c;
> >      > - expecting = S_KEY;
> >      > - break;
> >      > - } else if (c == '[') {
> >      > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
> >      > - if (depth == ed && path[pos] == '[' && ei == ci) {
> >      > - ed++, pos++, ci = 0;
> >      > - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
> >      > - ei *= 10;
> >      > - ei += path[pos] - '0';
> >      > - }
> >      > - if (path[pos] != 0) pos++;
> >      > - }
> >      > - nesting[depth++] = c;
> >      > - break;
> >      > - } else if (c == ']' && depth > 0) { // Empty array
> >      > - MG_EOO(']');
> >      > - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
> >     == 0) {
> >      > - i += 3;
> >      > - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
> >     == 0) {
> >      > - i += 3;
> >      > - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
> >     == 0) {
> >      > - i += 4;
> >      > - } else if (c == '-' || ((c >= '0' && c <= '9'))) {
> >      > - int numlen = 0;
> >      > - mg_atod(&s[i], len - i, &numlen);
> >      > - i += numlen - 1;
> >      > - } else if (c == '"') {
> >      > - int n = mg_pass_string(&s[i + 1], len - i - 1);
> >      > - if (n < 0) return n;
> >      > - i += n + 1;
> >      > - } else {
> >      > - return MG_JSON_INVALID;
> >      > - }
> >      > - MG_CHECKRET('V');
> >      > - if (depth == ed && ei >= 0) ci++;
> >      > - expecting = S_COMMA_OR_EOO;
> >      > - break;
> >      > +#ifndef MG_MAX_SSI_DEPTH
> >      > +#define MG_MAX_SSI_DEPTH 5
> >      > +#endif
> >      >
> >      > - case S_KEY:
> >      > - if (c == '"') {
> >      > - int n = mg_pass_string(&s[i + 1], len - i - 1);
> >      > - if (n < 0) return n;
> >      > - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
> >      > - if (depth < ed) return MG_JSON_NOT_FOUND;
> >      > - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
> >      > - // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
> >      > - // &s[i + 1], n, depth, ed);
> >      > - // NOTE(cpq): in the check sequence below is important.
> >      > - // strncmp() must go first: it fails fast if the remaining
> >     length of
> >      > - // the path is smaller than `n`.
> >      > - if (depth == ed && path[pos - 1] == '.' &&
> >      > - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
> >      > - (path[pos + n] == '\0' || path[pos + n] == '.' ||
> >      > - path[pos + n] == '[')) {
> >      > - pos += n;
> >      > - }
> >      > - i += n + 1;
> >      > - expecting = S_COLON;
> >      > - } else if (c == '}') { // Empty object
> >      > - MG_EOO('}');
> >      > - expecting = S_COMMA_OR_EOO;
> >      > - } else {
> >      > - return MG_JSON_INVALID;
> >      > - }
> >      > - break;
> >      > +#ifndef MG_SSI_BUFSIZ
> >      > +#define MG_SSI_BUFSIZ 1024
> >      > +#endif
> >      >
> >      > - case S_COLON:
> >      > - if (c == ':') {
> >      > - expecting = S_VALUE;
> >      > +#if MG_ENABLE_SSI
> >      > +static char *mg_ssi(const char *path, const char *root, int
> >     depth) {
> >      > + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> >      > + FILE *fp = fopen(path, "rb");
> >      > + if (fp != NULL) {
> >      > + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> >      > + int ch, intag = 0;
> >      > + size_t len = 0;
> >      > + buf[0] = arg[0] = '\0';
> >      > + while ((ch = fgetc(fp)) != EOF) {
> >      > + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
> >     == '-') {
> >      > + buf[len++] = (char) (ch & 0xff);
> >      > + buf[len] = '\0';
> >      > + if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> >      > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> >      > + *p = (char *) path + strlen(path), *data;
> >      > + while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> >      > + mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
> >     arg);
> >      > + if (depth < MG_MAX_SSI_DEPTH &&
> >      > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> >      > + mg_iobuf_add(&b, b.len, data, strlen(data));
> >      > + free(data);
> >      > + } else {
> >      > + MG_ERROR(("%s: file=%s error or too deep", path, arg));
> >      > + }
> >      > + } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> >      > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> >      > + mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> >      > + if (depth < MG_MAX_SSI_DEPTH &&
> >      > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> >      > + mg_iobuf_add(&b, b.len, data, strlen(data));
> >      > + free(data);
> >      > + } else {
> >      > + MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> >      > + }
> >      > } else {
> >      > - return MG_JSON_INVALID;
> >      > + // Unknown SSI tag
> >      > + MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> >      > + mg_iobuf_add(&b, b.len, buf, len);
> >      > }
> >      > - break;
> >      > -
> >      > - case S_COMMA_OR_EOO:
> >      > - if (depth <= 0) {
> >      > - return MG_JSON_INVALID;
> >      > - } else if (c == ',') {
> >      > - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
> >      > - } else if (c == ']' || c == '}') {
> >      > - MG_EOO('O');
> >      > - if (depth == ed && ei >= 0) ci++;
> >      > - } else {
> >      > - return MG_JSON_INVALID;
> >      > + intag = 0;
> >      > + len = 0;
> >      > + } else if (ch == '<') {
> >      > + intag = 1;
> >      > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> >      > + len = 0;
> >      > + buf[len++] = (char) (ch & 0xff);
> >      > + } else if (intag) {
> >      > + if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> >      > + intag = 0;
> >      > + } else if (len >= sizeof(buf) - 2) {
> >      > + MG_ERROR(("%s: SSI tag is too large", path));
> >      > + len = 0;
> >      > }
> >      > - break;
> >      > + buf[len++] = (char) (ch & 0xff);
> >      > + } else {
> >      > + buf[len++] = (char) (ch & 0xff);
> >      > + if (len >= sizeof(buf)) {
> >      > + mg_iobuf_add(&b, b.len, buf, len);
> >      > + len = 0;
> >      > + }
> >      > + }
> >      > }
> >      > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> >      > + if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
> >      > + fclose(fp);
> >      > }
> >      > - return MG_JSON_NOT_FOUND;
> >      > + (void) depth;
> >      > + (void) root;
> >      > + return (char *) b.buf;
> >      > }
> >      >
> >      > -bool mg_json_get_num(struct mg_str json, const char *path,
> >     double *v) {
> >      > - int n, toklen, found = 0;
> >      > - if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
> >      > - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <=
> >     '9'))) {
> >      > - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
> >      > - found = 1;
> >      > - }
> >      > - return found;
> >      > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> >      > + const char *fullpath) {
> >      > + const char *headers = "Content-Type: text/html;
> >     charset=utf-8\r\n";
> >      > + char *data = mg_ssi(fullpath, root, 0);
> >      > + mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> >      > + free(data);
> >      > +}
> >      > +#else
> >      > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> >      > + const char *fullpath) {
> >      > + mg_http_reply(c, 501, NULL, "SSI not enabled");
> >      > + (void) root, (void) fullpath;
> >      > }
> >      > +#endif
> >      >
> >      > -bool mg_json_get_bool(struct mg_str json, const char *path, bool
> >     *v) {
> >      > - int found = 0, off = mg_json_get(json, path, NULL);
> >      > - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
> >      > - if (v != NULL) *v = json.ptr[off] == 't';
> >      > - found = 1;
> >      > - }
> >      > - return found;
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/str.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +struct mg_str mg_str_s(const char *s) {
> >      > + struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
> >      > + return str;
> >      > }
> >      >
> >      > -static bool json_unescape(const char *s, size_t len, char *to,
> >     size_t n) {
> >      > - size_t i, j;
> >      > - for (i = 0, j = 0; i < len && j < n; i++, j++) {
> >      > - if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
> >      > - // \uXXXX escape. We could process a simple one-byte chars
> >      > - // \u00xx from the ASCII range. More complex chars would require
> >      > - // dragging in a UTF8 library, which is too much for us
> >      > - if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up
> >      > - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i +
> >     4, 2);
> >      > +struct mg_str mg_str_n(const char *s, size_t n) {
> >      > + struct mg_str str = {(char *) s, n};
> >      > + return str;
> >      > +}
> >      >
> >      > - i += 5;
> >      > - } else if (s[i] == '\\' && i + 1 < len) {
> >      > - char c = json_esc(s[i + 1], 0);
> >      > - if (c == 0) return false;
> >      > - to[j] = c;
> >      > - i++;
> >      > - } else {
> >      > - to[j] = s[i];
> >      > - }
> >      > +static int mg_tolc(char c) {
> >      > + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
> >      > +}
> >      > +
> >      > +int mg_casecmp(const char *s1, const char *s2) {
> >      > + int diff = 0;
> >      > + do {
> >      > + int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
> >      > + diff = c - d;
> >      > + } while (diff == 0 && s1[-1] != '\0');
> >      > + return diff;
> >      > +}
> >      > +
> >      > +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> >      > + size_t i = 0;
> >      > + while (i < str1.len && i < str2.len) {
> >      > + int c1 = str1.buf[i];
> >      > + int c2 = str2.buf[i];
> >      > + if (c1 < c2) return -1;
> >      > + if (c1 > c2) return 1;
> >      > + i++;
> >      > }
> >      > - if (j >= n) return false;
> >      > - if (n > 0) to[j] = '\0';
> >      > - return true;
> >      > + if (i < str1.len) return 1;
> >      > + if (i < str2.len) return -1;
> >      > + return 0;
> >      > }
> >      >
> >      > -char *mg_json_get_str(struct mg_str json, const char *path) {
> >      > - char *result = NULL;
> >      > - int len = 0, off = mg_json_get(json, path, &len);
> >      > - if (off >= 0 && len > 1 && json.ptr[off] == '"') {
> >      > - if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
> >      > - !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
> >      > - (size_t) len)) {
> >      > - free(result);
> >      > - result = NULL;
> >      > - }
> >      > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
> >     str2) {
> >      > + size_t i = 0;
> >      > + while (i < str1.len && i < str2.len) {
> >      > + int c1 = mg_tolc(str1.buf[i]);
> >      > + int c2 = mg_tolc(str2.buf[i]);
> >      > + if (c1 < c2) return -1;
> >      > + if (c1 > c2) return 1;
> >      > + i++;
> >      > }
> >      > - return result;
> >      > + if (i < str1.len) return 1;
> >      > + if (i < str2.len) return -1;
> >      > + return 0;
> >      > }
> >      >
> >      > -char *mg_json_get_b64(struct mg_str json, const char *path, int
> >     *slen) {
> >      > - char *result = NULL;
> >      > - int len = 0, off = mg_json_get(json, path, &len);
> >      > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> >      > - (result = (char *) calloc(1, (size_t) len)) != NULL) {
> >      > - int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
> >      > - if (slen != NULL) *slen = k;
> >      > +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
> >     *caps) {
> >      > + size_t i = 0, j = 0, ni = 0, nj = 0;
> >      > + if (caps) caps->buf = NULL, caps->len = 0;
> >      > + while (i < p.len || j < s.len) {
> >      > + if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] ==
> >     p.buf[i])) {
> >      > + if (caps == NULL) {
> >      > + } else if (p.buf[i] == '?') {
> >      > + caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap
> >      > + caps++, caps->buf = NULL, caps->len = 0; // Init next cap
> >      > + } else if (caps->buf != NULL && caps->len == 0) {
> >      > + caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize
> >     current cap
> >      > + caps++, caps->len = 0, caps->buf = NULL; // Init next cap
> >      > + }
> >      > + i++, j++;
> >      > + } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
> >      > + if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j];
> >     // Init cap
> >      > + ni = i++, nj = j + 1;
> >      > + } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' ||
> >     s.buf[j] != '/')) {
> >      > + i = ni, j = nj;
> >      > + if (caps && caps->buf == NULL && caps->len == 0) {
> >      > + caps--, caps->len = 0; // Restart previous cap
> >      > + }
> >      > + } else {
> >      > + return false;
> >      > + }
> >      > }
> >      > - return result;
> >      > + if (caps && caps->buf && caps->len == 0) {
> >      > + caps->len = (size_t) (&s.buf[j] - caps->buf);
> >      > + }
> >      > + return true;
> >      > }
> >      >
> >      > -char *mg_json_get_hex(struct mg_str json, const char *path, int
> >     *slen) {
> >      > - char *result = NULL;
> >      > - int len = 0, off = mg_json_get(json, path, &len);
> >      > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
> >      > - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
> >      > - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *)
> >     result);
> >      > - result[len / 2 - 1] = '\0';
> >      > - if (slen != NULL) *slen = len / 2 - 1;
> >      > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
> >     *b, char sep) {
> >      > + if (s.len == 0 || s.buf == NULL) {
> >      > + return false; // Empty string, nothing to span - fail
> >      > + } else {
> >      > + size_t len = 0;
> >      > + while (len < s.len && s.buf[len] != sep) len++; // Find separator
> >      > + if (a) *a = mg_str_n(s.buf, len); // Init a
> >      > + if (b) *b = mg_str_n(s.buf + len, s.len - len); // Init b
> >      > + if (b && len < s.len) b->buf++, b->len--; // Skip separator
> >      > + return true;
> >      > }
> >      > - return result;
> >      > }
> >      >
> >      > -long mg_json_get_long(struct mg_str json, const char *path, long
> >     dflt) {
> >      > - double dv;
> >      > - long result = dflt;
> >      > - if (mg_json_get_num(json, path, &dv)) result = (long) dv;
> >      > - return result;
> >      > +bool mg_str_to_num(struct mg_str str, int base, void *val,
> >     size_t val_len) {
> >      > + size_t i = 0, ndigits = 0;
> >      > + uint64_t max = val_len == sizeof(uint8_t) ? 0xFF
> >      > + : val_len == sizeof(uint16_t) ? 0xFFFF
> >      > + : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
> >      > + : (uint64_t) ~0;
> >      > + uint64_t result = 0;
> >      > + if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return
> >     false;
> >      > + if (base == 0 && str.len >= 2) {
> >      > + if (str.buf[i] == '0') {
> >      > + i++;
> >      > + base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
> >      > + if (base != 10) ++i;
> >      > + } else {
> >      > + base = 10;
> >      > + }
> >      > + }
> >      > + switch (base) {
> >      > + case 2:
> >      > + while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
> >      > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
> >      > + if (result > max/2) return false; // Overflow
> >      > + result *= 2;
> >      > + if (result > max - digit) return false; // Overflow
> >      > + result += digit;
> >      > + i++, ndigits++;
> >      > + }
> >      > + break;
> >      > + case 10:
> >      > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
> >      > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
> >      > + if (result > max/10) return false; // Overflow
> >      > + result *= 10;
> >      > + if (result > max - digit) return false; // Overflow
> >      > + result += digit;
> >      > + i++, ndigits++;
> >      > + }
> >      > + break;
> >      > + case 16:
> >      > + while (i < str.len) {
> >      > + char c = str.buf[i];
> >      > + uint64_t digit = (c >= '0' && c <= '9') ? (uint64_t) (c - '0')
> >      > + : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
> >      > + : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
> >      > + : (uint64_t) ~0;
> >      > + if (digit == (uint64_t) ~0) break;
> >      > + if (result > max/16) return false; // Overflow
> >      > + result *= 16;
> >      > + if (result > max - digit) return false; // Overflow
> >      > + result += digit;
> >      > + i++, ndigits++;
> >      > + }
> >      > + break;
> >      > + default:
> >      > + return false;
> >      > + }
> >      > + if (ndigits == 0) return false;
> >      > + if (i != str.len) return false;
> >      > + if (val_len == 1) {
> >      > + *((uint8_t *) val) = (uint8_t) result;
> >      > + } else if (val_len == 2) {
> >      > + *((uint16_t *) val) = (uint16_t) result;
> >      > + } else if (val_len == 4) {
> >      > + *((uint32_t *) val) = (uint32_t) result;
> >      > + } else {
> >      > + *((uint64_t *) val) = (uint64_t) result;
> >      > + }
> >      > + return true;
> >      > }
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/log.c"
> >      > +#line 1 "src/timer.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      > +#define MG_TIMER_CALLED 4
> >      >
> >      > -
> >      > -static void default_logger(char c, void *param) {
> >      > - putchar(c);
> >      > - (void) c, (void) param;
> >      > -}
> >      > -
> >      > -static int s_level = MG_LL_INFO;
> >      > -static mg_pfn_t s_log_func = default_logger;
> >      > -static void *s_log_func_param = NULL;
> >      > -
> >      > -void mg_log_set_fn(mg_pfn_t fn, void *param) {
> >      > - s_log_func = fn;
> >      > - s_log_func_param = param;
> >      > -}
> >      > -
> >      > -static void logc(unsigned char c) {
> >      > - s_log_func((char) c, s_log_func_param);
> >      > -}
> >      > -
> >      > -static void logs(const char *buf, size_t len) {
> >      > - size_t i;
> >      > - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
> >      > -}
> >      > -
> >      > -void mg_log_set(int log_level) {
> >      > - MG_DEBUG(("Setting log level to %d", log_level));
> >      > - s_level = log_level;
> >      > -}
> >      > -
> >      > -bool mg_log_prefix(int level, const char *file, int line, const
> >     char *fname) {
> >      > - if (level <= s_level) {
> >      > - const char *p = strrchr(file, '/');
> >      > - char buf[41];
> >      > - size_t n;
> >      > - if (p == NULL) p = strrchr(file, '\\');
> >      > - n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s",
> >     mg_millis(), level,
> >      > - p == NULL ? file : p + 1, line, fname);
> >      > - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
> >      > - while (n < sizeof(buf)) buf[n++] = ' ';
> >      > - logs(buf, n - 1);
> >      > - return true;
> >      > - } else {
> >      > - return false;
> >      > - }
> >      > +void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
> >     uint64_t ms,
> >      > + unsigned flags, void (*fn)(void *), void *arg) {
> >      > + t->id = 0, t->period_ms = ms, t->expire = 0;
> >      > + t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> >      > + *head = t;
> >      > }
> >      >
> >      > -void mg_log(const char *fmt, ...) {
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
> >      > - va_end(ap);
> >      > - logc((unsigned char) '\n');
> >      > +void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> >      > + while (*head && *head != t) head = &(*head)->next;
> >      > + if (*head) *head = t->next;
> >      > }
> >      >
> >      > -static unsigned char nibble(unsigned c) {
> >      > - return (unsigned char) (c < 10 ? c + '0' : c + 'W');
> >      > +// t: expiration time, prd: period, now: current time. Return
> >     true if expired
> >      > +bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> >      > + if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
> >      > + if (*t == 0) *t = now + prd; // Firt poll? Set expiration
> >      > + if (*t > now) return false; // Not expired yet, return
> >      > + *t = (now - *t) > prd ? now + prd : *t + prd; // Next
> >     expiration time
> >      > + return true; // Expired, return true
> >      > }
> >      >
> >      > -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
> >      > -void mg_hexdump(const void *buf, size_t len) {
> >      > - const unsigned char *p = (const unsigned char *) buf;
> >      > - unsigned char ascii[16], alen = 0;
> >      > - size_t i;
> >      > - for (i = 0; i < len; i++) {
> >      > - if ((i % 16) == 0) {
> >      > - // Print buffered ascii chars
> >      > - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
> >     alen = 0;
> >      > - // Print hex address, then \t
> >      > - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
> >      > - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
> >      > - }
> >      > - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
> >     nibbles, e.g. c5
> >      > - logc(' '); // Space after hex number
> >      > - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
> >     buf
> >      > +void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> >      > + struct mg_timer *t, *tmp;
> >      > + for (t = *head; t != NULL; t = tmp) {
> >      > + bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> >      > + !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
> >      > + bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> >      > + tmp = t->next;
> >      > + if (!once && !expired) continue;
> >      > + if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
> >     MG_TIMER_CALLED)) {
> >      > + t->fn(t->arg);
> >      > + }
> >      > + t->flags |= MG_TIMER_CALLED;
> >      > }
> >      > - while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
> >      > - logs(" ", 2), logs((char *) ascii, 16), logc('\n');
> >      > }
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/md5.c"
> >      > +#line 1 "src/tls_aes128.c"
> >      > #endif
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
> >     THE GOOD OF ALL
> >      > + *
> >      > + * This is a simple and straightforward implementation of the
> >     AES Rijndael
> >      > + * 128-bit block cipher designed by Vincent Rijmen and Joan
> >     Daemen. The focus
> >      > + * of this work was correctness & accuracy. It is written in 'C'
> >     without any
> >      > + * particular focus upon optimization or speed. It should be
> >     endian (memory
> >      > + * byte order) neutral since the few places that care are
> >     handled explicitly.
> >      > + *
> >      > + * This implementation of Rijndael was created by Steven M.
> >     Gibson of GRC.com.
> >      > + *
> >      > + * It is intended for general purpose use, but was written in
> >     support of GRC's
> >      > + * reference implementation of the SQRL (Secure Quick Reliable
> >     Login) client.
> >      > + *
> >      > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> >     <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
> >      > + *
> >      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
> >     WARRANTY MADE
> >      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
> >     YOUR OWN RISK.
> >      > + *
> >      > +
> >     *******************************************************************************/
> >      > +
> >      >
> >     +/******************************************************************************/
> >      > +#define AES_DECRYPTION 1 // whether AES decryption is supported
> >      >
> >     +/******************************************************************************/
> >      > +
> >      > +#define MG_ENCRYPT 1 // specify whether we're encrypting
> >      > +#define MG_DECRYPT 0 // or decrypting
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#if MG_TLS == MG_TLS_BUILTIN
> >      >
> >     +/******************************************************************************
> >      > + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
> >      > +
> >     ******************************************************************************/
> >      > +static void aes_init_keygen_tables(void);
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * AES_SETKEY : called to expand the key for encryption or
> >     decryption
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_setkey(aes_context *ctx, // pointer to context
> >      > + int mode, // 1 or 0 for Encrypt/Decrypt
> >      > + const uchar *key, // AES input key
> >      > + uint keysize); // size in bytes (must be 16, 24, 32 for
> >      > + // 128, 192 or 256-bit keys respectively)
> >      > + // returns 0 for success
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block
> >     of data
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_cipher(aes_context *ctx, // pointer to context
> >      > + const uchar input[16], // 128-bit block to en/decipher
> >      > + uchar output[16]); // 128-bit output result block
> >      > + // returns 0 for success
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * GCM_CONTEXT : GCM context / holds keytables, instance data,
> >     and AES ctx
> >      > +
> >     ******************************************************************************/
> >      > +typedef struct {
> >      > + int mode; // cipher direction: encrypt/decrypt
> >      > + uint64_t len; // cipher data length processed so far
> >      > + uint64_t add_len; // total add data length
> >      > + uint64_t HL[16]; // precalculated lo-half HTable
> >      > + uint64_t HH[16]; // precalculated hi-half HTable
> >      > + uchar base_ectr[16]; // first counter-mode cipher output for tag
> >      > + uchar y[16]; // the current cipher-input IV|Counter value
> >      > + uchar buf[16]; // buf working value
> >      > + aes_context aes_ctx; // cipher context used
> >      > +} gcm_context;
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * GCM_SETKEY : sets the GCM (and AES) keying material for use
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_setkey(
> >      > + gcm_context *ctx, // caller-provided context ptr
> >      > + const uchar *key, // pointer to cipher key
> >      > + const uint keysize // size in bytes (must be 16, 24, 32 for
> >      > + // 128, 192 or 256-bit keys respectively)
> >      > +); // returns 0 for success
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_CRYPT_AND_TAG
> >      > + *
> >      > + * This either encrypts or decrypts the user-provided data and,
> >     either
> >      > + * way, generates an authentication tag of the requested length.
> >     It must be
> >      > + * called with a GCM context whose key has already been set with
> >     GCM_SETKEY.
> >      > + *
> >      > + * The user would typically call this explicitly to ENCRYPT a
> >     buffer of data
> >      > + * and optional associated data, and produce its an
> >     authentication tag.
> >      > + *
> >      > + * To reverse the process the user would typically call the
> >     companion
> >      > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
> >     user-provided
> >      > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
> >     function
> >      > + * to perform its decryption and tag generation, which it then
> >     compares.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_crypt_and_tag(
> >      > + gcm_context *ctx, // gcm context with key already setup
> >      > + int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
> >      > + const uchar *iv, // pointer to the 12-byte initialization vector
> >      > + size_t iv_len, // byte length if the IV. should always be 12
> >      > + const uchar *add, // pointer to the non-ciphered additional data
> >      > + size_t add_len, // byte length of the additional AEAD data
> >      > + const uchar *input, // pointer to the cipher data source
> >      > + uchar *output, // pointer to the cipher data destination
> >      > + size_t length, // byte length of the cipher data
> >      > + uchar *tag, // pointer to the tag to be generated
> >      > + size_t tag_len); // byte length of the tag to be generated
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_START
> >      > + *
> >      > + * Given a user-provided GCM context, this initializes it, sets
> >     the encryption
> >      > + * mode, and preprocesses the initialization vector and
> >     additional AEAD data.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_start(
> >      > + gcm_context *ctx, // pointer to user-provided GCM context
> >      > + int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0)
> >      > + const uchar *iv, // pointer to initialization vector
> >      > + size_t iv_len, // IV length in bytes (should == 12)
> >      > + const uchar *add, // pointer to additional AEAD data (NULL if
> >     none)
> >      > + size_t add_len); // length of additional AEAD data (bytes)
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_UPDATE
> >      > + *
> >      > + * This is called once or more to process bulk plaintext or
> >     ciphertext data.
> >      > + * We give this some number of bytes of input and it returns the
> >     same number
> >      > + * of output bytes. If called multiple times (which is fine) all
> >     but the final
> >      > + * invocation MUST be called with length mod 16 == 0. (Only the
> >     final call can
> >      > + * have a partial block length of < 128 bits.)
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_update(gcm_context *ctx, // pointer to
> >     user-provided GCM context
> >      > + size_t length, // length, in bytes, of data to process
> >      > + const uchar *input, // pointer to source data
> >      > + uchar *output); // pointer to destination data
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_FINISH
> >      > + *
> >      > + * This is called once after all calls to GCM_UPDATE to finalize
> >     the GCM.
> >      > + * It performs the final GHASH to produce the resulting
> >     authentication TAG.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_finish(
> >      > + gcm_context *ctx, // pointer to user-provided GCM context
> >      > + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0
> >      > + size_t tag_len); // length, in bytes, of the tag-receiving buf
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_ZERO_CTX
> >      > + *
> >      > + * The GCM context contains both the GCM context and the AES
> >     context.
> >      > + * This includes keying and key-related material which is security-
> >      > + * sensitive, so it MUST be zeroed after use. This function does
> >     that.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static void gcm_zero_ctx(gcm_context *ctx);
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
> >     THE GOOD OF ALL
> >      > + *
> >      > + * This is a simple and straightforward implementation of the
> >     AES Rijndael
> >      > + * 128-bit block cipher designed by Vincent Rijmen and Joan
> >     Daemen. The focus
> >      > + * of this work was correctness & accuracy. It is written in 'C'
> >     without any
> >      > + * particular focus upon optimization or speed. It should be
> >     endian (memory
> >      > + * byte order) neutral since the few places that care are
> >     handled explicitly.
> >      > + *
> >      > + * This implementation of Rijndael was created by Steven M.
> >     Gibson of GRC.com.
> >      > + *
> >      > + * It is intended for general purpose use, but was written in
> >     support of GRC's
> >      > + * reference implementation of the SQRL (Secure Quick Reliable
> >     Login) client.
> >      > + *
> >      > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
> >     <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
> >      > + *
> >      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
> >     WARRANTY MADE
> >      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
> >     YOUR OWN RISK.
> >      > + *
> >      > +
> >     *******************************************************************************/
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +static int aes_tables_inited = 0; // run-once flag for
> >     performing key
> >      > + // expasion table generation (see below)
> >      > +/*
> >      > + * The following static local tables must be filled-in before
> >     the first use of
> >      > + * the GCM or AES ciphers. They are used for the AES key
> >     expansion/scheduling
> >      > + * and once built are read-only and thread safe. The
> >     "gcm_initialize" function
> >      > + * must be called once during system initialization to populate
> >     these arrays
> >      > + * for subsequent use by the AES key scheduler. If they have not
> >     been built
> >      > + * before attempted use, an error will be returned to the caller.
> >      > + *
> >      > + * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES
> >     decryption. Since
> >      > + * GCM uses AES in counter-mode, where the AES cipher output is
> >     XORed with
> >      > + * the GCM input, we ONLY NEED AES encryption. Thus, to save
> >     space AES
> >      > + * decryption is typically disabled by setting AES_DECRYPTION to
> >     0 in aes.h.
> >      > + */
> >      > +// We always need our forward tables
> >      > +static uchar FSb[256]; // Forward substitution box (FSb)
> >      > +static uint32_t FT0[256]; // Forward key schedule assembly tables
> >      > +static uint32_t FT1[256];
> >      > +static uint32_t FT2[256];
> >      > +static uint32_t FT3[256];
> >      > +
> >      > +#if AES_DECRYPTION // We ONLY need reverse for decryption
> >      > +static uchar RSb[256]; // Reverse substitution box (RSb)
> >      > +static uint32_t RT0[256]; // Reverse key schedule assembly tables
> >      > +static uint32_t RT1[256];
> >      > +static uint32_t RT2[256];
> >      > +static uint32_t RT3[256];
> >      > +#endif /* AES_DECRYPTION */
> >      > +
> >      > +static uint32_t RCON[10]; // AES round constants
> >      >
> >      > +/*
> >      > + * Platform Endianness Neutralizing Load and Store Macro
> >     definitions
> >      > + * AES wants platform-neutral Little Endian (LE) byte ordering
> >      > + */
> >      > +#define GET_UINT32_LE(n, b, i) \
> >      > + { \
> >      > + (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) | \
> >      > + ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] <<
> >     24); \
> >      > + }
> >      >
> >      > -
> >      > -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
> >      > -
> >      > -static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
> >      > - if (MG_BIG_ENDIAN) {
> >      > - do {
> >      > - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
> >      > - ((unsigned) buf[1] << 8 | buf[0]);
> >      > - *(uint32_t *) buf = t;
> >      > - buf += 4;
> >      > - } while (--longs);
> >      > - } else {
> >      > - (void) buf, (void) longs; // Little endian. Do nothing
> >      > +#define PUT_UINT32_LE(n, b, i) \
> >      > + { \
> >      > + (b)[(i)] = (uchar) ((n)); \
> >      > + (b)[(i) + 1] = (uchar) ((n) >> 8); \
> >      > + (b)[(i) + 2] = (uchar) ((n) >> 16); \
> >      > + (b)[(i) + 3] = (uchar) ((n) >> 24); \
> >      > }
> >      > -}
> >      >
> >      > -#define F1(x, y, z) (z ^ (x & (y ^ z)))
> >      > -#define F2(x, y, z) F1(z, x, y)
> >      > -#define F3(x, y, z) (x ^ y ^ z)
> >      > -#define F4(x, y, z) (y ^ (x | ~z))
> >      > +/*
> >      > + * AES forward and reverse encryption round processing macros
> >      > + */
> >      > +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
> >      > + { \
> >      > + X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
> >      > + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \
> >      > + \
> >      > + X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
> >      > + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \
> >      > + \
> >      > + X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
> >      > + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \
> >      > + \
> >      > + X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
> >      > + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \
> >      > + }
> >      >
> >      > -#define MD5STEP(f, w, x, y, z, data, s) \
> >      > - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
> >      > +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
> >      > + { \
> >      > + X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
> >      > + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \
> >      > + \
> >      > + X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
> >      > + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \
> >      > + \
> >      > + X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
> >      > + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \
> >      > + \
> >      > + X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
> >      > + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \
> >      > + }
> >      >
> >      > /*
> >      > - * Start MD5 accumulation. Set bit count to 0 and buffer to
> >     mysterious
> >      > - * initialization constants.
> >      > + * These macros improve the readability of the key
> >      > + * generation initialization code by collapsing
> >      > + * repetitive common operations into logical pieces.
> >      > */
> >      > -void mg_md5_init(mg_md5_ctx *ctx) {
> >      > - ctx->buf[0] = 0x67452301;
> >      > - ctx->buf[1] = 0xefcdab89;
> >      > - ctx->buf[2] = 0x98badcfe;
> >      > - ctx->buf[3] = 0x10325476;
> >      > +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
> >      > +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
> >      > +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
> >      > +#define MIX(x, y) \
> >      > + { \
> >      > + y = ((y << 1) | (y >> 7)) & 0xFF; \
> >      > + x ^= y; \
> >      > + }
> >      > +#define CPY128 \
> >      > + { \
> >      > + *RK++ = *SK++; \
> >      > + *RK++ = *SK++; \
> >      > + *RK++ = *SK++; \
> >      > + *RK++ = *SK++; \
> >      > + }
> >      >
> >      > - ctx->bits[0] = 0;
> >      > - ctx->bits[1] = 0;
> >      > -}
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * AES_INIT_KEYGEN_TABLES
> >      > + *
> >      > + * Fills the AES key expansion tables allocated above with their
> >     static
> >      > + * data. This is not "per key" data, but static system-wide
> >     read-only
> >      > + * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be
> >     called once
> >      > + * at system initialization to setup the tables for all
> >     subsequent use.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +void aes_init_keygen_tables(void) {
> >      > + int i, x, y, z; // general purpose iteration and computation
> >     locals
> >      > + int pow[256];
> >      > + int log[256];
> >      > +
> >      > + if (aes_tables_inited) return;
> >      > +
> >      > + // fill the 'pow' and 'log' tables over GF(2^8)
> >      > + for (i = 0, x = 1; i < 256; i++) {
> >      > + pow[i] = x;
> >      > + log[x] = i;
> >      > + x = (x ^ XTIME(x)) & 0xFF;
> >      > + }
> >      > + // compute the round constants
> >      > + for (i = 0, x = 1; i < 10; i++) {
> >      > + RCON[i] = (uint32_t) x;
> >      > + x = XTIME(x) & 0xFF;
> >      > + }
> >      > + // fill the forward and reverse substitution boxes
> >      > + FSb[0x00] = 0x63;
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > + RSb[0x63] = 0x00;
> >      > +#endif /* AES_DECRYPTION */
> >      > +
> >      > + for (i = 1; i < 256; i++) {
> >      > + x = y = pow[255 - log[i]];
> >      > + MIX(x, y);
> >      > + MIX(x, y);
> >      > + MIX(x, y);
> >      > + MIX(x, y);
> >      > + FSb[i] = (uchar) (x ^= 0x63);
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > + RSb[x] = (uchar) i;
> >      > +#endif /* AES_DECRYPTION */
> >      > + }
> >      > + // generate the forward and reverse key expansion tables
> >      > + for (i = 0; i < 256; i++) {
> >      > + x = FSb[i];
> >      > + y = XTIME(x) & 0xFF;
> >      > + z = (y ^ x) & 0xFF;
> >      >
> >      > -static void mg_md5_transform(uint32_t buf[4], uint32_t const
> >     in[16]) {
> >      > - uint32_t a, b, c, d;
> >      > + FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x
> >     << 16) ^
> >      > + ((uint32_t) z << 24);
> >      >
> >      > - a = buf[0];
> >      > - b = buf[1];
> >      > - c = buf[2];
> >      > - d = buf[3];
> >      > + FT1[i] = ROTL8(FT0[i]);
> >      > + FT2[i] = ROTL8(FT1[i]);
> >      > + FT3[i] = ROTL8(FT2[i]);
> >      >
> >      > - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
> >      > - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
> >      > - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
> >      > - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
> >      > - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
> >      > - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
> >      > - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
> >      > - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
> >      > - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
> >      > - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
> >      > - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
> >      > - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
> >      > - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
> >      > - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
> >      > - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
> >      > - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > + x = RSb[i];
> >      >
> >      > - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
> >      > - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
> >      > - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
> >      > - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
> >      > - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
> >      > - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
> >      > - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
> >      > - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
> >      > - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
> >      > - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
> >      > - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
> >      > - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
> >      > - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
> >      > - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
> >      > - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
> >      > - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
> >      > + RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x)
> >     << 8) ^
> >      > + ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
> >      >
> >      > - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
> >      > - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
> >      > - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
> >      > - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
> >      > - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
> >      > - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
> >      > - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
> >      > - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
> >      > - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
> >      > - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
> >      > - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
> >      > - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
> >      > - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
> >      > - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
> >      > - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
> >      > - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
> >      > + RT1[i] = ROTL8(RT0[i]);
> >      > + RT2[i] = ROTL8(RT1[i]);
> >      > + RT3[i] = ROTL8(RT2[i]);
> >      > +#endif /* AES_DECRYPTION */
> >      > + }
> >      > + aes_tables_inited = 1; // flag that the tables have been generated
> >      > +} // to permit subsequent use of the AES cipher
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * AES_SET_ENCRYPTION_KEY
> >      > + *
> >      > + * This is called by 'aes_setkey' when we're establishing a key for
> >      > + * subsequent encryption. We give it a pointer to the encryption
> >      > + * context, a pointer to the key, and the key's length in bytes.
> >      > + * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_set_encryption_key(aes_context *ctx, const uchar
> >     *key,
> >      > + uint keysize) {
> >      > + uint i; // general purpose iteration local
> >      > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
> >      > +
> >      > + for (i = 0; i < (keysize >> 2); i++) {
> >      > + GET_UINT32_LE(RK[i], key, i << 2);
> >      > + }
> >      >
> >      > - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
> >      > - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
> >      > - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
> >      > - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
> >      > - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
> >      > - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
> >      > - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
> >      > - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
> >      > - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
> >      > - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
> >      > - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
> >      > - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
> >      > - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
> >      > - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
> >      > - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
> >      > - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
> >      > + switch (ctx->rounds) {
> >      > + case 10:
> >      > + for (i = 0; i < 10; i++, RK += 4) {
> >      > + RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
> >      > +
> >      > + RK[5] = RK[1] ^ RK[4];
> >      > + RK[6] = RK[2] ^ RK[5];
> >      > + RK[7] = RK[3] ^ RK[6];
> >      > + }
> >      > + break;
> >      >
> >      > - buf[0] += a;
> >      > - buf[1] += b;
> >      > - buf[2] += c;
> >      > - buf[3] += d;
> >      > -}
> >      > + case 12:
> >      > + for (i = 0; i < 8; i++, RK += 6) {
> >      > + RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
> >      > +
> >      > + RK[7] = RK[1] ^ RK[6];
> >      > + RK[8] = RK[2] ^ RK[7];
> >      > + RK[9] = RK[3] ^ RK[8];
> >      > + RK[10] = RK[4] ^ RK[9];
> >      > + RK[11] = RK[5] ^ RK[10];
> >      > + }
> >      > + break;
> >      >
> >      > -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
> >     size_t len) {
> >      > - uint32_t t;
> >      > + case 14:
> >      > + for (i = 0; i < 7; i++, RK += 8) {
> >      > + RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
> >      > +
> >      > + RK[9] = RK[1] ^ RK[8];
> >      > + RK[10] = RK[2] ^ RK[9];
> >      > + RK[11] = RK[3] ^ RK[10];
> >      > +
> >      > + RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
> >      > +
> >      > + RK[13] = RK[5] ^ RK[12];
> >      > + RK[14] = RK[6] ^ RK[13];
> >      > + RK[15] = RK[7] ^ RK[14];
> >      > + }
> >      > + break;
> >      >
> >      > - t = ctx->bits[0];
> >      > - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
> >     ctx->bits[1]++;
> >      > - ctx->bits[1] += (uint32_t) len >> 29;
> >      > + default:
> >      > + return -1;
> >      > + }
> >      > + return (0);
> >      > +}
> >      > +
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * AES_SET_DECRYPTION_KEY
> >      > + *
> >      > + * This is called by 'aes_setkey' when we're establishing a
> >      > + * key for subsequent decryption. We give it a pointer to
> >      > + * the encryption context, a pointer to the key, and the key's
> >      > + * length in bits. Valid lengths are: 128, 192, or 256 bits.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_set_decryption_key(aes_context *ctx, const uchar
> >     *key,
> >      > + uint keysize) {
> >      > + int i, j;
> >      > + aes_context cty; // a calling aes context for set_encryption_key
> >      > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
> >      > + uint32_t *SK;
> >      > + int ret;
> >      >
> >      > - t = (t >> 3) & 0x3f;
> >      > + cty.rounds = ctx->rounds; // initialize our local aes context
> >      > + cty.rk = cty.buf; // round count and key buf pointer
> >      >
> >      > - if (t) {
> >      > - unsigned char *p = (unsigned char *) ctx->in + t;
> >      > + if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0)
> >     return (ret);
> >      >
> >      > - t = 64 - t;
> >      > - if (len < t) {
> >      > - memcpy(p, buf, len);
> >      > - return;
> >      > + SK = cty.rk + cty.rounds * 4;
> >      > +
> >      > + CPY128 // copy a 128-bit block from *SK to *RK
> >      > +
> >      > + for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
> >      > + for (j = 0; j < 4; j++, SK++) {
> >      > + *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
> >      > + RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
> >      > }
> >      > - memcpy(p, buf, t);
> >      > - mg_byte_reverse(ctx->in, 16);
> >      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > - buf += t;
> >      > - len -= t;
> >      > + }
> >      > + CPY128 // copy a 128-bit block from *SK to *RK
> >      > + memset(&cty, 0, sizeof(aes_context)); // clear local aes context
> >      > + return (0);
> >      > +}
> >      > +
> >      > +#endif /* AES_DECRYPTION */
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * AES_SETKEY
> >      > + *
> >      > + * Invoked to establish the key schedule for subsequent
> >     encryption/decryption
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_setkey(aes_context *ctx, // AES context provided
> >     by our caller
> >      > + int mode, // ENCRYPT or DECRYPT flag
> >      > + const uchar *key, // pointer to the key
> >      > + uint keysize) // key length in bytes
> >      > +{
> >      > + // since table initialization is not thread safe, we could
> >     either add
> >      > + // system-specific mutexes and init the AES key generation
> >     tables on
> >      > + // demand, or ask the developer to simply call "gcm_initialize"
> >     once during
> >      > + // application startup before threading begins. That's what we
> >     choose.
> >      > + if (!aes_tables_inited) return (-1); // fail the call when not
> >     inited.
> >      > +
> >      > + ctx->mode = mode; // capture the key type we're creating
> >      > + ctx->rk = ctx->buf; // initialize our round key pointer
> >      > +
> >      > + switch (keysize) // set the rounds count based upon the keysize
> >      > + {
> >      > + case 16:
> >      > + ctx->rounds = 10;
> >      > + break; // 16-byte, 128-bit key
> >      > + case 24:
> >      > + ctx->rounds = 12;
> >      > + break; // 24-byte, 192-bit key
> >      > + case 32:
> >      > + ctx->rounds = 14;
> >      > + break; // 32-byte, 256-bit key
> >      > + default:
> >      > + return (-1);
> >      > }
> >      >
> >      > - while (len >= 64) {
> >      > - memcpy(ctx->in, buf, 64);
> >      > - mg_byte_reverse(ctx->in, 16);
> >      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > - buf += 64;
> >      > - len -= 64;
> >      > +#if AES_DECRYPTION
> >      > + if (mode == MG_DECRYPT) // expand our key for encryption or
> >     decryption
> >      > + return (aes_set_decryption_key(ctx, key, keysize));
> >      > + else /* MG_ENCRYPT */
> >      > +#endif /* AES_DECRYPTION */
> >      > + return (aes_set_encryption_key(ctx, key, keysize));
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * AES_CIPHER
> >      > + *
> >      > + * Perform AES encryption and decryption.
> >      > + * The AES context will have been setup with the encryption mode
> >      > + * and all keying information appropriate for the task.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int aes_cipher(aes_context *ctx, const uchar input[16],
> >      > + uchar output[16]) {
> >      > + int i;
> >      > + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general
> >     purpose locals
> >      > +
> >      > + RK = ctx->rk;
> >      > +
> >      > + GET_UINT32_LE(X0, input, 0);
> >      > + X0 ^= *RK++; // load our 128-bit
> >      > + GET_UINT32_LE(X1, input, 4);
> >      > + X1 ^= *RK++; // input buffer in a storage
> >      > + GET_UINT32_LE(X2, input, 8);
> >      > + X2 ^= *RK++; // memory endian-neutral way
> >      > + GET_UINT32_LE(X3, input, 12);
> >      > + X3 ^= *RK++;
> >      > +
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > +
> >      > + if (ctx->mode == MG_DECRYPT) {
> >      > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> >      > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> >      > + AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> >      > + }
> >      > +
> >      > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> >      > +
> >      > + X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
> >      > + ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
> >      > + ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
> >      > + ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
> >      > + ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
> >      > + } else /* MG_ENCRYPT */
> >      > + {
> >      > +#endif /* AES_DECRYPTION */
> >      > +
> >      > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
> >      > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> >      > + AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
> >      > + }
> >      > +
> >      > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
> >      > +
> >      > + X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
> >      > +
> >      > + X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
> >      > + ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
> >      > + ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
> >      > + ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
> >      > +
> >      > +#if AES_DECRYPTION // whether AES decryption is supported
> >      > }
> >      > +#endif /* AES_DECRYPTION */
> >      > +
> >      > + PUT_UINT32_LE(X0, output, 0);
> >      > + PUT_UINT32_LE(X1, output, 4);
> >      > + PUT_UINT32_LE(X2, output, 8);
> >      > + PUT_UINT32_LE(X3, output, 12);
> >      > +
> >      > + return (0);
> >      > +}
> >      > +/* end of aes.c */
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
> >     THE GOOD OF ALL
> >      > + *
> >      > + * This is a simple and straightforward implementation of
> >     AES-GCM authenticated
> >      > + * encryption. The focus of this work was correctness &
> >     accuracy. It is written
> >      > + * in straight 'C' without any particular focus upon
> >     optimization or speed. It
> >      > + * should be endian (memory byte order) neutral since the few
> >     places that care
> >      > + * are handled explicitly.
> >      > + *
> >      > + * This implementation of AES-GCM was created by Steven M.
> >     Gibson of GRC.com.
> >      > + *
> >      > + * It is intended for general purpose use, but was written in
> >     support of GRC's
> >      > + * reference implementation of the SQRL (Secure Quick Reliable
> >     Login) client.
> >      > + *
> >      > + * See:
> >     http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> >     <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
> >      > + *
> >     http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
> >     <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/>
> >      > + * gcm/gcm-revised-spec.pdf
> >      > + *
> >      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
> >     WARRANTY MADE
> >      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
> >     YOUR OWN RISK.
> >      > + *
> >      > +
> >     *******************************************************************************/
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * ==== IMPLEMENTATION WARNING ====
> >      > + *
> >      > + * This code was developed for use within SQRL's fixed
> >     environmnent. Thus, it
> >      > + * is somewhat less "general purpose" than it would be if it
> >     were designed as
> >      > + * a general purpose AES-GCM library. Specifically, it bothers
> >     with almost NO
> >      > + * error checking on parameter limits, buffer bounds, etc. It
> >     assumes that it
> >      > + * is being invoked by its author or by someone who understands
> >     the values it
> >      > + * expects to receive. Its behavior will be undefined otherwise.
> >      > + *
> >      > + * All functions that might fail are defined to return 'ints' to
> >     indicate a
> >      > + * problem. Most do not do so now. But this allows for error
> >     propagation out
> >      > + * of internal functions if robust error checking should ever be
> >     desired.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +
> >      > +/* Calculating the "GHASH"
> >      > + *
> >      > + * There are many ways of calculating the so-called GHASH in
> >     software, each with
> >      > + * a traditional size vs performance tradeoff. The GHASH (Galois
> >     field hash) is
> >      > + * an intriguing construction which takes two 128-bit strings
> >     (also the cipher's
> >      > + * block size and the fundamental operation size for the system)
> >     and hashes them
> >      > + * into a third 128-bit result.
> >      > + *
> >      > + * Many implementation solutions have been worked out that use
> >     large precomputed
> >      > + * table lookups in place of more time consuming bit fiddling,
> >     and this approach
> >      > + * can be scaled easily upward or downward as needed to change
> >     the time/space
> >      > + * tradeoff. It's been studied extensively and there's a solid
> >     body of theory
> >      > + * and practice. For example, without using any lookup tables an
> >     implementation
> >      > + * might obtain 119 cycles per byte throughput, whereas using a
> >     simple, though
> >      > + * large, key-specific 64 kbyte 8-bit lookup table the
> >     performance jumps to 13
> >      > + * cycles per byte.
> >      > + *
> >      > + * And Intel's processors have, since 2010, included an
> >     instruction which does
> >      > + * the entire 128x128->128 bit job in just several 64x64->128
> >     bit pieces.
> >      > + *
> >      > + * Since SQRL is interactive, and only processing a few 128-bit
> >     blocks, I've
> >      > + * settled upon a relatively slower but appealing small-table
> >     compromise which
> >      > + * folds a bunch of not only time consuming but also bit
> >     twiddling into a simple
> >      > + * 16-entry table which is attributed to Victor Shoup's 1996
> >     work while at
> >      > + * Bellcore: "On Fast and Provably Secure MessageAuthentication
> >     Based on
> >      > + * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf
> >     <http://www.shoup.net/papers/macs.pdf>
> >      > + * See, also section 4.1 of the "gcm-revised-spec" cited above.
> >      > + */
> >      >
> >      > - memcpy(ctx->in, buf, len);
> >      > -}
> >      > +/*
> >      > + * This 16-entry table of pre-computed constants is used by the
> >      > + * GHASH multiplier to improve over a strictly table-free but
> >      > + * significantly slower 128x128 bit multiple within GF(2^128).
> >      > + */
> >      > +static const uint64_t last4[16] = {
> >      > + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
> >      > + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
> >      >
> >      > -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
> >      > - unsigned count;
> >      > - unsigned char *p;
> >      > - uint32_t *a;
> >      > +/*
> >      > + * Platform Endianness Neutralizing Load and Store Macro
> >     definitions
> >      > + * GCM wants platform-neutral Big Endian (BE) byte ordering
> >      > + */
> >      > +#define GET_UINT32_BE(n, b, i) \
> >      > + { \
> >      > + (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] <<
> >     16) | \
> >      > + ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]); \
> >      > + }
> >      >
> >      > - count = (ctx->bits[0] >> 3) & 0x3F;
> >      > +#define PUT_UINT32_BE(n, b, i) \
> >      > + { \
> >      > + (b)[(i)] = (uchar) ((n) >> 24); \
> >      > + (b)[(i) + 1] = (uchar) ((n) >> 16); \
> >      > + (b)[(i) + 2] = (uchar) ((n) >> 8); \
> >      > + (b)[(i) + 3] = (uchar) ((n)); \
> >      > + }
> >      >
> >      > - p = ctx->in + count;
> >      > - *p++ = 0x80;
> >      > - count = 64 - 1 - count;
> >      > - if (count < 8) {
> >      > - memset(p, 0, count);
> >      > - mg_byte_reverse(ctx->in, 16);
> >      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > - memset(ctx->in, 0, 56);
> >      > - } else {
> >      > - memset(p, 0, count - 8);
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_INITIALIZE
> >      > + *
> >      > + * Must be called once to initialize the GCM library.
> >      > + *
> >      > + * At present, this only calls the AES keygen table generator,
> >     which expands
> >      > + * the AES keying tables for use. This is NOT A THREAD-SAFE
> >     function, so it
> >      > + * MUST be called during system initialization before a
> >     multi-threading
> >      > + * environment is running.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +int mg_gcm_initialize(void) {
> >      > + aes_init_keygen_tables();
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_MULT
> >      > + *
> >      > + * Performs a GHASH operation on the 128-bit input vector 'x',
> >     setting
> >      > + * the 128-bit output vector to 'x' times H using our
> >     precomputed tables.
> >      > + * 'x' and 'output' are seen as elements of GCM's GF(2^128)
> >     Galois field.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static void gcm_mult(gcm_context *ctx, // pointer to established
> >     context
> >      > + const uchar x[16], // pointer to 128-bit input vector
> >      > + uchar output[16]) // pointer to 128-bit output vector
> >      > +{
> >      > + int i;
> >      > + uchar lo, hi, rem;
> >      > + uint64_t zh, zl;
> >      > +
> >      > + lo = (uchar) (x[15] & 0x0f);
> >      > + hi = (uchar) (x[15] >> 4);
> >      > + zh = ctx->HH[lo];
> >      > + zl = ctx->HL[lo];
> >      > +
> >      > + for (i = 15; i >= 0; i--) {
> >      > + lo = (uchar) (x[i] & 0x0f);
> >      > + hi = (uchar) (x[i] >> 4);
> >      > +
> >      > + if (i != 15) {
> >      > + rem = (uchar) (zl & 0x0f);
> >      > + zl = (zh << 60) | (zl >> 4);
> >      > + zh = (zh >> 4);
> >      > + zh ^= (uint64_t) last4[rem] << 48;
> >      > + zh ^= ctx->HH[lo];
> >      > + zl ^= ctx->HL[lo];
> >      > + }
> >      > + rem = (uchar) (zl & 0x0f);
> >      > + zl = (zh << 60) | (zl >> 4);
> >      > + zh = (zh >> 4);
> >      > + zh ^= (uint64_t) last4[rem] << 48;
> >      > + zh ^= ctx->HH[hi];
> >      > + zl ^= ctx->HL[hi];
> >      > }
> >      > - mg_byte_reverse(ctx->in, 14);
> >      > + PUT_UINT32_BE(zh >> 32, output, 0);
> >      > + PUT_UINT32_BE(zh, output, 4);
> >      > + PUT_UINT32_BE(zl >> 32, output, 8);
> >      > + PUT_UINT32_BE(zl, output, 12);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_SETKEY
> >      > + *
> >      > + * This is called to set the AES-GCM key. It initializes the AES
> >     key
> >      > + * and populates the gcm context's pre-calculated HTables.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +static int gcm_setkey(
> >      > + gcm_context *ctx, // pointer to caller-provided gcm context
> >      > + const uchar *key, // pointer to the AES encryption key
> >      > + const uint keysize) // size in bytes (must be 16, 24, 32 for
> >      > + // 128, 192 or 256-bit keys respectively)
> >      > +{
> >      > + int ret, i, j;
> >      > + uint64_t hi, lo;
> >      > + uint64_t vl, vh;
> >      > + unsigned char h[16];
> >      > +
> >      > + memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided
> >     GCM context
> >      > + memset(h, 0, 16); // initialize the block to encrypt
> >      > +
> >      > + // encrypt the null 128-bit block to generate a key-based value
> >      > + // which is then used to initialize our GHASH lookup tables
> >      > + if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize))
> >     != 0)
> >      > + return (ret);
> >      > + if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
> >      > +
> >      > + GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian
> >      > + GET_UINT32_BE(lo, h, 4);
> >      > + vh = (uint64_t) hi << 32 | lo;
> >      > +
> >      > + GET_UINT32_BE(hi, h, 8);
> >      > + GET_UINT32_BE(lo, h, 12);
> >      > + vl = (uint64_t) hi << 32 | lo;
> >      > +
> >      > + ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128)
> >      > + ctx->HH[8] = vh;
> >      > + ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128)
> >      > + ctx->HL[0] = 0;
> >      > +
> >      > + for (i = 4; i > 0; i >>= 1) {
> >      > + uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
> >      > + vl = (vh << 63) | (vl >> 1);
> >      > + vh = (vh >> 1) ^ ((uint64_t) T << 32);
> >      > + ctx->HL[i] = vl;
> >      > + ctx->HH[i] = vh;
> >      > + }
> >      > + for (i = 2; i < 16; i <<= 1) {
> >      > + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
> >      > + vh = *HiH;
> >      > + vl = *HiL;
> >      > + for (j = 1; j < i; j++) {
> >      > + HiH[j] = vh ^ ctx->HH[j];
> >      > + HiL[j] = vl ^ ctx->HL[j];
> >      > + }
> >      > + }
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM processing occurs four phases: SETKEY, START, UPDATE and
> >     FINISH.
> >      > + *
> >      > + * SETKEY:
> >      > + *
> >      > + * START: Sets the Encryption/Decryption mode.
> >      > + * Accepts the initialization vector and additional data.
> >      > + *
> >      > + * UPDATE: Encrypts or decrypts the plaintext or ciphertext.
> >      > + *
> >      > + * FINISH: Performs a final GHASH to generate the authentication
> >     tag.
> >      > + *
> >      > +
> >     ******************************************************************************
> >      > + *
> >      > + * GCM_START
> >      > + *
> >      > + * Given a user-provided GCM context, this initializes it, sets
> >     the encryption
> >      > + * mode, and preprocesses the initialization vector and
> >     additional AEAD data.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +int gcm_start(gcm_context *ctx, // pointer to user-provided GCM
> >     context
> >      > + int mode, // GCM_ENCRYPT or GCM_DECRYPT
> >      > + const uchar *iv, // pointer to initialization vector
> >      > + size_t iv_len, // IV length in bytes (should == 12)
> >      > + const uchar *add, // ptr to additional AEAD data (NULL if none)
> >      > + size_t add_len) // length of additional AEAD data (bytes)
> >      > +{
> >      > + int ret; // our error return if the AES encrypt fails
> >      > + uchar work_buf[16]; // XOR source built from provided IV if len
> >     != 16
> >      > + const uchar *p; // general purpose array pointer
> >      > + size_t use_len; // byte count to process, up to 16 bytes
> >      > + size_t i; // local loop iterator
> >      > +
> >      > + // since the context might be reused under the same key
> >      > + // we zero the working buffers for this next new process
> >      > + memset(ctx->y, 0x00, sizeof(ctx->y));
> >      > + memset(ctx->buf, 0x00, sizeof(ctx->buf));
> >      > + ctx->len = 0;
> >      > + ctx->add_len = 0;
> >      > +
> >      > + ctx->mode = mode; // set the GCM encryption/decryption mode
> >      > + ctx->aes_ctx.mode = MG_ENCRYPT; // GCM *always* runs AES in
> >     ENCRYPTION mode
> >      > +
> >      > + if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV
> >      > + memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the
> >     'y' buff
> >      > + ctx->y[15] = 1; // start "counting" from 1 (not 0)
> >      > + } else // if we don't have a 12-byte IV, we GHASH whatever
> >     we've been given
> >      > + {
> >      > + memset(work_buf, 0x00, 16); // clear the working buffer
> >      > + PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into
> >     buffer
> >      > +
> >      > + p = iv;
> >      > + while (iv_len > 0) {
> >      > + use_len = (iv_len < 16) ? iv_len : 16;
> >      > + for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
> >      > + gcm_mult(ctx, ctx->y, ctx->y);
> >      > + iv_len -= use_len;
> >      > + p += use_len;
> >      > + }
> >      > + for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
> >      > + gcm_mult(ctx, ctx->y, ctx->y);
> >      > + }
> >      > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr))
> >     != 0)
> >      > + return (ret);
> >      > +
> >      > + ctx->add_len = add_len;
> >      > + p = add;
> >      > + while (add_len > 0) {
> >      > + use_len = (add_len < 16) ? add_len : 16;
> >      > + for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
> >      > + gcm_mult(ctx, ctx->buf, ctx->buf);
> >      > + add_len -= use_len;
> >      > + p += use_len;
> >      > + }
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_UPDATE
> >      > + *
> >      > + * This is called once or more to process bulk plaintext or
> >     ciphertext data.
> >      > + * We give this some number of bytes of input and it returns the
> >     same number
> >      > + * of output bytes. If called multiple times (which is fine) all
> >     but the final
> >      > + * invocation MUST be called with length mod 16 == 0. (Only the
> >     final call can
> >      > + * have a partial block length of < 128 bits.)
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +int gcm_update(gcm_context *ctx, // pointer to user-provided GCM
> >     context
> >      > + size_t length, // length, in bytes, of data to process
> >      > + const uchar *input, // pointer to source data
> >      > + uchar *output) // pointer to destination data
> >      > +{
> >      > + int ret; // our error return if the AES encrypt fails
> >      > + uchar ectr[16]; // counter-mode cipher output for XORing
> >      > + size_t use_len; // byte count to process, up to 16 bytes
> >      > + size_t i; // local loop iterator
> >      > +
> >      > + ctx->len += length; // bump the GCM context's running length count
> >      > +
> >      > + while (length > 0) {
> >      > + // clamp the length to process at 16 bytes
> >      > + use_len = (length < 16) ? length : 16;
> >      > +
> >      > + // increment the context's 128-bit IV||Counter 'y' vector
> >      > + for (i = 16; i > 12; i--)
> >      > + if (++ctx->y[i - 1] != 0) break;
> >      > +
> >      > + // encrypt the context's 'y' vector under the established key
> >      > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0)
> >     return (ret);
> >      > +
> >      > + // encrypt or decrypt the input to the output
> >      > + if (ctx->mode == MG_ENCRYPT) {
> >      > + for (i = 0; i < use_len; i++) {
> >      > + // XOR the cipher's ouptut vector (ectr) with our input
> >      > + output[i] = (uchar) (ectr[i] ^ input[i]);
> >      > + // now we mix in our data into the authentication hash.
> >      > + // if we're ENcrypting we XOR in the post-XOR (output)
> >      > + // results, but if we're DEcrypting we XOR in the input
> >      > + // data
> >      > + ctx->buf[i] ^= output[i];
> >      > + }
> >      > + } else {
> >      > + for (i = 0; i < use_len; i++) {
> >      > + // but if we're DEcrypting we XOR in the input data first,
> >      > + // i.e. before saving to ouput data, otherwise if the input
> >      > + // and output buffer are the same (inplace decryption) we
> >      > + // would not get the correct auth tag
> >      >
> >      > - a = (uint32_t *) ctx->in;
> >      > - a[14] = ctx->bits[0];
> >      > - a[15] = ctx->bits[1];
> >      > + ctx->buf[i] ^= input[i];
> >      >
> >      > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
> >      > - mg_byte_reverse((unsigned char *) ctx->buf, 4);
> >      > - memcpy(digest, ctx->buf, 16);
> >      > - memset((char *) ctx, 0, sizeof(*ctx));
> >      > -}
> >      > -#endif
> >      > + // XOR the cipher's ouptut vector (ectr) with our input
> >      > + output[i] = (uchar) (ectr[i] ^ input[i]);
> >      > + }
> >      > + }
> >      > + gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/mqtt.c"
> >      > -#endif
> >      > + length -= use_len; // drop the remaining byte count to process
> >      > + input += use_len; // bump our input pointer forward
> >      > + output += use_len; // bump our output pointer forward
> >      > + }
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_FINISH
> >      > + *
> >      > + * This is called once after all calls to GCM_UPDATE to finalize
> >     the GCM.
> >      > + * It performs the final GHASH to produce the resulting
> >     authentication TAG.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM
> >     context
> >      > + uchar *tag, // pointer to buffer which receives the tag
> >      > + size_t tag_len) // length, in bytes, of the tag-receiving buf
> >      > +{
> >      > + uchar work_buf[16];
> >      > + uint64_t orig_len = ctx->len * 8;
> >      > + uint64_t orig_add_len = ctx->add_len * 8;
> >      > + size_t i;
> >      >
> >      > + if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
> >      >
> >      > + if (orig_len || orig_add_len) {
> >      > + memset(work_buf, 0x00, 16);
> >      >
> >      > + PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
> >      > + PUT_UINT32_BE((orig_add_len), work_buf, 4);
> >      > + PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
> >      > + PUT_UINT32_BE((orig_len), work_buf, 12);
> >      >
> >      > + for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
> >      > + gcm_mult(ctx, ctx->buf, ctx->buf);
> >      > + for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
> >      > + }
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_CRYPT_AND_TAG
> >      > + *
> >      > + * This either encrypts or decrypts the user-provided data and,
> >     either
> >      > + * way, generates an authentication tag of the requested length.
> >     It must be
> >      > + * called with a GCM context whose key has already been set with
> >     GCM_SETKEY.
> >      > + *
> >      > + * The user would typically call this explicitly to ENCRYPT a
> >     buffer of data
> >      > + * and optional associated data, and produce its an
> >     authentication tag.
> >      > + *
> >      > + * To reverse the process the user would typically call the
> >     companion
> >      > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
> >     user-provided
> >      > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
> >     function
> >      > + * to perform its decryption and tag generation, which it then
> >     compares.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +int gcm_crypt_and_tag(
> >      > + gcm_context *ctx, // gcm context with key already setup
> >      > + int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
> >      > + const uchar *iv, // pointer to the 12-byte initialization vector
> >      > + size_t iv_len, // byte length if the IV. should always be 12
> >      > + const uchar *add, // pointer to the non-ciphered additional data
> >      > + size_t add_len, // byte length of the additional AEAD data
> >      > + const uchar *input, // pointer to the cipher data source
> >      > + uchar *output, // pointer to the cipher data destination
> >      > + size_t length, // byte length of the cipher data
> >      > + uchar *tag, // pointer to the tag to be generated
> >      > + size_t tag_len) // byte length of the tag to be generated
> >      > +{ /*
> >      > + assuming that the caller has already invoked gcm_setkey to
> >      > + prepare the gcm context with the keying material, we simply
> >      > + invoke each of the three GCM sub-functions in turn...
> >      > + */
> >      > + gcm_start(ctx, mode, iv, iv_len, add, add_len);
> >      > + gcm_update(ctx, length, input, output);
> >      > + gcm_finish(ctx, tag, tag_len);
> >      > + return (0);
> >      > +}
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * GCM_ZERO_CTX
> >      > + *
> >      > + * The GCM context contains both the GCM context and the AES
> >     context.
> >      > + * This includes keying and key-related material which is security-
> >      > + * sensitive, so it MUST be zeroed after use. This function does
> >     that.
> >      > + *
> >      > +
> >     ******************************************************************************/
> >      > +void gcm_zero_ctx(gcm_context *ctx) {
> >      > + // zero the context originally provided to us
> >      > + memset(ctx, 0, sizeof(gcm_context));
> >      > +}
> >      > +//
> >      > +// aes-gcm.c
> >      > +// Pods
> >      > +//
> >      > +// Created by Markus Kosmal on 20/11/14.
> >      > +//
> >      > +//
> >      >
> >      > +int mg_aes_gcm_encrypt(unsigned char *output, //
> >      > + const unsigned char *input, size_t input_length,
> >      > + const unsigned char *key, const size_t key_len,
> >      > + const unsigned char *iv, const size_t iv_len,
> >      > + unsigned char *aead, size_t aead_len, unsigned char *tag,
> >      > + const size_t tag_len) {
> >      > + int ret = 0; // our return value
> >      > + gcm_context ctx; // includes the AES context structure
> >      >
> >      > + gcm_setkey(&ctx, key, (uint) key_len);
> >      >
> >      > + ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead,
> >     aead_len, input,
> >      > + output, input_length, tag, tag_len);
> >      >
> >      > -#define MQTT_CLEAN_SESSION 0x02
> >      > -#define MQTT_HAS_WILL 0x04
> >      > -#define MQTT_WILL_RETAIN 0x20
> >      > -#define MQTT_HAS_PASSWORD 0x40
> >      > -#define MQTT_HAS_USER_NAME 0x80
> >      > + gcm_zero_ctx(&ctx);
> >      >
> >      > -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
> >     uint8_t flags,
> >      > - uint32_t len) {
> >      > - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
> >      > - buf[0] = (uint8_t) ((cmd << 4) | flags);
> >      > - do {
> >      > - *vlen = len % 0x80;
> >      > - len /= 0x80;
> >      > - if (len > 0) *vlen |= 0x80;
> >      > - vlen++;
> >      > - } while (len > 0 && vlen < &buf[sizeof(buf)]);
> >      > - mg_send(c, buf, (size_t) (vlen - buf));
> >      > + return (ret);
> >      > }
> >      >
> >      > -static void mg_send_u16(struct mg_connection *c, uint16_t value) {
> >      > - mg_send(c, &value, sizeof(value));
> >      > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
> >     char *input,
> >      > + size_t input_length, const unsigned char *key,
> >      > + const size_t key_len, const unsigned char *iv,
> >      > + const size_t iv_len) {
> >      > + int ret = 0; // our return value
> >      > + gcm_context ctx; // includes the AES context structure
> >      > +
> >      > + size_t tag_len = 0;
> >      > + unsigned char *tag_buf = NULL;
> >      > +
> >      > + gcm_setkey(&ctx, key, (uint) key_len);
> >      > +
> >      > + ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0,
> >     input, output,
> >      > + input_length, tag_buf, tag_len);
> >      > +
> >      > + gcm_zero_ctx(&ctx);
> >      > +
> >      > + return (ret);
> >      > }
> >      > +#endif
> >      > +// End of aes128 PD
> >      >
> >      > -void mg_mqtt_login(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts) {
> >      > - char rnd[10], client_id[21], zero = 0;
> >      > - struct mg_str cid = opts->client_id;
> >      > - uint32_t total_len = 7 + 1 + 2 + 2;
> >      > - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/tls_builtin.c"
> >      > +#endif
> >      >
> >      > - if (cid.len == 0) {
> >      > - mg_random(rnd, sizeof(rnd));
> >      > - mg_hex(rnd, sizeof(rnd), client_id);
> >      > - client_id[sizeof(client_id) - 1] = '\0';
> >      > - cid = mg_str(client_id);
> >      > - }
> >      >
> >      > - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
> >     (3.1.1)
> >      > - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
> >      > - hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3); // Connection
> >     flags
> >      > - if (opts->user.len > 0) {
> >      > - total_len += 2 + (uint32_t) opts->user.len;
> >      > - hdr[7] |= MQTT_HAS_USER_NAME;
> >      > +
> >      > +
> >      > +#if MG_TLS == MG_TLS_BUILTIN
> >      > +
> >      > +/* TLS 1.3 Record Content Type (RFC8446 B.1) */
> >      > +#define MG_TLS_CHANGE_CIPHER 20
> >      > +#define MG_TLS_ALERT 21
> >      > +#define MG_TLS_HANDSHAKE 22
> >      > +#define MG_TLS_APP_DATA 23
> >      > +#define MG_TLS_HEARTBEAT 24
> >      > +
> >      > +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
> >      > +#define MG_TLS_CLIENT_HELLO 1
> >      > +#define MG_TLS_SERVER_HELLO 2
> >      > +#define MG_TLS_ENCRYPTED_EXTENSIONS 8
> >      > +#define MG_TLS_CERTIFICATE 11
> >      > +#define MG_TLS_CERTIFICATE_VERIFY 15
> >      > +#define MG_TLS_FINISHED 20
> >      > +
> >      > +// handshake is re-entrant, so we need to keep track of its
> >     state state names
> >      > +// refer to RFC8446#A.1
> >      > +enum mg_tls_hs_state {
> >      > + // Client state machine:
> >      > + MG_TLS_STATE_CLIENT_START, // Send ClientHello
> >      > + MG_TLS_STATE_CLIENT_WAIT_SH, // Wait for ServerHello
> >      > + MG_TLS_STATE_CLIENT_WAIT_EE, // Wait for EncryptedExtensions
> >      > + MG_TLS_STATE_CLIENT_WAIT_CERT, // Wait for Certificate
> >      > + MG_TLS_STATE_CLIENT_WAIT_CV, // Wait for CertificateVerify
> >      > + MG_TLS_STATE_CLIENT_WAIT_FINISHED, // Wait for Finished
> >      > + MG_TLS_STATE_CLIENT_CONNECTED, // Done
> >      > +
> >      > + // Server state machine:
> >      > + MG_TLS_STATE_SERVER_START, // Wait for ClientHello
> >      > + MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finished
> >      > + MG_TLS_STATE_SERVER_CONNECTED // Done
> >      > +};
> >      > +
> >      > +// per-connection TLS data
> >      > +struct tls_data {
> >      > + enum mg_tls_hs_state state; // keep track of connection
> >     handshake progress
> >      > +
> >      > + struct mg_iobuf send; // For the receive path, we're reusing
> >     c->rtls
> >      > + struct mg_iobuf recv; // While c->rtls contains full records,
> >     recv reuses
> >      > + // the same underlying buffer but points at individual
> >      > + // decrypted messages
> >      > + uint8_t content_type; // Last received record content type
> >      > +
> >      > + mg_sha256_ctx sha256; // incremental SHA-256 hash for TLS
> >     handshake
> >      > +
> >      > + uint32_t sseq; // server sequence number, used in encryption
> >      > + uint32_t cseq; // client sequence number, used in decryption
> >      > +
> >      > + uint8_t random[32]; // client random from ClientHello
> >      > + uint8_t session_id[32]; // client session ID between the
> >     handshake states
> >      > + uint8_t x25519_cli[32]; // client X25519 key between the
> >     handshake states
> >      > + uint8_t x25519_sec[32]; // x25519 secret between the handshake
> >     states
> >      > +
> >      > + int skip_verification; // perform checks on server certificate?
> >      > + struct mg_str server_cert_der; // server certificate in DER format
> >      > + uint8_t server_key[32]; // server EC private key
> >      > + char hostname[254]; // server hostname (client extension)
> >      > +
> >      > + uint8_t certhash[32]; // certificate message hash
> >      > + uint8_t pubkey[64]; // server EC public key to verify cert
> >      > + uint8_t sighash[32]; // server EC public key to verify cert
> >      > +
> >      > + // keys for AES encryption
> >      > + uint8_t handshake_secret[32];
> >      > + uint8_t server_write_key[16];
> >      > + uint8_t server_write_iv[12];
> >      > + uint8_t server_finished_key[32];
> >      > + uint8_t client_write_key[16];
> >      > + uint8_t client_write_iv[12];
> >      > + uint8_t client_finished_key[32];
> >      > +};
> >      > +
> >      > +#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) |
> >     MG_U8P(p)[1]))
> >      > +#define MG_LOAD_BE24(p) \
> >      > + ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) |
> >     MG_U8P(p)[2]))
> >      > +#define MG_STORE_BE16(p, n) \
> >      > + do { \
> >      > + MG_U8P(p)[0] = ((n) >> 8U) & 255; \
> >      > + MG_U8P(p)[1] = (n) & 255; \
> >      > + } while (0)
> >      > +
> >      > +#define TLS_RECHDR_SIZE 5 // 1 byte type, 2 bytes version, 2
> >     bytes length
> >      > +#define TLS_MSGHDR_SIZE 4 // 1 byte type, 3 bytes length
> >      > +
> >      > +#if 1
> >      > +static void mg_ssl_key_log(const char *label, uint8_t
> >     client_random[32],
> >      > + uint8_t *secret, size_t secretsz) {
> >      > + (void) label;
> >      > + (void) client_random;
> >      > + (void) secret;
> >      > + (void) secretsz;
> >      > +}
> >      > +#else
> >      > +#include <stdio.h>
> >      > +static void mg_ssl_key_log(const char *label, uint8_t
> >     client_random[32],
> >      > + uint8_t *secret, size_t secretsz) {
> >      > + char *keylogfile = getenv("SSLKEYLOGFILE");
> >      > + if (keylogfile == NULL) {
> >      > + return;
> >      > }
> >      > - if (opts->pass.len > 0) {
> >      > - total_len += 2 + (uint32_t) opts->pass.len;
> >      > - hdr[7] |= MQTT_HAS_PASSWORD;
> >      > + FILE *f = fopen(keylogfile, "a");
> >      > + fprintf(f, "%s ", label);
> >      > + for (int i = 0; i < 32; i++) {
> >      > + fprintf(f, "%02x", client_random[i]);
> >      > }
> >      > - if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
> >      > - total_len +=
> >      > - 4 + (uint32_t) opts->will_topic.len + (uint32_t)
> >     opts->will_message.len;
> >      > - hdr[7] |= MQTT_HAS_WILL;
> >      > + fprintf(f, " ");
> >      > + for (unsigned int i = 0; i < secretsz; i++) {
> >      > + fprintf(f, "%02x", secret[i]);
> >      > }
> >      > - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
> >      > - if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
> >      > - total_len += (uint32_t) cid.len;
> >      > - if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U
> >     : 0);
> >      > + fprintf(f, "\n");
> >      > + fclose(f);
> >      > +}
> >      > +#endif
> >      >
> >      > - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
> >      > - mg_send(c, hdr, sizeof(hdr));
> >      > - // keepalive == 0 means "do not disconnect us!"
> >      > - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
> >      > +// for derived tls keys we need SHA256([0]*32)
> >      > +static uint8_t zeros[32] = {0};
> >      > +static uint8_t zeros_sha256_digest[32] = {
> >      > + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
> >      > + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
> >      > + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
> >      >
> >      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // V5 properties
> >      > - mg_send_u16(c, mg_htons((uint16_t) cid.len));
> >      > - mg_send(c, cid.ptr, cid.len);
> >      > +// helper to hexdump buffers inline
> >      > +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t
> >     bufsz) {
> >      > + MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
> >      > +}
> >      >
> >      > - if (hdr[7] & MQTT_HAS_WILL) {
> >      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // will props
> >      > - mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
> >      > - mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
> >      > - mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
> >      > - mg_send(c, opts->will_message.ptr, opts->will_message.len);
> >      > +// helper utilities to parse ASN.1 DER
> >      > +struct mg_der_tlv {
> >      > + uint8_t type;
> >      > + uint32_t len;
> >      > + uint8_t *value;
> >      > +};
> >      > +
> >      > +// parse DER into a TLV record
> >      > +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct
> >     mg_der_tlv *tlv) {
> >      > + if (dersz < 2) {
> >      > + return -1;
> >      > }
> >      > - if (opts->user.len > 0) {
> >      > - mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
> >      > - mg_send(c, opts->user.ptr, opts->user.len);
> >      > + tlv->type = der[0];
> >      > + tlv->len = der[1];
> >      > + tlv->value = der + 2;
> >      > + if (tlv->len > 0x7f) {
> >      > + uint32_t i, n = tlv->len - 0x80;
> >      > + tlv->len = 0;
> >      > + for (i = 0; i < n; i++) {
> >      > + tlv->len = (tlv->len << 8) | (der[2 + i]);
> >      > + }
> >      > + tlv->value = der + 2 + n;
> >      > }
> >      > - if (opts->pass.len > 0) {
> >      > - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
> >      > - mg_send(c, opts->pass.ptr, opts->pass.len);
> >      > + if (der + dersz < tlv->value + tlv->len) {
> >      > + return -1;
> >      > }
> >      > + return 0;
> >      > }
> >      >
> >      > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> >      > - struct mg_str data, int qos, bool retain) {
> >      > - uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 :
> >     0)), zero = 0;
> >      > - uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
> >      > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char
> >     *) topic.ptr,
> >      > - (int) data.len, (char *) data.ptr));
> >      > - if (qos > 0) len += 2;
> >      > - if (c->is_mqtt5) len++;
> >      > - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
> >      > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
> >      > - mg_send(c, topic.ptr, topic.len);
> >      > - if (qos > 0) {
> >      > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> >      > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> >      > +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid,
> >     size_t oidsz,
> >      > + struct mg_der_tlv *tlv) {
> >      > + uint8_t *p, *end;
> >      > + struct mg_der_tlv child = {0, 0, NULL};
> >      > + if (mg_der_to_tlv(der, dersz, tlv) < 0) {
> >      > + return -1; // invalid DER
> >      > + } else if (tlv->type == 6) { // found OID, check value
> >      > + return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
> >      > + } else if ((tlv->type & 0x20) == 0) {
> >      > + return 0; // Primitive, but not OID: not found
> >      > + }
> >      > + // Constructed object: scan children
> >      > + p = tlv->value;
> >      > + end = tlv->value + tlv->len;
> >      > + while (end > p) {
> >      > + int r;
> >      > + mg_der_to_tlv(p, (size_t) (end - p), &child);
> >      > + r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
> >      > + if (r < 0) return -1; // error
> >      > + if (r > 0) return 1; // found OID!
> >      > + p = child.value + child.len;
> >      > }
> >      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> >      > - mg_send(c, data.ptr, data.len);
> >      > + return 0; // not found
> >      > }
> >      >
> >      > -void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic,
> >     int qos) {
> >      > - uint8_t qos_ = qos & 3, zero = 0;
> >      > - uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5
> >     ? 1 : 0);
> >      > - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
> >      > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
> >      > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
> >      > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
> >      > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
> >      > - mg_send(c, topic.ptr, topic.len);
> >      > - mg_send(c, &qos_, sizeof(qos_));
> >      > +// Did we receive a full TLS record in the c->rtls buffer?
> >      > +static bool mg_tls_got_record(struct mg_connection *c) {
> >      > + return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
> >      > + c->rtls.len >=
> >      > + (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
> >      > }
> >      >
> >      > -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
> >      > - struct mg_mqtt_message *m) {
> >      > - uint8_t lc = 0, *p, *end;
> >      > - uint32_t n = 0, len_len = 0;
> >      > +// Remove a single TLS record from the recv buffer
> >      > +static void mg_tls_drop_record(struct mg_connection *c) {
> >      > + struct mg_iobuf *rio = &c->rtls;
> >      > + uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
> >      > + mg_iobuf_del(rio, 0, n);
> >      > +}
> >      >
> >      > - memset(m, 0, sizeof(*m));
> >      > - m->dgram.ptr = (char *) buf;
> >      > - if (len < 2) return MQTT_INCOMPLETE;
> >      > - m->cmd = (uint8_t) (buf[0] >> 4);
> >      > - m->qos = (buf[0] >> 1) & 3;
> >      > +// Remove a single TLS message from decrypted buffer, remove the
> >     wrapping
> >      > +// record if it was the last message within a record
> >      > +static void mg_tls_drop_message(struct mg_connection *c) {
> >      > + uint32_t len;
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (tls->recv.len == 0) {
> >      > + return;
> >      > + }
> >      > + len = MG_LOAD_BE24(tls->recv.buf + 1);
> >      > + mg_sha256_update(&tls->sha256, tls->recv.buf, len +
> >     TLS_MSGHDR_SIZE);
> >      > + tls->recv.buf += len + TLS_MSGHDR_SIZE;
> >      > + tls->recv.len -= len + TLS_MSGHDR_SIZE;
> >      > + if (tls->recv.len == 0) {
> >      > + mg_tls_drop_record(c);
> >      > + }
> >      > +}
> >      >
> >      > - n = len_len = 0;
> >      > - p = (uint8_t *) buf + 1;
> >      > - while ((size_t) (p - buf) < len) {
> >      > - lc = *((uint8_t *) p++);
> >      > - n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
> >      > - len_len++;
> >      > - if (!(lc & 0x80)) break;
> >      > - if (len_len >= 4) return MQTT_MALFORMED;
> >      > +// TLS1.3 secret derivation based on the key label
> >      > +static void mg_tls_derive_secret(const char *label, uint8_t
> >     *key, size_t keysz,
> >      > + uint8_t *data, size_t datasz, uint8_t *hash,
> >      > + size_t hashsz) {
> >      > + size_t labelsz = strlen(label);
> >      > + uint8_t secret[32];
> >      > + uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
> >      > + // TODO: assert lengths of label, key, data and hash
> >      > + if (labelsz > 0) memmove(packed + 3, label, labelsz);
> >      > + packed[3 + labelsz] = (uint8_t) datasz;
> >      > + if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
> >      > + packed[4 + labelsz + datasz] = 1;
> >      > +
> >      > + mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
> >      > + memmove(hash, secret, hashsz);
> >      > +}
> >      > +
> >      > +// at this point we have x25519 shared secret, we can generate a
> >     set of derived
> >      > +// handshake encryption keys
> >      > +static void mg_tls_generate_handshake_keys(struct mg_connection
> >     *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > +
> >      > + mg_sha256_ctx sha256;
> >      > + uint8_t early_secret[32];
> >      > + uint8_t pre_extract_secret[32];
> >      > + uint8_t hello_hash[32];
> >      > + uint8_t server_hs_secret[32];
> >      > + uint8_t client_hs_secret[32];
> >      > +
> >      > + mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
> >      > + mg_tls_derive_secret("tls13 derived", early_secret, 32,
> >     zeros_sha256_digest,
> >      > + 32, pre_extract_secret, 32);
> >      > + mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
> >      > + sizeof(pre_extract_secret), tls->x25519_sec,
> >      > + sizeof(tls->x25519_sec));
> >      > + mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
> >      > +
> >      > + // mg_sha256_final is not idempotent, need to copy sha256
> >     context to calculate
> >      > + // the digest
> >      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> >      > + mg_sha256_final(hello_hash, &sha256);
> >      > +
> >      > + mg_tls_hexdump("hello hash", hello_hash, 32);
> >      > + // derive keys needed for the rest of the handshake
> >      > + mg_tls_derive_secret("tls13 s hs traffic",
> >     tls->handshake_secret, 32,
> >      > + hello_hash, 32, server_hs_secret, 32);
> >      > + mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
> >      > + tls->server_write_key, 16);
> >      > + mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
> >      > + tls->server_write_iv, 12);
> >      > + mg_tls_derive_secret("tls13 finished", server_hs_secret, 32,
> >     NULL, 0,
> >      > + tls->server_finished_key, 32);
> >      > +
> >      > + mg_tls_derive_secret("tls13 c hs traffic",
> >     tls->handshake_secret, 32,
> >      > + hello_hash, 32, client_hs_secret, 32);
> >      > + mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
> >      > + tls->client_write_key, 16);
> >      > + mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
> >      > + tls->client_write_iv, 12);
> >      > + mg_tls_derive_secret("tls13 finished", client_hs_secret, 32,
> >     NULL, 0,
> >      > + tls->client_finished_key, 32);
> >      > +
> >      > + mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
> >      > + mg_tls_hexdump("s key", tls->server_write_key, 16);
> >      > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> >      > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> >      > + mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
> >      > + mg_tls_hexdump("c key", tls->client_write_key, 16);
> >      > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> >      > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> >      > +
> >      > + mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> >      > + server_hs_secret, 32);
> >      > + mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
> >      > + client_hs_secret, 32);
> >      > +}
> >      > +
> >      > +static void mg_tls_generate_application_keys(struct
> >     mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + uint8_t hash[32];
> >      > + uint8_t premaster_secret[32];
> >      > + uint8_t master_secret[32];
> >      > + uint8_t server_secret[32];
> >      > + uint8_t client_secret[32];
> >      > +
> >      > + mg_sha256_ctx sha256;
> >      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> >      > + mg_sha256_final(hash, &sha256);
> >      > +
> >      > + mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
> >      > + zeros_sha256_digest, 32, premaster_secret, 32);
> >      > + mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
> >      > +
> >      > + mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32,
> >     hash, 32,
> >      > + server_secret, 32);
> >      > + mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
> >      > + tls->server_write_key, 16);
> >      > + mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
> >      > + tls->server_write_iv, 12);
> >      > + mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32,
> >     hash, 32,
> >      > + client_secret, 32);
> >      > + mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
> >      > + tls->client_write_key, 16);
> >      > + mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
> >      > + tls->client_write_iv, 12);
> >      > +
> >      > + mg_tls_hexdump("s ap traffic", server_secret, 32);
> >      > + mg_tls_hexdump("s key", tls->server_write_key, 16);
> >      > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
> >      > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
> >      > + mg_tls_hexdump("c ap traffic", client_secret, 32);
> >      > + mg_tls_hexdump("c key", tls->client_write_key, 16);
> >      > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
> >      > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
> >      > + tls->sseq = tls->cseq = 0;
> >      > +
> >      > + mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random,
> >     server_secret, 32);
> >      > + mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random,
> >     client_secret, 32);
> >      > +}
> >      > +
> >      > +// AES GCM encryption of the message + put encoded data into the
> >     write buffer
> >      > +static void mg_tls_encrypt(struct mg_connection *c, const
> >     uint8_t *msg,
> >      > + size_t msgsz, uint8_t msgtype) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *wio = &tls->send;
> >      > + uint8_t *outmsg;
> >      > + uint8_t *tag;
> >      > + size_t encsz = msgsz + 16 + 1;
> >      > + uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> >      > + (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
> >      > + uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
> >      > + (uint8_t) ((encsz >> 8) & 0xff),
> >      > + (uint8_t) (encsz & 0xff)};
> >      > + uint8_t nonce[12];
> >      > +
> >      > + mg_gcm_initialize();
> >      > +
> >      > + if (c->is_client) {
> >      > + memmove(nonce, tls->client_write_iv,
> >     sizeof(tls->client_write_iv));
> >      > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> >      > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> >      > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> >      > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> >      > + } else {
> >      > + memmove(nonce, tls->server_write_iv,
> >     sizeof(tls->server_write_iv));
> >      > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> >      > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> >      > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> >      > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
> >      > }
> >      > - end = p + n;
> >      > - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
> >      > - m->dgram.len = (size_t) (end - buf);
> >      >
> >      > - switch (m->cmd) {
> >      > - case MQTT_CMD_CONNACK:
> >      > - if (end - p < 2) return MQTT_MALFORMED;
> >      > - m->ack = p[1];
> >      > - break;
> >      > - case MQTT_CMD_PUBACK:
> >      > - case MQTT_CMD_PUBREC:
> >      > - case MQTT_CMD_PUBREL:
> >      > - case MQTT_CMD_PUBCOMP:
> >      > - case MQTT_CMD_SUBSCRIBE:
> >      > - case MQTT_CMD_SUBACK:
> >      > - case MQTT_CMD_UNSUBSCRIBE:
> >      > - case MQTT_CMD_UNSUBACK:
> >      > - if (p + 2 > end) return MQTT_MALFORMED;
> >      > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > - p += 2;
> >      > - break;
> >      > - case MQTT_CMD_PUBLISH: {
> >      > - if (p + 2 > end) return MQTT_MALFORMED;
> >      > - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > - m->topic.ptr = (char *) p + 2;
> >      > - p += 2 + m->topic.len;
> >      > - if (p > end) return MQTT_MALFORMED;
> >      > - if (m->qos > 0) {
> >      > - if (p + 2 > end) return MQTT_MALFORMED;
> >      > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
> >      > - p += 2;
> >      > - }
> >      > - if (p > end) return MQTT_MALFORMED;
> >      > - if (version == 5 && p + 2 < end) p += 1 + p[0]; // Skip options
> >      > - if (p > end) return MQTT_MALFORMED;
> >      > - m->data.ptr = (char *) p;
> >      > - m->data.len = (size_t) (end - p);
> >      > - break;
> >      > + mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
> >      > + mg_iobuf_resize(wio, wio->len + encsz);
> >      > + outmsg = wio->buf + wio->len;
> >      > + tag = wio->buf + wio->len + msgsz + 1;
> >      > + memmove(outmsg, msg, msgsz);
> >      > + outmsg[msgsz] = msgtype;
> >      > + if (c->is_client) {
> >      > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
> >     tls->client_write_key,
> >      > + sizeof(tls->client_write_key), nonce, sizeof(nonce),
> >      > + associated_data, sizeof(associated_data), tag, 16);
> >      > + tls->cseq++;
> >      > + } else {
> >      > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
> >     tls->server_write_key,
> >      > + sizeof(tls->server_write_key), nonce, sizeof(nonce),
> >      > + associated_data, sizeof(associated_data), tag, 16);
> >      > + tls->sseq++;
> >      > + }
> >      > + wio->len += encsz;
> >      > +}
> >      > +
> >      > +// read an encrypted record, decrypt it in place
> >      > +static int mg_tls_recv_record(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *rio = &c->rtls;
> >      > + uint16_t msgsz;
> >      > + uint8_t *msg;
> >      > + uint8_t nonce[12];
> >      > + int r;
> >      > + if (tls->recv.len > 0) {
> >      > + return 0; /* some data from previous record is still present */
> >      > + }
> >      > + for (;;) {
> >      > + if (!mg_tls_got_record(c)) {
> >      > + return MG_IO_WAIT;
> >      > }
> >      > - default:
> >      > + if (rio->buf[0] == MG_TLS_APP_DATA) {
> >      > break;
> >      > + } else if (rio->buf[0] ==
> >      > + MG_TLS_CHANGE_CIPHER) { // Skip ChangeCipher messages
> >      > + mg_tls_drop_record(c);
> >      > + } else if (rio->buf[0] == MG_TLS_ALERT) { // Skip Alerts
> >      > + MG_INFO(("TLS ALERT packet received"));
> >      > + mg_tls_drop_record(c);
> >      > + } else {
> >      > + mg_error(c, "unexpected packet");
> >      > + return -1;
> >      > + }
> >      > }
> >      > - return MQTT_OK;
> >      > +
> >      > + mg_gcm_initialize();
> >      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> >      > + msg = rio->buf + 5;
> >      > + if (c->is_client) {
> >      > + memmove(nonce, tls->server_write_iv,
> >     sizeof(tls->server_write_iv));
> >      > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
> >      > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
> >      > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
> >      > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
> >      > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
> >      > + sizeof(tls->server_write_key), nonce, sizeof(nonce));
> >      > + tls->sseq++;
> >      > + } else {
> >      > + memmove(nonce, tls->client_write_iv,
> >     sizeof(tls->client_write_iv));
> >      > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
> >      > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
> >      > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
> >      > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
> >      > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
> >      > + sizeof(tls->client_write_key), nonce, sizeof(nonce));
> >      > + tls->cseq++;
> >      > + }
> >      > + r = msgsz - 16 - 1;
> >      > + tls->content_type = msg[msgsz - 16 - 1];
> >      > + tls->recv.buf = msg;
> >      > + tls->recv.size = tls->recv.len = msgsz - 16 - 1;
> >      > + return r;
> >      > }
> >      >
> >      > -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
> >      > - void *fn_data) {
> >      > - if (ev == MG_EV_READ) {
> >      > - for (;;) {
> >      > - uint8_t version = c->is_mqtt5 ? 5 : 4;
> >      > - struct mg_mqtt_message mm;
> >      > - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
> >      > - if (rc == MQTT_MALFORMED) {
> >      > - MG_ERROR(("%lu MQTT malformed message", c->id));
> >      > - c->is_closing = 1;
> >      > - break;
> >      > - } else if (rc == MQTT_OK) {
> >      > - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
> >      > - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
> >      > - switch (mm.cmd) {
> >      > - case MQTT_CMD_CONNACK:
> >      > - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
> >      > - if (mm.ack == 0) {
> >      > - MG_DEBUG(("%lu Connected", c->id));
> >      > - } else {
> >      > - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
> >      > - c->is_closing = 1;
> >      > - }
> >      > - break;
> >      > - case MQTT_CMD_PUBLISH: {
> >      > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
> >      > - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
> >      > - if (mm.qos > 0) {
> >      > - uint16_t id = mg_htons(mm.id <http://mm.id>);
> >      > - mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
> >      > - mg_send(c, &id, sizeof(id));
> >      > - }
> >      > - mg_call(c, MG_EV_MQTT_MSG, &mm);
> >      > - break;
> >      > - }
> >      > - }
> >      > - mg_call(c, MG_EV_MQTT_CMD, &mm);
> >      > - mg_iobuf_del(&c->recv, 0, mm.dgram.len);
> >      > - } else {
> >      > - break;
> >      > +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
> >      > + uint8_t hash[32]) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + uint8_t sig_content[130] = {
> >      > + " "
> >      > + " "
> >      > + "TLS 1.3, server CertificateVerify\0"};
> >      > + mg_sha256_ctx sha256;
> >      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> >      > + mg_sha256_final(sig_content + 98, &sha256);
> >      > +
> >      > + mg_sha256_init(&sha256);
> >      > + mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
> >      > + mg_sha256_final(hash, &sha256);
> >      > +}
> >      > +
> >      > +// read and parse ClientHello record
> >      > +static int mg_tls_server_recv_hello(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *rio = &c->rtls;
> >      > + uint8_t session_id_len;
> >      > + uint16_t j;
> >      > + uint16_t cipher_suites_len;
> >      > + uint16_t ext_len;
> >      > + uint8_t *ext;
> >      > + uint16_t msgsz;
> >      > +
> >      > + if (!mg_tls_got_record(c)) {
> >      > + return MG_IO_WAIT;
> >      > + }
> >      > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
> >     MG_TLS_CLIENT_HELLO) {
> >      > + mg_error(c, "not a client hello packet");
> >      > + return -1;
> >      > + }
> >      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> >      > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> >      > + // store client random
> >      > + memmove(tls->random, rio->buf + 11, sizeof(tls->random));
> >      > + // store session_id
> >      > + session_id_len = rio->buf[43];
> >      > + if (session_id_len == sizeof(tls->session_id)) {
> >      > + memmove(tls->session_id, rio->buf + 44, session_id_len);
> >      > + } else if (session_id_len != 0) {
> >      > + MG_INFO(("bad session id len"));
> >      > + }
> >      > + cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
> >      > + ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len +
> >     cipher_suites_len);
> >      > + ext = rio->buf + 50 + session_id_len + cipher_suites_len;
> >      > + for (j = 0; j < ext_len;) {
> >      > + uint16_t k;
> >      > + uint16_t key_exchange_len;
> >      > + uint8_t *key_exchange;
> >      > + uint16_t n = MG_LOAD_BE16(ext + j + 2);
> >      > + if (ext[j] != 0x00 ||
> >      > + ext[j + 1] != 0x33) { // not a key share extension, ignore
> >      > + j += (uint16_t) (n + 4);
> >      > + continue;
> >      > + }
> >      > + key_exchange_len = MG_LOAD_BE16(ext + j + 5);
> >      > + key_exchange = ext + j + 6;
> >      > + for (k = 0; k < key_exchange_len;) {
> >      > + uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
> >      > + if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1]
> >     == 0x1d) {
> >      > + memmove(tls->x25519_cli, key_exchange + k + 4, m);
> >      > + mg_tls_drop_record(c);
> >      > + return 0;
> >      > }
> >      > + k += (uint16_t) (m + 4);
> >      > }
> >      > + j += (uint16_t) (n + 4);
> >      > }
> >      > - (void) ev_data;
> >      > - (void) fn_data;
> >      > -}
> >      > -
> >      > -void mg_mqtt_ping(struct mg_connection *nc) {
> >      > - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
> >      > -}
> >      > -
> >      > -void mg_mqtt_pong(struct mg_connection *nc) {
> >      > - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
> >      > + mg_error(c, "bad client hello");
> >      > + return -1;
> >      > }
> >      >
> >      > -void mg_mqtt_disconnect(struct mg_connection *nc) {
> >      > - mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
> >      > -}
> >      > +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
> >      > +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
> >      > +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
> >      > +
> >      > +// put ServerHello record into wio buffer
> >      > +static void mg_tls_server_send_hello(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *wio = &tls->send;
> >      > +
> >      > + uint8_t msg_server_hello[122] = {
> >      > + // server hello, tls 1.2
> >      > + 0x02,
> >      > + 0x00,
> >      > + 0x00,
> >      > + 0x76,
> >      > + 0x03,
> >      > + 0x03,
> >      > + // random (32 bytes)
> >      > + PLACEHOLDER_32B,
> >      > + // session ID length + session ID (32 bytes)
> >      > + 0x20,
> >      > + PLACEHOLDER_32B,
> >      > +#if defined(CHACHA20) && CHACHA20
> >      > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
> >      > + 0x13,
> >      > + 0x03,
> >      > + 0x00,
> >      > +#else
> >      > + // TLS_AES_128_GCM_SHA256 + no compression
> >      > + 0x13,
> >      > + 0x01,
> >      > + 0x00,
> >      > +#endif
> >      > + // extensions + keyshare
> >      > + 0x00,
> >      > + 0x2e,
> >      > + 0x00,
> >      > + 0x33,
> >      > + 0x00,
> >      > + 0x24,
> >      > + 0x00,
> >      > + 0x1d,
> >      > + 0x00,
> >      > + 0x20,
> >      > + // x25519 keyshare
> >      > + PLACEHOLDER_32B,
> >      > + // supported versions (tls1.3 == 0x304)
> >      > + 0x00,
> >      > + 0x2b,
> >      > + 0x00,
> >      > + 0x02,
> >      > + 0x03,
> >      > + 0x04
> >      > + };
> >      >
> >      > -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > - const struct mg_mqtt_opts *opts,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
> >      > - if (c != NULL) {
> >      > - struct mg_mqtt_opts empty;
> >      > - memset(&empty, 0, sizeof(empty));
> >      > - mg_mqtt_login(c, opts == NULL ? &empty : opts);
> >      > - c->pfn = mqtt_cb;
> >      > + // calculate keyshare
> >      > + uint8_t x25519_pub[X25519_BYTES];
> >      > + uint8_t x25519_prv[X25519_BYTES];
> >      > + mg_random(x25519_prv, sizeof(x25519_prv));
> >      > + mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
> >      > + mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
> >      > + mg_tls_hexdump("s x25519 sec", tls->x25519_sec,
> >     sizeof(tls->x25519_sec));
> >      > +
> >      > + // fill in the gaps: random + session ID + keyshare
> >      > + memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
> >      > + memmove(msg_server_hello + 39, tls->session_id,
> >     sizeof(tls->session_id));
> >      > + memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
> >      > +
> >      > + // server hello message
> >      > + mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
> >      > + mg_iobuf_add(wio, wio->len, msg_server_hello,
> >     sizeof(msg_server_hello));
> >      > + mg_sha256_update(&tls->sha256, msg_server_hello,
> >     sizeof(msg_server_hello));
> >      > +
> >      > + // change cipher message
> >      > + mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
> >      > +}
> >      > +
> >      > +static void mg_tls_server_send_ext(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + // server extensions
> >      > + uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
> >      > + mg_sha256_update(&tls->sha256, ext, sizeof(ext));
> >      > + mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
> >      > +}
> >      > +
> >      > +static void mg_tls_server_send_cert(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + // server DER certificate (empty)
> >      > + size_t n = tls->server_cert_der.len;
> >      > + uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
> >      > + if (cert == NULL) {
> >      > + mg_error(c, "tls cert oom");
> >      > + return;
> >      > }
> >      > - return c;
> >      > -}
> >      > + cert[0] = 0x0b; // handshake header
> >      > + cert[1] = (uint8_t) (((n + 9) >> 16) & 255U); // 3 bytes:
> >     payload length
> >      > + cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
> >      > + cert[3] = (uint8_t) ((n + 9) & 255U);
> >      > + cert[4] = 0; // request context
> >      > + cert[5] = (uint8_t) (((n + 5) >> 16) & 255U); // 3 bytes: cert
> >     (s) length
> >      > + cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
> >      > + cert[7] = (uint8_t) ((n + 5) & 255U);
> >      > + cert[8] =
> >      > + (uint8_t) (((n) >> 16) & 255U); // 3 bytes: first (and only)
> >     cert len
> >      > + cert[9] = (uint8_t) (((n) >> 8) & 255U);
> >      > + cert[10] = (uint8_t) (n & 255U);
> >      > + // bytes 11+ are certificate in DER format
> >      > + memmove(cert + 11, tls->server_cert_der.buf, n);
> >      > + cert[11 + n] = cert[12 + n] = 0; // certificate extensions (none)
> >      > + mg_sha256_update(&tls->sha256, cert, 13 + n);
> >      > + mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
> >      > + free(cert);
> >      > +}
> >      > +
> >      > +// type adapter between uECC hash context and our sha256
> >     implementation
> >      > +typedef struct SHA256_HashContext {
> >      > + MG_UECC_HashContext uECC;
> >      > + mg_sha256_ctx ctx;
> >      > +} SHA256_HashContext;
> >      > +
> >      > +static void init_SHA256(const MG_UECC_HashContext *base) {
> >      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> >      > + mg_sha256_init(&c->ctx);
> >      > +}
> >      > +
> >      > +static void update_SHA256(const MG_UECC_HashContext *base,
> >      > + const uint8_t *message, unsigned message_size) {
> >      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> >      > + mg_sha256_update(&c->ctx, message, message_size);
> >      > +}
> >      > +static void finish_SHA256(const MG_UECC_HashContext *base,
> >      > + uint8_t *hash_result) {
> >      > + SHA256_HashContext *c = (SHA256_HashContext *) base;
> >      > + mg_sha256_final(hash_result, &c->ctx);
> >      > +}
> >      > +
> >      > +static void mg_tls_server_send_cert_verify(struct mg_connection
> >     *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + // server certificate verify packet
> >      > + uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00,
> >     0x00};
> >      > + size_t sigsz, verifysz = 0;
> >      > + uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
> >      > + struct SHA256_HashContext ctx = {
> >      > + {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
> >      > + {{0}, 0, 0, {0}}};
> >      > + int neg1, neg2;
> >      > + uint8_t sig[64] = {0};
> >      > +
> >      > + mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
> >      > +
> >      > + mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash),
> >     &ctx.uECC,
> >      > + sig, mg_uecc_secp256r1());
> >      > +
> >      > + neg1 = !!(sig[0] & 0x80);
> >      > + neg2 = !!(sig[32] & 0x80);
> >      > + verify[8] = 0x30; // ASN.1 SEQUENCE
> >      > + verify[9] = (uint8_t) (68 + neg1 + neg2);
> >      > + verify[10] = 0x02; // ASN.1 INTEGER
> >      > + verify[11] = (uint8_t) (32 + neg1);
> >      > + memmove(verify + 12 + neg1, sig, 32);
> >      > + verify[12 + 32 + neg1] = 0x02; // ASN.1 INTEGER
> >      > + verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
> >      > + memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
> >      > +
> >      > + sigsz = (size_t) (70 + neg1 + neg2);
> >      > + verifysz = 8U + sigsz;
> >      > + verify[3] = (uint8_t) (sigsz + 4);
> >      > + verify[7] = (uint8_t) sigsz;
> >      > +
> >      > + mg_sha256_update(&tls->sha256, verify, verifysz);
> >      > + mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
> >      > +}
> >      > +
> >      > +static void mg_tls_server_send_finish(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *wio = &tls->send;
> >      > + mg_sha256_ctx sha256;
> >      > + uint8_t hash[32];
> >      > + uint8_t finish[36] = {0x14, 0, 0, 32};
> >      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> >      > + mg_sha256_final(hash, &sha256);
> >      > + mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash,
> >     32);
> >      > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> >      > + mg_io_send(c, wio->buf, wio->len);
> >      > + wio->len = 0;
> >      > +
> >      > + mg_sha256_update(&tls->sha256, finish, sizeof(finish));
> >      > +}
> >      > +
> >      > +static int mg_tls_server_recv_finish(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + // we have to backup sha256 value to restore it later, since
> >     Finished record
> >      > + // is exceptional and is not supposed to be added to the
> >     rolling hash
> >      > + // calculation.
> >      > + mg_sha256_ctx sha256 = tls->sha256;
> >      > + if (mg_tls_recv_record(c) < 0) {
> >      > + return -1;
> >      > + }
> >      > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> >      > + mg_error(c, "expected Finish but got msg 0x%02x",
> >     tls->recv.buf[0]);
> >      > + return -1;
> >      > + }
> >      > + mg_tls_drop_message(c);
> >      >
> >      > -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
> >     char *url,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
> >      > - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
> >      > - return c;
> >      > + // restore hash
> >      > + tls->sha256 = sha256;
> >      > + return 0;
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/net.c"
> >      > +static void mg_tls_client_send_hello(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *wio = &tls->send;
> >      > +
> >      > + const char *hostname = tls->hostname;
> >      > + size_t hostnamesz = strlen(tls->hostname);
> >      > + uint8_t x25519_pub[X25519_BYTES];
> >      > +
> >      > + uint8_t msg_client_hello[162 + 32] = {
> >      > + // TLS Client Hello header reported as TLS1.2 (5)
> >      > + 0x16,
> >      > + 0x03,
> >      > + 0x01,
> >      > + 0x00,
> >      > + 0xfe,
> >      > + // server hello, tls 1.2 (6)
> >      > + 0x01,
> >      > + 0x00,
> >      > + 0x00,
> >      > + 0x8c,
> >      > + 0x03,
> >      > + 0x03,
> >      > + // random (32 bytes)
> >      > + PLACEHOLDER_32B,
> >      > + // session ID length + session ID (32 bytes)
> >      > + 0x20,
> >      > + PLACEHOLDER_32B,
> >      > +#if defined(CHACHA20) && CHACHA20
> >      > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
> >      > + 0x13,
> >      > + 0x03,
> >      > + 0x00,
> >      > +#else
> >      > + 0x00,
> >      > + 0x02, // size = 2 bytes
> >      > + 0x13,
> >      > + 0x01, // TLS_AES_128_GCM_SHA256
> >      > + 0x01,
> >      > + 0x00, // no compression
> >      > #endif
> >      >
> >      > + // extensions + keyshare
> >      > + 0x00,
> >      > + 0xfe,
> >      > + // x25519 keyshare
> >      > + 0x00,
> >      > + 0x33,
> >      > + 0x00,
> >      > + 0x26,
> >      > + 0x00,
> >      > + 0x24,
> >      > + 0x00,
> >      > + 0x1d,
> >      > + 0x00,
> >      > + 0x20,
> >      > + PLACEHOLDER_32B,
> >      > + // supported groups (x25519)
> >      > + 0x00,
> >      > + 0x0a,
> >      > + 0x00,
> >      > + 0x04,
> >      > + 0x00,
> >      > + 0x02,
> >      > + 0x00,
> >      > + 0x1d,
> >      > + // supported versions (tls1.3 == 0x304)
> >      > + 0x00,
> >      > + 0x2b,
> >      > + 0x00,
> >      > + 0x03,
> >      > + 0x02,
> >      > + 0x03,
> >      > + 0x04,
> >      > + // session ticket (none)
> >      > + 0x00,
> >      > + 0x23,
> >      > + 0x00,
> >      > + 0x00,
> >      > + // signature algorithms (we don't care, so list all the common
> >     ones)
> >      > + 0x00,
> >      > + 0x0d,
> >      > + 0x00,
> >      > + 0x24,
> >      > + 0x00,
> >      > + 0x22,
> >      > + 0x04,
> >      > + 0x03,
> >      > + 0x05,
> >      > + 0x03,
> >      > + 0x06,
> >      > + 0x03,
> >      > + 0x08,
> >      > + 0x07,
> >      > + 0x08,
> >      > + 0x08,
> >      > + 0x08,
> >      > + 0x1a,
> >      > + 0x08,
> >      > + 0x1b,
> >      > + 0x08,
> >      > + 0x1c,
> >      > + 0x08,
> >      > + 0x09,
> >      > + 0x08,
> >      > + 0x0a,
> >      > + 0x08,
> >      > + 0x0b,
> >      > + 0x08,
> >      > + 0x04,
> >      > + 0x08,
> >      > + 0x05,
> >      > + 0x08,
> >      > + 0x06,
> >      > + 0x04,
> >      > + 0x01,
> >      > + 0x05,
> >      > + 0x01,
> >      > + 0x06,
> >      > + 0x01,
> >      > + // server name
> >      > + 0x00,
> >      > + 0x00,
> >      > + 0x00,
> >      > + 0xfe,
> >      > + 0x00,
> >      > + 0xfe,
> >      > + 0x00,
> >      > + 0x00,
> >      > + 0xfe
> >      > + };
> >      >
> >      > + // patch ClientHello with correct hostname length + offset:
> >      > + MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
> >      > + MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
> >      > + MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
> >      > + MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
> >      > + MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
> >      > + MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
> >      > +
> >      > + // calculate keyshare
> >      > + mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
> >      > + mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
> >      > +
> >      > + // fill in the gaps: random + session ID + keyshare
> >      > + mg_random(tls->session_id, sizeof(tls->session_id));
> >      > + mg_random(tls->random, sizeof(tls->random));
> >      > + memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
> >      > + memmove(msg_client_hello + 44, tls->session_id,
> >     sizeof(tls->session_id));
> >      > + memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
> >      > +
> >      > + // server hello message
> >      > + mg_iobuf_add(wio, wio->len, msg_client_hello,
> >     sizeof(msg_client_hello));
> >      > + mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
> >      > + mg_sha256_update(&tls->sha256, msg_client_hello + 5,
> >      > + sizeof(msg_client_hello) - 5);
> >      > + mg_sha256_update(&tls->sha256, (uint8_t *) hostname,
> >     strlen(hostname));
> >      > +
> >      > + // change cipher message
> >      > + mg_iobuf_add(wio, wio->len, (const char *)
> >     "\x14\x03\x03\x00\x01\x01", 6);
> >      > + mg_io_send(c, wio->buf, wio->len);
> >      > + wio->len = 0;
> >      > +}
> >      > +
> >      > +static int mg_tls_client_recv_hello(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *rio = &c->rtls;
> >      > + uint16_t msgsz;
> >      > + uint8_t *ext;
> >      > + uint16_t ext_len;
> >      > + int j;
> >      > +
> >      > + if (!mg_tls_got_record(c)) {
> >      > + return MG_IO_WAIT;
> >      > + }
> >      > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
> >     MG_TLS_SERVER_HELLO) {
> >      > + if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
> >      > + mg_error(c, "tls alert %d", rio->buf[6]);
> >      > + return -1;
> >      > + }
> >      > + MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0],
> >     rio->buf[5]));
> >      > + mg_error(c, "not a server hello packet");
> >      > + return -1;
> >      > + }
> >      >
> >      > -
> >      > -
> >      > -
> >      > -
> >      > -size_t mg_vprintf(struct mg_connection *c, const char *fmt,
> >     va_list *ap) {
> >      > - size_t old = c->send.len;
> >      > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> >      > - return c->send.len - old;
> >      > -}
> >      > -
> >      > -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
> >      > - size_t len = 0;
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - len = mg_vprintf(c, fmt, &ap);
> >      > - va_end(ap);
> >      > - return len;
> >      > -}
> >      > -
> >      > -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
> >      > - if (mg_vcasecmp(&str, "localhost") != 0) return false;
> >      > - addr->ip = mg_htonl(0x7f000001);
> >      > - addr->is_ip6 = false;
> >      > - return true;
> >      > + msgsz = MG_LOAD_BE16(rio->buf + 3);
> >      > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
> >      > +
> >      > + ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
> >      > + ext = rio->buf + 5 + 39 + 32 + 3 + 2;
> >      > +
> >      > + for (j = 0; j < ext_len;) {
> >      > + uint16_t ext_type = MG_LOAD_BE16(ext + j);
> >      > + uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
> >      > + uint16_t group;
> >      > + uint8_t *key_exchange;
> >      > + uint16_t key_exchange_len;
> >      > + if (ext_type != 0x0033) { // not a key share extension, ignore
> >      > + j += (uint16_t) (ext_len2 + 4);
> >      > + continue;
> >      > + }
> >      > + group = MG_LOAD_BE16(ext + j + 4);
> >      > + if (group != 0x001d) {
> >      > + mg_error(c, "bad key exchange group");
> >      > + return -1;
> >      > + }
> >      > + key_exchange_len = MG_LOAD_BE16(ext + j + 6);
> >      > + key_exchange = ext + j + 8;
> >      > + if (key_exchange_len != 32) {
> >      > + mg_error(c, "bad key exchange length");
> >      > + return -1;
> >      > + }
> >      > + mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
> >      > + mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
> >      > + mg_tls_drop_record(c);
> >      > + /* generate handshake keys */
> >      > + mg_tls_generate_handshake_keys(c);
> >      > + return 0;
> >      > + }
> >      > + mg_error(c, "bad client hello");
> >      > + return -1;
> >      > }
> >      >
> >      > -static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
> >      > - if (str.len > 0) return false;
> >      > - addr->ip = 0;
> >      > - addr->is_ip6 = false;
> >      > - return true;
> >      > +static int mg_tls_client_recv_ext(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (mg_tls_recv_record(c) < 0) {
> >      > + return -1;
> >      > + }
> >      > + if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
> >      > + mg_error(c, "expected server extensions but got msg 0x%02x",
> >      > + tls->recv.buf[0]);
> >      > + return -1;
> >      > + }
> >      > + mg_tls_drop_message(c);
> >      > + return 0;
> >      > }
> >      >
> >      > -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
> >      > - uint8_t data[4] = {0, 0, 0, 0};
> >      > - size_t i, num_dots = 0;
> >      > - for (i = 0; i < str.len; i++) {
> >      > - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> >      > - int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
> >      > - if (octet > 255) return false;
> >      > - data[num_dots] = (uint8_t) octet;
> >      > - } else if (str.ptr[i] == '.') {
> >      > - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return
> >     false;
> >      > - num_dots++;
> >      > - } else {
> >      > - return false;
> >      > - }
> >      > +static int mg_tls_client_recv_cert(struct mg_connection *c) {
> >      > + uint8_t *cert;
> >      > + uint32_t certsz;
> >      > + struct mg_der_tlv oid, pubkey, seq, subj;
> >      > + int subj_match = 0;
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (mg_tls_recv_record(c) < 0) {
> >      > + return -1;
> >      > + }
> >      > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
> >      > + mg_error(c, "expected server certificate but got msg 0x%02x",
> >      > + tls->recv.buf[0]);
> >      > + return -1;
> >      > + }
> >      > + if (tls->skip_verification) {
> >      > + mg_tls_drop_message(c);
> >      > + return 0;
> >      > }
> >      > - if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
> >      > - memcpy(&addr->ip, data, sizeof(data));
> >      > - addr->is_ip6 = false;
> >      > - return true;
> >      > -}
> >      >
> >      > -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
> >      > - int i;
> >      > - if (str.len < 14) return false;
> >      > - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] !=
> >     ':') return false;
> >      > - for (i = 2; i < 6; i++) {
> >      > - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
> >      > + if (tls->recv.len < 11) {
> >      > + mg_error(c, "certificate list too short");
> >      > + return -1;
> >      > }
> >      > - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return
> >     false;
> >      > - memset(addr->ip6, 0, sizeof(addr->ip6));
> >      > - addr->ip6[10] = addr->ip6[11] = 255;
> >      > - memcpy(&addr->ip6[12], &addr->ip, 4);
> >      > - addr->is_ip6 = true;
> >      > - return true;
> >      > -}
> >      >
> >      > -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
> >      > - size_t i, j = 0, n = 0, dc = 42;
> >      > - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
> >      > - if (mg_v4mapped(str, addr)) return true;
> >      > - for (i = 0; i < str.len; i++) {
> >      > - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
> >      > - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
> >      > - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
> >      > - unsigned long val;
> >      > - if (i > j + 3) return false;
> >      > - // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1),
> >     &str.ptr[j]));
> >      > - val = mg_unhexn(&str.ptr[j], i - j + 1);
> >      > - addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
> >      > - addr->ip6[n + 1] = (uint8_t) (val & 255);
> >      > - } else if (str.ptr[i] == ':') {
> >      > - j = i + 1;
> >      > - if (i > 0 && str.ptr[i - 1] == ':') {
> >      > - dc = n; // Double colon
> >      > - if (i > 1 && str.ptr[i - 2] == ':') return false;
> >      > - } else if (i > 0) {
> >      > - n += 2;
> >      > + cert = tls->recv.buf + 11;
> >      > + certsz = MG_LOAD_BE24(tls->recv.buf + 8);
> >      > + if (certsz > tls->recv.len - 11) {
> >      > + mg_error(c, "certificate too long: %d vs %d", certsz,
> >     tls->recv.len - 11);
> >      > + return -1;
> >      > + }
> >      > +
> >      > + do {
> >      > + // secp256r1 public key
> >      > + if (mg_der_find(cert, certsz,
> >      > + (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
> >      > + &oid) < 0) {
> >      > + mg_error(c, "certificate secp256r1 public key OID not found");
> >      > + return -1;
> >      > + }
> >      > + if (mg_der_to_tlv(oid.value + oid.len,
> >      > + (size_t) (cert + certsz - (oid.value + oid.len)),
> >      > + &pubkey) < 0) {
> >      > + mg_error(c, "certificate secp256r1 public key not found");
> >      > + return -1;
> >      > + }
> >      > +
> >      > + // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32
> >     content bytes
> >      > + if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] !=
> >     0 ||
> >      > + pubkey.value[1] != 4) {
> >      > + mg_error(c, "unsupported public key bitstring encoding");
> >      > + return -1;
> >      > + }
> >      > + memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
> >      > + } while (0);
> >      > +
> >      > + // Subject Alternative Names
> >      > + do {
> >      > + if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3,
> >     &oid) < 0) {
> >      > + mg_error(c, "certificate does not contain subject alternative
> >     names");
> >      > + return -1;
> >      > + }
> >      > + if (mg_der_to_tlv(oid.value + oid.len,
> >      > + (size_t) (cert + certsz - (oid.value + oid.len)),
> >      > + &seq) < 0) {
> >      > + mg_error(c, "certificate subject alternative names not found");
> >      > + return -1;
> >      > + }
> >      > + if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
> >      > + mg_error(
> >      > + c,
> >      > + "certificate subject alternative names is not a constructed
> >     object");
> >      > + return -1;
> >      > + }
> >      > + MG_VERBOSE(("verify hostname %s", tls->hostname));
> >      > + while (seq.len > 0) {
> >      > + if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
> >      > + mg_error(c, "bad subject alternative name");
> >      > + return -1;
> >      > }
> >      > - if (n > 14) return false;
> >      > - addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing ::
> >      > - } else {
> >      > - return false;
> >      > + MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
> >      > + if (mg_match(mg_str((const char *) tls->hostname),
> >      > + mg_str_n((const char *) subj.value, subj.len), NULL)) {
> >      > + subj_match = 1;
> >      > + break;
> >      > + }
> >      > + seq.len = (uint32_t) (seq.value + seq.len - (subj.value +
> >     subj.len));
> >      > + seq.value = subj.value + subj.len;
> >      > + }
> >      > + if (!subj_match) {
> >      > + mg_error(c, "certificate did not match the hostname");
> >      > + return -1;
> >      > }
> >      > + } while (0);
> >      > +
> >      > + mg_tls_drop_message(c);
> >      > + mg_tls_calc_cert_verify_hash(c, tls->sighash);
> >      > + return 0;
> >      > +}
> >      > +
> >      > +static int mg_tls_client_recv_cert_verify(struct mg_connection
> >     *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (mg_tls_recv_record(c) < 0) {
> >      > + return -1;
> >      > }
> >      > - if (n < 14 && dc == 42) return false;
> >      > - if (n < 14) {
> >      > - memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
> >      > - memset(&addr->ip6[dc], 0, 14 - n);
> >      > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
> >      > + mg_error(c, "expected server certificate verify but got msg
> >     0x%02x",
> >      > + tls->recv.buf[0]);
> >      > + return -1;
> >      > + }
> >      > + // Ignore CertificateVerify is strict checks are not required
> >      > + if (tls->skip_verification) {
> >      > + mg_tls_drop_message(c);
> >      > + return 0;
> >      > }
> >      > - addr->is_ip6 = true;
> >      > - return true;
> >      > -}
> >      >
> >      > -bool mg_aton(struct mg_str str, struct mg_addr *addr) {
> >      > - // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
> >      > - return mg_atone(str, addr) || mg_atonl(str, addr) ||
> >     mg_aton4(str, addr) ||
> >      > - mg_aton6(str, addr);
> >      > + // Extract certificate signature and verify it using pubkey and
> >     sighash
> >      > + do {
> >      > + uint8_t sig[64];
> >      > + struct mg_der_tlv seq, a, b;
> >      > + if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) <
> >     0) {
> >      > + mg_error(c, "verification message is not an ASN.1 DER sequence");
> >      > + return -1;
> >      > + }
> >      > + if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
> >      > + mg_error(c, "missing first part of the signature");
> >      > + return -1;
> >      > + }
> >      > + if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
> >      > + mg_error(c, "missing second part of the signature");
> >      > + return -1;
> >      > + }
> >      > + // Integers may be padded with zeroes
> >      > + if (a.len > 32) {
> >      > + a.value = a.value + (a.len - 32);
> >      > + a.len = 32;
> >      > + }
> >      > + if (b.len > 32) {
> >      > + b.value = b.value + (b.len - 32);
> >      > + b.len = 32;
> >      > + }
> >      > +
> >      > + memmove(sig, a.value, a.len);
> >      > + memmove(sig + 32, b.value, b.len);
> >      > +
> >      > + if (mg_uecc_verify(tls->pubkey, tls->sighash,
> >     sizeof(tls->sighash), sig,
> >      > + mg_uecc_secp256r1()) != 1) {
> >      > + mg_error(c, "failed to verify certificate");
> >      > + return -1;
> >      > + }
> >      > + } while (0);
> >      > +
> >      > + mg_tls_drop_message(c);
> >      > + return 0;
> >      > }
> >      >
> >      > -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
> >      > - struct mg_connection *c =
> >      > - (struct mg_connection *) calloc(1, sizeof(*c) +
> >     mgr->extraconnsize);
> >      > - if (c != NULL) {
> >      > - c->mgr = mgr;
> >      > - c->send.align = c->recv.align = MG_IO_SIZE;
> >      > - c->id = ++mgr->nextid;
> >      > +static int mg_tls_client_recv_finish(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (mg_tls_recv_record(c) < 0) {
> >      > + return -1;
> >      > }
> >      > - return c;
> >      > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
> >      > + mg_error(c, "expected server finished but got msg 0x%02x",
> >      > + tls->recv.buf[0]);
> >      > + return -1;
> >      > + }
> >      > + mg_tls_drop_message(c);
> >      > + return 0;
> >      > }
> >      >
> >      > -void mg_close_conn(struct mg_connection *c) {
> >      > - mg_resolve_cancel(c); // Close any pending DNS query
> >      > - LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
> >      > - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
> >      > - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
> >      > - // Order of operations is important. `MG_EV_CLOSE` event must
> >     be fired
> >      > - // before we deallocate received data, see #1331
> >      > - mg_call(c, MG_EV_CLOSE, NULL);
> >      > - MG_DEBUG(("%lu %p closed", c->id, c->fd));
> >      > -
> >      > - mg_tls_free(c);
> >      > - mg_iobuf_free(&c->recv);
> >      > - mg_iobuf_free(&c->send);
> >      > - memset(c, 0, sizeof(*c));
> >      > - free(c);
> >      > +static void mg_tls_client_send_finish(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + struct mg_iobuf *wio = &tls->send;
> >      > + mg_sha256_ctx sha256;
> >      > + uint8_t hash[32];
> >      > + uint8_t finish[36] = {0x14, 0, 0, 32};
> >      > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
> >      > + mg_sha256_final(hash, &sha256);
> >      > + mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash,
> >     32);
> >      > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
> >      > + mg_io_send(c, wio->buf, wio->len);
> >      > + wio->len = 0;
> >      > +}
> >      > +
> >      > +static void mg_tls_client_handshake(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + switch (tls->state) {
> >      > + case MG_TLS_STATE_CLIENT_START:
> >      > + mg_tls_client_send_hello(c);
> >      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
> >      > + // Fallthrough
> >      > + case MG_TLS_STATE_CLIENT_WAIT_SH:
> >      > + if (mg_tls_client_recv_hello(c) < 0) {
> >      > + break;
> >      > + }
> >      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
> >      > + // Fallthrough
> >      > + case MG_TLS_STATE_CLIENT_WAIT_EE:
> >      > + if (mg_tls_client_recv_ext(c) < 0) {
> >      > + break;
> >      > + }
> >      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
> >      > + // Fallthrough
> >      > + case MG_TLS_STATE_CLIENT_WAIT_CERT:
> >      > + if (mg_tls_client_recv_cert(c) < 0) {
> >      > + break;
> >      > + }
> >      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
> >      > + // Fallthrough
> >      > + case MG_TLS_STATE_CLIENT_WAIT_CV:
> >      > + if (mg_tls_client_recv_cert_verify(c) < 0) {
> >      > + break;
> >      > + }
> >      > + tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
> >      > + // Fallthrough
> >      > + case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
> >      > + if (mg_tls_client_recv_finish(c) < 0) {
> >      > + break;
> >      > + }
> >      > + mg_tls_client_send_finish(c);
> >      > + mg_tls_generate_application_keys(c);
> >      > + tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
> >      > + c->is_tls_hs = 0;
> >      > + break;
> >      > + default: mg_error(c, "unexpected client state: %d",
> >     tls->state); break;
> >      > + }
> >      > }
> >      >
> >      > -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
> >     *url,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = NULL;
> >      > - if (url == NULL || url[0] == '\0') {
> >      > - MG_ERROR(("null url"));
> >      > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > - MG_ERROR(("OOM"));
> >      > - } else {
> >      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > - c->is_udp = (strncmp(url, "udp:", 4) == 0);
> >      > - c->fd = (void *) (size_t) MG_INVALID_SOCKET;
> >      > - c->fn = fn;
> >      > - c->is_client = true;
> >      > - c->fn_data = fn_data;
> >      > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> >      > - mg_call(c, MG_EV_OPEN, NULL);
> >      > - mg_resolve(c, url);
> >      > +static void mg_tls_server_handshake(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + switch (tls->state) {
> >      > + case MG_TLS_STATE_SERVER_START:
> >      > + if (mg_tls_server_recv_hello(c) < 0) {
> >      > + return;
> >      > + }
> >      > + mg_tls_server_send_hello(c);
> >      > + mg_tls_generate_handshake_keys(c);
> >      > + mg_tls_server_send_ext(c);
> >      > + mg_tls_server_send_cert(c);
> >      > + mg_tls_server_send_cert_verify(c);
> >      > + mg_tls_server_send_finish(c);
> >      > + tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
> >      > + // fallthrough
> >      > + case MG_TLS_STATE_SERVER_NEGOTIATED:
> >      > + if (mg_tls_server_recv_finish(c) < 0) {
> >      > + return;
> >      > + }
> >      > + mg_tls_generate_application_keys(c);
> >      > + tls->state = MG_TLS_STATE_SERVER_CONNECTED;
> >      > + c->is_tls_hs = 0;
> >      > + return;
> >      > + default: mg_error(c, "unexpected server state: %d",
> >     tls->state); break;
> >      > }
> >      > - return c;
> >      > }
> >      >
> >      > -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
> >     *url,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = NULL;
> >      > - if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > - MG_ERROR(("OOM %s", url));
> >      > - } else if (!mg_open_listener(c, url)) {
> >      > - MG_ERROR(("Failed: %s, errno %d", url, errno));
> >      > - free(c);
> >      > - c = NULL;
> >      > +void mg_tls_handshake(struct mg_connection *c) {
> >      > + if (c->is_client) {
> >      > + mg_tls_client_handshake(c);
> >      > } else {
> >      > - c->is_listening = 1;
> >      > - c->is_udp = strncmp(url, "udp:", 4) == 0;
> >      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > - c->fn = fn;
> >      > - c->fn_data = fn_data;
> >      > - mg_call(c, MG_EV_OPEN, NULL);
> >      > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
> >      > + mg_tls_server_handshake(c);
> >      > }
> >      > - return c;
> >      > }
> >      >
> >      > -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
> >      > - mg_event_handler_t fn, void *fn_data) {
> >      > - struct mg_connection *c = mg_alloc_conn(mgr);
> >      > - if (c != NULL) {
> >      > - c->fd = (void *) (size_t) fd;
> >      > - c->fn = fn;
> >      > - c->fn_data = fn_data;
> >      > - MG_EPOLL_ADD(c);
> >      > - mg_call(c, MG_EV_OPEN, NULL);
> >      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > +static int mg_parse_pem(const struct mg_str pem, const struct
> >     mg_str label,
> >      > + struct mg_str *der) {
> >      > + size_t n = 0, m = 0;
> >      > + char *s;
> >      > + const char *c;
> >      > + struct mg_str caps[5];
> >      > + if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END
> >     #-----#"), caps)) {
> >      > + der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
> >      > + der->len = pem.len;
> >      > + return 0;
> >      > + }
> >      > + if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label)
> >     != 0) {
> >      > + return -1; // bad label
> >      > + }
> >      > + if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
> >      > + return -1;
> >      > }
> >      > - return c;
> >      > -}
> >      >
> >      > -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
> >     milliseconds,
> >      > - unsigned flags, void (*fn)(void *), void *arg) {
> >      > - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
> >      > - if (t != NULL) {
> >      > - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
> >      > - t->id = mgr->timerid++;
> >      > + for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
> >      > + if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
> >      > + continue;
> >      > + }
> >      > + s[n++] = *c;
> >      > }
> >      > - return t;
> >      > + m = mg_base64_decode(s, n, s, n);
> >      > + if (m == 0) {
> >      > + free(s);
> >      > + return -1;
> >      > + }
> >      > + der->buf = s;
> >      > + der->len = m;
> >      > + return 0;
> >      > }
> >      >
> >      > -void mg_mgr_free(struct mg_mgr *mgr) {
> >      > - struct mg_connection *c;
> >      > - struct mg_timer *tmp, *t = mgr->timers;
> >      > - while (t != NULL) tmp = t->next, free(t), t = tmp;
> >      > - mgr->timers = NULL; // Important. Next call to poll won't touch
> >     timers
> >      > - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
> >      > - mg_mgr_poll(mgr, 0);
> >      > -#if MG_ENABLE_FREERTOS_TCP
> >      > - FreeRTOS_DeleteSocketSet(mgr->ss);
> >      > -#endif
> >      > - MG_DEBUG(("All connections closed"));
> >      > -#if MG_ENABLE_EPOLL
> >      > - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
> >      > -#endif
> >      > -}
> >      > +void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > + struct mg_str key;
> >      > + struct tls_data *tls = (struct tls_data *) calloc(1,
> >     sizeof(struct tls_data));
> >      > + if (tls == NULL) {
> >      > + mg_error(c, "tls oom");
> >      > + return;
> >      > + }
> >      >
> >      > -void mg_mgr_init(struct mg_mgr *mgr) {
> >      > - memset(mgr, 0, sizeof(*mgr));
> >      > -#if MG_ENABLE_EPOLL
> >      > - if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll:
> >     %d", errno));
> >      > -#else
> >      > - mgr->epoll_fd = -1;
> >      > -#endif
> >      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > - // clang-format off
> >      > - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
> >      > - // clang-format on
> >      > -#elif MG_ENABLE_FREERTOS_TCP
> >      > - mgr->ss = FreeRTOS_CreateSocketSet();
> >      > -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
> >      > - // Ignore SIGPIPE signal, so if client cancels the request, it
> >      > - // won't kill the whole process.
> >      > - signal(SIGPIPE, SIG_IGN);
> >      > -#endif
> >      > - mgr->dnstimeout = 3000;
> >      > - mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
> >      > - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
> >      > -}
> >      > + tls->state =
> >      > + c->is_client ? MG_TLS_STATE_CLIENT_START :
> >     MG_TLS_STATE_SERVER_START;
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/rpc.c"
> >      > -#endif
> >      > + tls->skip_verification = opts->skip_verification;
> >      > + tls->send.align = MG_IO_SIZE;
> >      >
> >      > + c->tls = tls;
> >      > + c->is_tls = c->is_tls_hs = 1;
> >      > + mg_sha256_init(&tls->sha256);
> >      >
> >      > -void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
> >      > - void (*fn)(struct mg_rpc_req *), void *fn_data) {
> >      > - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
> >      > - if (rpc != NULL) {
> >      > - rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data =
> >     fn_data;
> >      > - rpc->next = *head, *head = rpc;
> >      > + // save hostname (client extension)
> >      > + if (opts->name.len > 0) {
> >      > + if (opts->name.len >= sizeof(tls->hostname) - 1) {
> >      > + mg_error(c, "hostname too long");
> >      > + }
> >      > + strncpy((char *) tls->hostname, opts->name.buf,
> >     sizeof(tls->hostname) - 1);
> >      > + tls->hostname[opts->name.len] = 0;
> >      > }
> >      > -}
> >      >
> >      > -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
> >     mg_rpc_req *)) {
> >      > - struct mg_rpc *r;
> >      > - while ((r = *head) != NULL) {
> >      > - if (r->fn == fn || fn == NULL) {
> >      > - *head = r->next;
> >      > - free((void *) r->method.ptr);
> >      > - free(r);
> >      > - } else {
> >      > - head = &(*head)->next;
> >      > - }
> >      > + if (c->is_client) {
> >      > + tls->server_cert_der.buf = NULL;
> >      > + return;
> >      > }
> >      > -}
> >      >
> >      > -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
> >     method) {
> >      > - struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
> >      > - while (h != NULL && !mg_match(method, h->method, NULL)) h =
> >     h->next;
> >      > - if (h != NULL) {
> >      > - r->rpc = h;
> >      > - h->fn(r);
> >      > + // parse PEM or DER certificate
> >      > + if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"),
> >     &tls->server_cert_der) <
> >      > + 0) {
> >      > + MG_ERROR(("Failed to load certificate"));
> >      > + return;
> >      > + }
> >      > +
> >      > + // parse PEM or DER EC key
> >      > + if (opts->key.buf == NULL) {
> >      > + mg_error(c, "certificate provided without a private key");
> >      > + return;
> >      > + }
> >      > +
> >      > + if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key)
> >     == 0) {
> >      > + if (key.len < 39) {
> >      > + MG_ERROR(("EC private key too short"));
> >      > + return;
> >      > + }
> >      > + // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
> >      > + // 30 nn 02 01 01 04 20 [key] ...
> >      > + if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
> >      > + MG_ERROR(("EC private key: ASN.1 bad sequence"));
> >      > + return;
> >      > + }
> >      > + if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
> >      > + MG_ERROR(("EC private key: ASN.1 bad data"));
> >      > + }
> >      > + memmove(tls->server_key, key.buf + 7, 32);
> >      > + free((void *) key.buf);
> >      > + } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"),
> >     &key) == 0) {
> >      > + mg_error(c, "PKCS8 private key format is not supported");
> >      > } else {
> >      > - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
> >     method.ptr);
> >      > + mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
> >      > }
> >      > }
> >      >
> >      > -void mg_rpc_process(struct mg_rpc_req *r) {
> >      > - int len, off = mg_json_get(r->frame, "$.method", &len);
> >      > - if (off > 0 && r->frame.ptr[off] == '"') {
> >      > - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1],
> >     (size_t) len - 2);
> >      > - mg_rpc_call(r, method);
> >      > - } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
> >      > - (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
> >      > - mg_rpc_call(r, mg_str("")); // JSON response! call "" method
> >     handler
> >      > - } else {
> >      > - mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len,
> >     r->frame.ptr); // Invalid
> >      > +void mg_tls_free(struct mg_connection *c) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + if (tls != NULL) {
> >      > + mg_iobuf_free(&tls->send);
> >      > + free((void *) tls->server_cert_der.buf);
> >      > }
> >      > + free(c->tls);
> >      > + c->tls = NULL;
> >      > }
> >      >
> >      > -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
> >     *ap) {
> >      > - int len, off = mg_json_get(r->frame, "$.id", &len);
> >      > - if (off > 0) {
> >      > - mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
> >      > - &r->frame.ptr[off], "result");
> >      > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> >      > - mg_xprintf(r->pfn, r->pfn_data, "}");
> >      > +long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + long n = MG_IO_WAIT;
> >      > + if (len > MG_IO_SIZE) len = MG_IO_SIZE;
> >      > + mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
> >      > + while (tls->send.len > 0 &&
> >      > + (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
> >      > + mg_iobuf_del(&tls->send, 0, (size_t) n);
> >      > }
> >      > + if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
> >      > + return (long) len;
> >      > }
> >      >
> >      > -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - mg_rpc_vok(r, fmt, &ap);
> >      > - va_end(ap);
> >      > -}
> >      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > + int r = 0;
> >      > + struct tls_data *tls = (struct tls_data *) c->tls;
> >      > + size_t minlen;
> >      >
> >      > -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
> >     *fmt, va_list *ap) {
> >      > - int len, off = mg_json_get(r->frame, "$.id", &len);
> >      > - mg_xprintf(r->pfn, r->pfn_data, "{");
> >      > - if (off > 0) {
> >      > - mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len,
> >     &r->frame.ptr[off]);
> >      > + r = mg_tls_recv_record(c);
> >      > + if (r < 0) {
> >      > + return r;
> >      > }
> >      > - mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error",
> >     "code", code,
> >      > - "message");
> >      > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
> >      > - mg_xprintf(r->pfn, r->pfn_data, "}}");
> >      > + if (tls->content_type != MG_TLS_APP_DATA) {
> >      > + tls->recv.len = 0;
> >      > + mg_tls_drop_record(c);
> >      > + return MG_IO_WAIT;
> >      > + }
> >      > + minlen = len < tls->recv.len ? len : tls->recv.len;
> >      > + memmove(buf, tls->recv.buf, minlen);
> >      > + tls->recv.buf += minlen;
> >      > + tls->recv.len -= minlen;
> >      > + if (tls->recv.len == 0) {
> >      > + mg_tls_drop_record(c);
> >      > + }
> >      > + return (long) minlen;
> >      > }
> >      >
> >      > -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
> >     ...) {
> >      > - va_list ap;
> >      > - va_start(ap, fmt);
> >      > - mg_rpc_verr(r, code, fmt, &ap);
> >      > - va_end(ap);
> >      > +size_t mg_tls_pending(struct mg_connection *c) {
> >      > + return mg_tls_got_record(c) ? 1 : 0;
> >      > }
> >      >
> >      > -static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
> >     va_list *ap) {
> >      > - struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
> >     **);
> >      > - size_t len = 0;
> >      > - for (h = *head; h != NULL; h = h->next) {
> >      > - if (h->method.len == 0) continue; // Ignore response handler
> >      > - len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
> >      > - (int) h->method.len, h->method.ptr);
> >      > - }
> >      > - return len;
> >      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > }
> >      >
> >      > -void mg_rpc_list(struct mg_rpc_req *r) {
> >      > - mg_rpc_ok(r, "[%M]", print_methods, r->head);
> >      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > }
> >      > +#endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/sha1.c"
> >      > +#line 1 "src/tls_dummy.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_TLS == MG_TLS_NONE
> >      > +void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > + (void) opts;
> >      > + mg_error(c, "TLS is not enabled");
> >      > +}
> >      > +void mg_tls_handshake(struct mg_connection *c) {
> >      > + (void) c;
> >      > +}
> >      > +void mg_tls_free(struct mg_connection *c) {
> >      > + (void) c;
> >      > +}
> >      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> >      > +}
> >      > +long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> >      > +}
> >      > +size_t mg_tls_pending(struct mg_connection *c) {
> >      > + (void) c;
> >      > + return 0;
> >      > +}
> >      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > +}
> >      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > +}
> >      > #endif
> >      > -/* Copyright(c) By Steve Reid <st...@edmweb.com> */
> >      > -/* 100% Public Domain */
> >      >
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/tls_mbed.c"
> >      > +#endif
> >      >
> >      >
> >      > -union char64long16 {
> >      > - unsigned char c[64];
> >      > - uint32_t l[16];
> >      > -};
> >      >
> >      > -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
> >     - (bits))))
> >      > +#if MG_TLS == MG_TLS_MBED
> >      >
> >      > -static uint32_t blk0(union char64long16 *block, int i) {
> >      > - if (MG_BIG_ENDIAN) {
> >      > - } else {
> >      > - block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
> >      > - (rol(block->l[i], 8) & 0x00FF00FF);
> >      > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
> >     0x03000000
> >      > +#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
> >      > +#else
> >      > +#define MG_MBEDTLS_RNG_GET
> >      > +#endif
> >      > +
> >      > +static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> >      > + mg_random(buf, len);
> >      > + (void) ctx;
> >      > + return 0;
> >      > +}
> >      > +
> >      > +static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
> >      > + int rc;
> >      > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
> >     return true;
> >      > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
> >      > + if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf,
> >     str.len)) != 0) {
> >      > + MG_ERROR(("cert err %#x", -rc));
> >      > + return false;
> >      > }
> >      > - return block->l[i];
> >      > + return true;
> >      > }
> >      >
> >      > -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
> >     define R0~R4) */
> >      > -#undef blk
> >      > -#undef R0
> >      > -#undef R1
> >      > -#undef R2
> >      > -#undef R3
> >      > -#undef R4
> >      > +static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
> >      > + int rc;
> >      > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
> >     return true;
> >      > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
> >      > + if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len,
> >     NULL,
> >      > + 0 MG_MBEDTLS_RNG_GET)) != 0) {
> >      > + MG_ERROR(("key err %#x", -rc));
> >      > + return false;
> >      > + }
> >      > + return true;
> >      > +}
> >      >
> >      > -#define blk(i) \
> >      > - (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
> >     8) & 15] ^ \
> >      > - block->l[(i + 2) & 15] ^ block->l[i & 15], \
> >      > - 1))
> >      > -#define R0(v, w, x, y, z, i) \
> >      > - z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
> >     5); \
> >      > - w = rol(w, 30);
> >      > -#define R1(v, w, x, y, z, i) \
> >      > - z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
> >      > - w = rol(w, 30);
> >      > -#define R2(v, w, x, y, z, i) \
> >      > - z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
> >      > - w = rol(w, 30);
> >      > -#define R3(v, w, x, y, z, i) \
> >      > - z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
> >     5); \
> >      > - w = rol(w, 30);
> >      > -#define R4(v, w, x, y, z, i) \
> >      > - z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
> >      > - w = rol(w, 30);
> >      > +void mg_tls_free(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + if (tls != NULL) {
> >      > + mbedtls_ssl_free(&tls->ssl);
> >      > + mbedtls_pk_free(&tls->pk);
> >      > + mbedtls_x509_crt_free(&tls->ca);
> >      > + mbedtls_x509_crt_free(&tls->cert);
> >      > + mbedtls_ssl_config_free(&tls->conf);
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + mbedtls_ssl_ticket_free(&tls->ticket);
> >      > +#endif
> >      > + free(tls);
> >      > + c->tls = NULL;
> >      > + }
> >      > +}
> >      >
> >      > -static void mg_sha1_transform(uint32_t state[5],
> >      > - const unsigned char buffer[64]) {
> >      > - uint32_t a, b, c, d, e;
> >      > - union char64long16 block[1];
> >      > +static int mg_net_send(void *ctx, const unsigned char *buf,
> >     size_t len) {
> >      > + long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> >      > + MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *)
> >     ctx)->id, n, errno));
> >      > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> >      > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> >      > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> >      > + return (int) n;
> >      > +}
> >      >
> >      > - memcpy(block, buffer, 64);
> >      > - a = state[0];
> >      > - b = state[1];
> >      > - c = state[2];
> >      > - d = state[3];
> >      > - e = state[4];
> >      > - R0(a, b, c, d, e, 0);
> >      > - R0(e, a, b, c, d, 1);
> >      > - R0(d, e, a, b, c, 2);
> >      > - R0(c, d, e, a, b, 3);
> >      > - R0(b, c, d, e, a, 4);
> >      > - R0(a, b, c, d, e, 5);
> >      > - R0(e, a, b, c, d, 6);
> >      > - R0(d, e, a, b, c, 7);
> >      > - R0(c, d, e, a, b, 8);
> >      > - R0(b, c, d, e, a, 9);
> >      > - R0(a, b, c, d, e, 10);
> >      > - R0(e, a, b, c, d, 11);
> >      > - R0(d, e, a, b, c, 12);
> >      > - R0(c, d, e, a, b, 13);
> >      > - R0(b, c, d, e, a, 14);
> >      > - R0(a, b, c, d, e, 15);
> >      > - R1(e, a, b, c, d, 16);
> >      > - R1(d, e, a, b, c, 17);
> >      > - R1(c, d, e, a, b, 18);
> >      > - R1(b, c, d, e, a, 19);
> >      > - R2(a, b, c, d, e, 20);
> >      > - R2(e, a, b, c, d, 21);
> >      > - R2(d, e, a, b, c, 22);
> >      > - R2(c, d, e, a, b, 23);
> >      > - R2(b, c, d, e, a, 24);
> >      > - R2(a, b, c, d, e, 25);
> >      > - R2(e, a, b, c, d, 26);
> >      > - R2(d, e, a, b, c, 27);
> >      > - R2(c, d, e, a, b, 28);
> >      > - R2(b, c, d, e, a, 29);
> >      > - R2(a, b, c, d, e, 30);
> >      > - R2(e, a, b, c, d, 31);
> >      > - R2(d, e, a, b, c, 32);
> >      > - R2(c, d, e, a, b, 33);
> >      > - R2(b, c, d, e, a, 34);
> >      > - R2(a, b, c, d, e, 35);
> >      > - R2(e, a, b, c, d, 36);
> >      > - R2(d, e, a, b, c, 37);
> >      > - R2(c, d, e, a, b, 38);
> >      > - R2(b, c, d, e, a, 39);
> >      > - R3(a, b, c, d, e, 40);
> >      > - R3(e, a, b, c, d, 41);
> >      > - R3(d, e, a, b, c, 42);
> >      > - R3(c, d, e, a, b, 43);
> >      > - R3(b, c, d, e, a, 44);
> >      > - R3(a, b, c, d, e, 45);
> >      > - R3(e, a, b, c, d, 46);
> >      > - R3(d, e, a, b, c, 47);
> >      > - R3(c, d, e, a, b, 48);
> >      > - R3(b, c, d, e, a, 49);
> >      > - R3(a, b, c, d, e, 50);
> >      > - R3(e, a, b, c, d, 51);
> >      > - R3(d, e, a, b, c, 52);
> >      > - R3(c, d, e, a, b, 53);
> >      > - R3(b, c, d, e, a, 54);
> >      > - R3(a, b, c, d, e, 55);
> >      > - R3(e, a, b, c, d, 56);
> >      > - R3(d, e, a, b, c, 57);
> >      > - R3(c, d, e, a, b, 58);
> >      > - R3(b, c, d, e, a, 59);
> >      > - R4(a, b, c, d, e, 60);
> >      > - R4(e, a, b, c, d, 61);
> >      > - R4(d, e, a, b, c, 62);
> >      > - R4(c, d, e, a, b, 63);
> >      > - R4(b, c, d, e, a, 64);
> >      > - R4(a, b, c, d, e, 65);
> >      > - R4(e, a, b, c, d, 66);
> >      > - R4(d, e, a, b, c, 67);
> >      > - R4(c, d, e, a, b, 68);
> >      > - R4(b, c, d, e, a, 69);
> >      > - R4(a, b, c, d, e, 70);
> >      > - R4(e, a, b, c, d, 71);
> >      > - R4(d, e, a, b, c, 72);
> >      > - R4(c, d, e, a, b, 73);
> >      > - R4(b, c, d, e, a, 74);
> >      > - R4(a, b, c, d, e, 75);
> >      > - R4(e, a, b, c, d, 76);
> >      > - R4(d, e, a, b, c, 77);
> >      > - R4(c, d, e, a, b, 78);
> >      > - R4(b, c, d, e, a, 79);
> >      > - state[0] += a;
> >      > - state[1] += b;
> >      > - state[2] += c;
> >      > - state[3] += d;
> >      > - state[4] += e;
> >      > - /* Erase working structures. The order of operations is important,
> >      > - * used to ensure that compiler doesn't optimize those out. */
> >      > - memset(block, 0, sizeof(block));
> >      > - a = b = c = d = e = 0;
> >      > - (void) a;
> >      > - (void) b;
> >      > - (void) c;
> >      > - (void) d;
> >      > - (void) e;
> >      > +static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> >      > + long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> >      > + MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> >      > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> >      > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> >      > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> >      > + return (int) n;
> >      > +}
> >      > +
> >      > +void mg_tls_handshake(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + int rc = mbedtls_ssl_handshake(&tls->ssl);
> >      > + if (rc == 0) { // Success
> >      > + MG_DEBUG(("%lu success", c->id));
> >      > + c->is_tls_hs = 0;
> >      > + mg_call(c, MG_EV_TLS_HS, NULL);
> >      > + } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> >      > + rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
> >      > + MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
> >     c->is_connecting,
> >      > + c->is_tls_hs, rc, -rc));
> >      > + } else {
> >      > + mg_error(c, "TLS handshake: -%#x", -rc); // Error
> >      > + }
> >      > }
> >      >
> >      > -void mg_sha1_init(mg_sha1_ctx *context) {
> >      > - context->state[0] = 0x67452301;
> >      > - context->state[1] = 0xEFCDAB89;
> >      > - context->state[2] = 0x98BADCFE;
> >      > - context->state[3] = 0x10325476;
> >      > - context->state[4] = 0xC3D2E1F0;
> >      > - context->count[0] = context->count[1] = 0;
> >      > +static void debug_cb(void *c, int lev, const char *s, int n,
> >     const char *s2) {
> >      > + n = (int) strlen(s2) - 1;
> >      > + MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev,
> >     n, s2));
> >      > + (void) s;
> >      > }
> >      >
> >      > -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
> >     *data,
> >      > - size_t len) {
> >      > - size_t i, j;
> >      > +void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> >      > + int rc = 0;
> >      > + c->tls = tls;
> >      > + if (c->tls == NULL) {
> >      > + mg_error(c, "TLS OOM");
> >      > + goto fail;
> >      > + }
> >      > + if (c->is_listening) goto fail;
> >      > + MG_DEBUG(("%lu Setting TLS", c->id));
> >      > + MG_PROF_ADD(c, "mbedtls_init_start");
> >      > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
> >     0x03000000 && \
> >      > + defined(MBEDTLS_PSA_CRYPTO_C)
> >      > + psa_crypto_init(); //
> >     https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711 <https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711>
> >      > +#endif
> >      > + mbedtls_ssl_init(&tls->ssl);
> >      > + mbedtls_ssl_config_init(&tls->conf);
> >      > + mbedtls_x509_crt_init(&tls->ca);
> >      > + mbedtls_x509_crt_init(&tls->cert);
> >      > + mbedtls_pk_init(&tls->pk);
> >      > + mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> >      > +#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> >      > + mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> >      > +#endif
> >      > + if ((rc = mbedtls_ssl_config_defaults(
> >      > + &tls->conf,
> >      > + c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> >      > + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> >      > + mg_error(c, "tls defaults %#x", -rc);
> >      > + goto fail;
> >      > + }
> >      > + mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
> >      >
> >      > - j = context->count[0];
> >      > - if ((context->count[0] += (uint32_t) len << 3) < j)
> >     context->count[1]++;
> >      > - context->count[1] += (uint32_t) (len >> 29);
> >      > - j = (j >> 3) & 63;
> >      > - if ((j + len) > 63) {
> >      > - memcpy(&context->buffer[j], data, (i = 64 - j));
> >      > - mg_sha1_transform(context->state, context->buffer);
> >      > - for (; i + 63 < len; i += 64) {
> >      > - mg_sha1_transform(context->state, &data[i]);
> >      > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> >      > + // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on
> >     client side
> >      > + // See https://github.com/Mbed-TLS/mbedtls/issues/7075
> >     <https://github.com/Mbed-TLS/mbedtls/issues/7075>
> >      > + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> >      > + } else {
> >      > + if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
> >      > + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> >      > + if (c->is_client && opts->name.buf != NULL && opts->name.buf[0]
> >     != '\0') {
> >      > + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
> >      > + mbedtls_ssl_set_hostname(&tls->ssl, host);
> >      > + MG_DEBUG(("%lu hostname verification: %s", c->id, host));
> >      > + free(host);
> >      > }
> >      > - j = 0;
> >      > - } else
> >      > - i = 0;
> >      > - memcpy(&context->buffer[j], &data[i], len - i);
> >      > + mbedtls_ssl_conf_authmode(&tls->conf,
> >     MBEDTLS_SSL_VERIFY_REQUIRED);
> >      > + }
> >      > + if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
> >      > + if (!mg_load_key(opts->key, &tls->pk)) goto fail;
> >      > + if (tls->cert.version &&
> >      > + (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert,
> >     &tls->pk)) != 0) {
> >      > + mg_error(c, "own cert %#x", -rc);
> >      > + goto fail;
> >      > + }
> >      > +
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + mbedtls_ssl_conf_session_tickets_cb(
> >      > + &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
> >      > + &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
> >      > +#endif
> >      > +
> >      > + if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> >      > + mg_error(c, "setup err %#x", -rc);
> >      > + goto fail;
> >      > + }
> >      > + c->is_tls = 1;
> >      > + c->is_tls_hs = 1;
> >      > + mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> >      > + MG_PROF_ADD(c, "mbedtls_init_end");
> >      > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
> >     0) {
> >      > + mg_tls_handshake(c);
> >      > + }
> >      > + return;
> >      > +fail:
> >      > + mg_tls_free(c);
> >      > }
> >      >
> >      > -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
> >     *context) {
> >      > - unsigned i;
> >      > - unsigned char finalcount[8], c;
> >      > +size_t mg_tls_pending(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> >      > +}
> >      >
> >      > - for (i = 0; i < 8; i++) {
> >      > - finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
> >     1)] >>
> >      > - ((3 - (i & 3)) * 8)) &
> >      > - 255);
> >      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> >      > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
> >     MBEDTLS_ERR_SSL_WANT_WRITE)
> >      > + return MG_IO_WAIT;
> >      > +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
> >      > + if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
> >      > + return MG_IO_WAIT;
> >      > }
> >      > - c = 0200;
> >      > - mg_sha1_update(context, &c, 1);
> >      > - while ((context->count[0] & 504) != 448) {
> >      > - c = 0000;
> >      > - mg_sha1_update(context, &c, 1);
> >      > +#endif
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > +}
> >      > +
> >      > +long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> >      > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
> >     MBEDTLS_ERR_SSL_WANT_WRITE)
> >      > + return MG_IO_WAIT;
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > +}
> >      > +
> >      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> >      > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1,
> >     sizeof(*ctx));
> >      > + if (ctx == NULL) {
> >      > + MG_ERROR(("TLS context init OOM"));
> >      > + } else {
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + int rc;
> >      > + mbedtls_ssl_ticket_init(&ctx->tickets);
> >      > + if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng,
> >     NULL,
> >      > + MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
> >      > + 0) {
> >      > + MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
> >      > + }
> >      > +#endif
> >      > + mgr->tls_ctx = ctx;
> >      > }
> >      > - mg_sha1_update(context, finalcount, 8);
> >      > - for (i = 0; i < 20; i++) {
> >      > - digest[i] =
> >      > - (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
> >     8)) & 255);
> >      > +}
> >      > +
> >      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> >      > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
> >      > + if (ctx != NULL) {
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + mbedtls_ssl_ticket_free(&ctx->tickets);
> >      > +#endif
> >      > + free(ctx);
> >      > + mgr->tls_ctx = NULL;
> >      > }
> >      > - memset(context, '\0', sizeof(*context));
> >      > - memset(&finalcount, '\0', sizeof(finalcount));
> >      > }
> >      > +#endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/sntp.c"
> >      > +#line 1 "src/tls_openssl.c"
> >      > #endif
> >      >
> >      >
> >      >
> >      > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
> >      >
> >      > +static int tls_err_cb(const char *s, size_t len, void *c) {
> >      > + int n = (int) len - 1;
> >      > + MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
> >      > + return 0; // undocumented
> >      > +}
> >      >
> >      > +static int mg_tls_err(struct mg_connection *c, struct mg_tls
> >     *tls, int res) {
> >      > + int err = SSL_get_error(tls->ssl, res);
> >      > + // We've just fetched the last error from the queue.
> >      > + // Now we need to clear the error queue. If we do not, then the
> >     following
> >      > + // can happen (actually reported):
> >      > + // - A new connection is accept()-ed with cert error (e.g.
> >     self-signed cert)
> >      > + // - Since all accept()-ed connections share listener's context,
> >      > + // - *ALL* SSL accepted connection report read error on the
> >     next poll cycle.
> >      > + // Thus a single errored connection can close all the rest,
> >     unrelated ones.
> >      > + // Clearing the error keeps the shared SSL_CTX in an OK state.
> >      >
> >      > -#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
> >      > -#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
> >      > + if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
> >      > + ERR_clear_error();
> >      > + if (err == SSL_ERROR_WANT_READ) return 0;
> >      > + if (err == SSL_ERROR_WANT_WRITE) return 0;
> >      > + return err;
> >      > +}
> >      >
> >      > -static int64_t gettimestamp(const uint32_t *data) {
> >      > - uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
> >      > - if (sec) sec -= SNTP_TIME_OFFSET;
> >      > - return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
> >     * 1000.0);
> >      > +static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
> >      > + BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
> >      > + STACK_OF(X509_INFO) *certs =
> >      > + bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
> >      > + if (bio) BIO_free(bio);
> >      > + return certs;
> >      > }
> >      >
> >      > -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
> >      > - int64_t res = -1;
> >      > - int mode = len > 0 ? buf[0] & 7 : 0;
> >      > - int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
> >      > - if (len < 48) {
> >      > - MG_ERROR(("%s", "corrupt packet"));
> >      > - } else if (mode != 4 && mode != 5) {
> >      > - MG_ERROR(("%s", "not a server reply"));
> >      > - } else if (buf[1] == 0) {
> >      > - MG_ERROR(("%s", "server sent a kiss of death"));
> >      > - } else if (version == 4 || version == 3) {
> >      > - // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
> >      > - int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
> >      > - int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
> >      > - int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
> >      > - int64_t t3 = (int64_t) mg_millis();
> >      > - int64_t delta = (t3 - t0) - (t2 - t1);
> >      > - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
> >     delta));
> >      > - res = t2 + delta / 2;
> >      > - } else {
> >      > - MG_ERROR(("unexpected version: %d", version));
> >      > +static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) *
> >     certs) {
> >      > + X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
> >      > + for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
> >      > + X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
> >      > + if (cert_info->x509 && !X509_STORE_add_cert(cert_store,
> >     cert_info->x509))
> >      > + return false;
> >      > + }
> >      > + return true;
> >      > +}
> >      > +
> >      > +static EVP_PKEY *load_key(struct mg_str s) {
> >      > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> >      > + EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0,
> >     NULL) : NULL;
> >      > + if (bio) BIO_free(bio);
> >      > + return key;
> >      > +}
> >      > +
> >      > +static X509 *load_cert(struct mg_str s) {
> >      > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
> >      > + X509 *cert = bio == NULL ? NULL
> >      > + : s.buf[0] == '-'
> >      > + ? PEM_read_bio_X509(bio, NULL, NULL, NULL) // PEM
> >      > + : d2i_X509_bio(bio, NULL); // DER
> >      > + if (bio) BIO_free(bio);
> >      > + return cert;
> >      > +}
> >      > +
> >      > +static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
> >      > + long ret = 0;
> >      > + if (cmd == BIO_CTRL_PUSH) ret = 1;
> >      > + if (cmd == BIO_CTRL_POP) ret = 1;
> >      > + if (cmd == BIO_CTRL_FLUSH) ret = 1;
> >      > +#if MG_TLS == MG_TLS_OPENSSL
> >      > + if (cmd == BIO_C_SET_NBIO) ret = 1;
> >      > +#endif
> >      > + // MG_DEBUG(("%d -> %ld", cmd, ret));
> >      > + (void) b, (void) cmd, (void) larg, (void) pargs;
> >      > + return ret;
> >      > +}
> >      > +
> >      > +static int mg_bio_read(BIO *bio, char *buf, int len) {
> >      > + struct mg_connection *c = (struct mg_connection *)
> >     BIO_get_data(bio);
> >      > + long res = mg_io_recv(c, buf, (size_t) len);
> >      > + // MG_DEBUG(("%p %d %ld", buf, len, res));
> >      > + len = res > 0 ? (int) res : -1;
> >      > + if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
> >      > + return len;
> >      > +}
> >      > +
> >      > +static int mg_bio_write(BIO *bio, const char *buf, int len) {
> >      > + struct mg_connection *c = (struct mg_connection *)
> >     BIO_get_data(bio);
> >      > + long res = mg_io_send(c, buf, (size_t) len);
> >      > + // MG_DEBUG(("%p %d %ld", buf, len, res));
> >      > + len = res > 0 ? (int) res : -1;
> >      > + if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
> >      > + return len;
> >      > +}
> >      > +
> >      > +void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> >      > + const char *id = "mongoose";
> >      > + static unsigned char s_initialised = 0;
> >      > + BIO *bio = NULL;
> >      > + int rc;
> >      > +
> >      > + if (tls == NULL) {
> >      > + mg_error(c, "TLS OOM");
> >      > + goto fail;
> >      > + }
> >      > +
> >      > + if (!s_initialised) {
> >      > + SSL_library_init();
> >      > + s_initialised++;
> >      > + }
> >      > + MG_DEBUG(("%lu Setting TLS", c->id));
> >      > + tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> >      > + : SSL_CTX_new(SSLv23_server_method());
> >      > + if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> >      > + mg_error(c, "SSL_new");
> >      > + goto fail;
> >      > + }
> >      > + SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> >      > + (unsigned) strlen(id));
> >      > + // Disable deprecated protocols
> >      > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> >      > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> >      > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> >      > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> >      > +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> >      > + SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> >      > +#endif
> >      > +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> >      > + SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> >      > +#endif
> >      > +
> >      > +#if MG_TLS == MG_TLS_WOLFSSL &&
> >     !defined(OPENSSL_COMPATIBLE_DEFAULTS)
> >      > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
> >      > + // Older versions require that either the CA is loaded or
> >     SSL_VERIFY_NONE
> >      > + // explicitly set
> >      > + SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
> >      > + }
> >      > +#endif
> >      > + if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
> >      > + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
> >     SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> >      > + NULL);
> >      > + STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
> >      > + rc = add_ca_certs(tls->ctx, certs);
> >      > + sk_X509_INFO_pop_free(certs, X509_INFO_free);
> >      > + if (!rc) {
> >      > + mg_error(c, "CA err");
> >      > + goto fail;
> >      > + }
> >      > + }
> >      > + if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
> >      > + X509 *cert = load_cert(opts->cert);
> >      > + rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
> >      > + X509_free(cert);
> >      > + if (cert == NULL || rc != 1) {
> >      > + mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
> >      > + goto fail;
> >      > + }
> >      > + }
> >      > + if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
> >      > + EVP_PKEY *key = load_key(opts->key);
> >      > + rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
> >      > + EVP_PKEY_free(key);
> >      > + if (key == NULL || rc != 1) {
> >      > + mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
> >      > + goto fail;
> >      > + }
> >      > + }
> >      > +
> >      > + SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> >      > +#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER >
> >     0x10002000L
> >      > + (void) SSL_set_ecdh_auto(tls->ssl, 1);
> >      > +#endif
> >      > +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> >      > + if (opts->name.len > 0) {
> >      > + char *s = mg_mprintf("%.*s", (int) opts->name.len,
> >     opts->name.buf);
> >      > +#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >=
> >     0x05005002
> >      > + SSL_set1_host(tls->ssl, s);
> >      > +#else
> >      > + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
> >      > +#endif
> >      > + SSL_set_tlsext_host_name(tls->ssl, s);
> >      > + free(s);
> >      > }
> >      > - return res;
> >      > -}
> >      > +#endif
> >      > +#if MG_TLS == MG_TLS_WOLFSSL
> >      > + tls->bm = BIO_meth_new(0, "bio_mg");
> >      > +#else
> >      > + tls->bm = BIO_meth_new(BIO_get_new_index() |
> >     BIO_TYPE_SOURCE_SINK, "bio_mg");
> >      > +#endif
> >      > + BIO_meth_set_write(tls->bm, mg_bio_write);
> >      > + BIO_meth_set_read(tls->bm, mg_bio_read);
> >      > + BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
> >      >
> >      > -static void sntp_cb(struct mg_connection *c, int ev, void *evd,
> >     void *fnd) {
> >      > - if (ev == MG_EV_READ) {
> >      > - int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
> >      > - if (milliseconds > 0) {
> >      > - MG_INFO(("%lu got time: %lld ms from epoch", c->id,
> >     milliseconds));
> >      > - mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
> >      > - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
> >      > - (unsigned) (milliseconds % 1000)));
> >      > - }
> >      > - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
> >      > - } else if (ev == MG_EV_CONNECT) {
> >      > - mg_sntp_request(c);
> >      > - } else if (ev == MG_EV_CLOSE) {
> >      > + bio = BIO_new(tls->bm);
> >      > + BIO_set_data(bio, c);
> >      > + SSL_set_bio(tls->ssl, bio, bio);
> >      > +
> >      > + c->tls = tls;
> >      > + c->is_tls = 1;
> >      > + c->is_tls_hs = 1;
> >      > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
> >     0) {
> >      > + mg_tls_handshake(c);
> >      > }
> >      > - (void) fnd;
> >      > - (void) evd;
> >      > + MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
> >     "client"));
> >      > + return;
> >      > +fail:
> >      > + free(tls);
> >      > }
> >      >
> >      > -void mg_sntp_request(struct mg_connection *c) {
> >      > - if (c->is_resolving) {
> >      > - MG_ERROR(("%lu wait until resolved", c->id));
> >      > +void mg_tls_handshake(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + int rc = c->is_client ? SSL_connect(tls->ssl) :
> >     SSL_accept(tls->ssl);
> >      > + if (rc == 1) {
> >      > + MG_DEBUG(("%lu success", c->id));
> >      > + c->is_tls_hs = 0;
> >      > + mg_call(c, MG_EV_TLS_HS, NULL);
> >      > } else {
> >      > - int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
> >      > - uint8_t buf[48] = {0};
> >      > - uint32_t *t = (uint32_t *) &buf[40];
> >      > - double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
> >      > - buf[0] = (0 << 6) | (4 << 3) | 3;
> >      > - t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
> >      > - t[1] = mg_htonl((uint32_t) frac);
> >      > - mg_send(c, buf, sizeof(buf));
> >      > + int code = mg_tls_err(c, tls, rc);
> >      > + if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
> >      > }
> >      > }
> >      >
> >      > -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
> >     char *url,
> >      > - mg_event_handler_t fn, void *fnd) {
> >      > - struct mg_connection *c = NULL;
> >      > - if (url == NULL) url = "udp://time.google.com:123
> >     <http://time.google.com:123>";
> >      > - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
> >      > - return c;
> >      > +void mg_tls_free(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + if (tls == NULL) return;
> >      > + SSL_free(tls->ssl);
> >      > + SSL_CTX_free(tls->ctx);
> >      > + BIO_meth_free(tls->bm);
> >      > + free(tls);
> >      > + c->tls = NULL;
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/sock.c"
> >      > -#endif
> >      > -
> >      > +size_t mg_tls_pending(struct mg_connection *c) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> >      > +}
> >      >
> >      > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + int n = SSL_read(tls->ssl, buf, (int) len);
> >      > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > +}
> >      >
> >      > +long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > + struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > + int n = SSL_write(tls->ssl, buf, (int) len);
> >      > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
> >      > + if (n <= 0) return MG_IO_ERR;
> >      > + return n;
> >      > +}
> >      >
> >      > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > +}
> >      >
> >      > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
> >      > + (void) mgr;
> >      > +}
> >      > +#endif
> >      >
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/tls_uecc.c"
> >      > +#endif
> >      > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
> >     2-clause license. */
> >      >
> >      >
> >      >
> >      >
> >      > -#if MG_ENABLE_SOCKET
> >      > +#if MG_TLS == MG_TLS_BUILTIN
> >      >
> >      > -#ifndef closesocket
> >      > -#define closesocket(x) close(x)
> >      > +#ifndef MG_UECC_RNG_MAX_TRIES
> >      > +#define MG_UECC_RNG_MAX_TRIES 64
> >      > #endif
> >      >
> >      > -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
> >      > -#define S2PTR(s_) ((void *) (size_t) (s_))
> >      > -
> >      > -#ifndef MSG_NONBLOCKING
> >      > -#define MSG_NONBLOCKING 0
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +#define MG_UECC_VLI_API
> >      > +#else
> >      > +#define MG_UECC_VLI_API static
> >      > #endif
> >      >
> >      > -#ifndef AF_INET6
> >      > -#define AF_INET6 10
> >      > +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM ==
> >     mg_uecc_arm) || \
> >      > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb) || \
> >      > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
> >      > +#define CONCATX(a, ...) a##__VA_ARGS__
> >      > +#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
> >      > +
> >      > +#define STRX(a) #a
> >      > +#define STR(a) STRX(a)
> >      > +
> >      > +#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
> >      > +#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
> >      > +#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
> >      > +#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
> >      > +#define EVAL4(...) __VA_ARGS__
> >      > +
> >      > +#define DEC_1 0
> >      > +#define DEC_2 1
> >      > +#define DEC_3 2
> >      > +#define DEC_4 3
> >      > +#define DEC_5 4
> >      > +#define DEC_6 5
> >      > +#define DEC_7 6
> >      > +#define DEC_8 7
> >      > +#define DEC_9 8
> >      > +#define DEC_10 9
> >      > +#define DEC_11 10
> >      > +#define DEC_12 11
> >      > +#define DEC_13 12
> >      > +#define DEC_14 13
> >      > +#define DEC_15 14
> >      > +#define DEC_16 15
> >      > +#define DEC_17 16
> >      > +#define DEC_18 17
> >      > +#define DEC_19 18
> >      > +#define DEC_20 19
> >      > +#define DEC_21 20
> >      > +#define DEC_22 21
> >      > +#define DEC_23 22
> >      > +#define DEC_24 23
> >      > +#define DEC_25 24
> >      > +#define DEC_26 25
> >      > +#define DEC_27 26
> >      > +#define DEC_28 27
> >      > +#define DEC_29 28
> >      > +#define DEC_30 29
> >      > +#define DEC_31 30
> >      > +#define DEC_32 31
> >      > +
> >      > +#define DEC(N) CONCAT(DEC_, N)
> >      > +
> >      > +#define SECOND_ARG(_, val, ...) val
> >      > +#define SOME_CHECK_0 ~, 0
> >      > +#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
> >      > +#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
> >      > +
> >      > +#define EMPTY(...)
> >      > +#define DEFER(...) __VA_ARGS__ EMPTY()
> >      > +
> >      > +#define REPEAT_NAME_0() REPEAT_0
> >      > +#define REPEAT_NAME_SOME() REPEAT_SOME
> >      > +#define REPEAT_0(...)
> >      > +#define REPEAT_SOME(N, stuff) \
> >      > + DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff)
> >     stuff
> >      > +#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
> >      > +
> >      > +#define REPEATM_NAME_0() REPEATM_0
> >      > +#define REPEATM_NAME_SOME() REPEATM_SOME
> >      > +#define REPEATM_0(...)
> >      > +#define REPEATM_SOME(N, macro) \
> >      > + macro(N) DEFER(CONCAT(REPEATM_NAME_,
> >     SOME_OR_0(DEC(N))))()(DEC(N), macro)
> >      > +#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
> >      > #endif
> >      >
> >      > -union usa {
> >      > - struct sockaddr sa;
> >      > - struct sockaddr_in sin;
> >      > -#if MG_ENABLE_IPV6
> >      > - struct sockaddr_in6 sin6;
> >      > -#endif
> >      > -};
> >      > +//
> >      >
> >      > -static socklen_t tousa(struct mg_addr *a, union usa *usa) {
> >      > - socklen_t len = sizeof(usa->sin);
> >      > - memset(usa, 0, sizeof(*usa));
> >      > - usa->sin.sin_family = AF_INET;
> >      > - usa->sin.sin_port = a->port;
> >      > - *(uint32_t *) &usa->sin.sin_addr = a->ip;
> >      > -#if MG_ENABLE_IPV6
> >      > - if (a->is_ip6) {
> >      > - usa->sin.sin_family = AF_INET6;
> >      > - usa->sin6.sin6_port = a->port;
> >      > - memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
> >      > - len = sizeof(usa->sin6);
> >      > - }
> >      > +#if (MG_UECC_WORD_SIZE == 1)
> >      > +#if MG_UECC_SUPPORTS_secp160r1
> >      > +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
> >      > #endif
> >      > - return len;
> >      > -}
> >      > -
> >      > -static void tomgaddr(union usa *usa, struct mg_addr *a, bool
> >     is_ip6) {
> >      > - a->is_ip6 = is_ip6;
> >      > - a->port = usa->sin.sin_port;
> >      > - memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
> >      > -#if MG_ENABLE_IPV6
> >      > - if (is_ip6) {
> >      > - memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
> >      > - a->port = usa->sin6.sin6_port;
> >      > - }
> >      > +#if MG_UECC_SUPPORTS_secp192r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 24
> >      > #endif
> >      > -}
> >      > -
> >      > -static bool mg_sock_would_block(void) {
> >      > - int err = MG_SOCKET_ERRNO;
> >      > - return err == EINPROGRESS || err == EWOULDBLOCK
> >      > -#ifndef WINCE
> >      > - || err == EAGAIN || err == EINTR
> >      > +#if MG_UECC_SUPPORTS_secp224r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 28
> >      > #endif
> >      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > - || err == WSAEINTR || err == WSAEWOULDBLOCK
> >      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 32
> >      > #endif
> >      > - ;
> >      > -}
> >      > -
> >      > -static bool mg_sock_conn_reset(void) {
> >      > - int err = MG_SOCKET_ERRNO;
> >      > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > - return err == WSAECONNRESET;
> >      > -#else
> >      > - return err == EPIPE || err == ECONNRESET;
> >      > +#elif (MG_UECC_WORD_SIZE == 4)
> >      > +#if MG_UECC_SUPPORTS_secp160r1
> >      > +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
> >      > #endif
> >      > -}
> >      > +#if MG_UECC_SUPPORTS_secp192r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 6
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp224r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 7
> >      > +#endif
> >      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 8
> >      > +#endif
> >      > +#elif (MG_UECC_WORD_SIZE == 8)
> >      > +#if MG_UECC_SUPPORTS_secp160r1
> >      > +#define MG_UECC_MAX_WORDS 3
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp192r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 3
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp224r1
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 4
> >      > +#endif
> >      > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
> >      > +#undef MG_UECC_MAX_WORDS
> >      > +#define MG_UECC_MAX_WORDS 4
> >      > +#endif
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +
> >      > +#define BITS_TO_WORDS(num_bits) \
> >      > + ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
> >      > + (MG_UECC_WORD_SIZE * 8)))
> >      > +#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
> >      > +
> >      > +struct MG_UECC_Curve_t {
> >      > + wordcount_t num_words;
> >      > + wordcount_t num_bytes;
> >      > + bitcount_t num_n_bits;
> >      > + mg_uecc_word_t p[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t n[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
> >      > + mg_uecc_word_t b[MG_UECC_MAX_WORDS];
> >      > + void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
> >      > +#endif
> >      > + void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
> >      > + MG_UECC_Curve curve);
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t
> >     *product);
> >      > +#endif
> >      > +};
> >      >
> >      > -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
> >      > - union usa usa;
> >      > - socklen_t n = sizeof(usa);
> >      > - if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
> >      > - tomgaddr(&usa, addr, n != sizeof(usa.sin));
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > +static void bcopy(uint8_t *dst, const uint8_t *src, unsigned
> >     num_bytes) {
> >      > + while (0 != num_bytes) {
> >      > + num_bytes--;
> >      > + dst[num_bytes] = src[num_bytes];
> >      > }
> >      > }
> >      > +#endif
> >      >
> >      > -static void iolog(struct mg_connection *c, char *buf, long n,
> >     bool r) {
> >      > - if (n == MG_IO_WAIT) {
> >      > - // Do nothing
> >      > - } else if (n <= 0) {
> >      > - c->is_closing = 1; // Termination. Don't call mg_error(): #1529
> >      > - } else if (n > 0) {
> >      > - if (c->is_hexdumping) {
> >      > - union usa usa;
> >      > - socklen_t slen = sizeof(usa.sin);
> >      > - if (getsockname(FD(c), &usa.sa <http://usa.sa>, &slen) < 0)
> >     (void) 0; // Ignore result
> >      > - MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
> >      > - r ? "<-" : "->", 4, &c->rem.ip, n));
> >      > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
> >     *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words);
> >      >
> >      > - mg_hexdump(buf, (size_t) n);
> >      > - }
> >      > - if (r) {
> >      > - c->recv.len += (size_t) n;
> >      > - mg_call(c, MG_EV_READ, &n);
> >      > - } else {
> >      > - mg_iobuf_del(&c->send, 0, (size_t) n);
> >      > - // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
> >      > - if (c->send.len == 0) {
> >      > - MG_EPOLL_MOD(c, 0);
> >      > - }
> >      > - mg_call(c, MG_EV_WRITE, &n);
> >      > - }
> >      > - }
> >      > -}
> >      > +#if (MG_UECC_PLATFORM == mg_uecc_arm || \
> >      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
> >      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
> >      >
> >      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > - long n;
> >      > - if (c->is_udp) {
> >      > - union usa usa;
> >      > - socklen_t slen = tousa(&c->rem, &usa);
> >      > - n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
> >     <http://usa.sa>, slen);
> >      > - if (n > 0) setlocaddr(FD(c), &c->loc);
> >      > - } else {
> >      > - n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> >      > -#if MG_ARCH == MG_ARCH_RTX
> >      > - if (n == EWOULDBLOCK) return MG_IO_WAIT;
> >      > #endif
> >      > - }
> >      > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> >      > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > -}
> >      >
> >      > -bool mg_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > - if (c->is_udp) {
> >      > - long n = mg_io_send(c, buf, len);
> >      > - MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int)
> >     c->send.len,
> >      > - (int) c->recv.len, n, MG_SOCKET_ERRNO));
> >      > - iolog(c, (char *) buf, n, false);
> >      > - return n > 0;
> >      > - } else {
> >      > - return mg_iobuf_add(&c->send, c->send.len, buf, len);
> >      > - }
> >      > -}
> >      > +#if (MG_UECC_PLATFORM == mg_uecc_avr)
> >      >
> >      > -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
> >      > -#if defined(MG_CUSTOM_NONBLOCK)
> >      > - MG_CUSTOM_NONBLOCK(fd);
> >      > -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
> >      > - unsigned long on = 1;
> >      > - ioctlsocket(fd, FIONBIO, &on);
> >      > -#elif MG_ARCH == MG_ARCH_RTX
> >      > - unsigned long on = 1;
> >      > - ioctlsocket(fd, FIONBIO, &on);
> >      > -#elif MG_ENABLE_FREERTOS_TCP
> >      > - const BaseType_t off = 0;
> >      > - if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
> >     != 0) (void) 0;
> >      > - if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
> >     != 0) (void) 0;
> >      > -#elif MG_ENABLE_LWIP
> >      > - lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
> >      > -#elif MG_ARCH == MG_ARCH_AZURERTOS
> >      > - fcntl(fd, F_SETFL, O_NONBLOCK);
> >      > -#elif MG_ARCH == MG_ARCH_TIRTOS
> >      > - int val = 0;
> >      > - setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
> >      > - // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
> >      > - int sz = sizeof(val);
> >      > - getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
> >      > - val /= 2; // set send low-water mark at half send buffer size
> >      > - setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
> >      > -#else
> >      > - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
> >     Non-blocking mode
> >      > - fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
> >      > #endif
> >      > -}
> >      >
> >      > -bool mg_open_listener(struct mg_connection *c, const char *url) {
> >      > - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
> >      > - bool success = false;
> >      > - c->loc.port = mg_htons(mg_url_port(url));
> >      > - if (!mg_aton(mg_url_host(url), &c->loc)) {
> >      > - MG_ERROR(("invalid listening URL: %s", url));
> >      > - } else {
> >      > - union usa usa;
> >      > - socklen_t slen = tousa(&c->loc, &usa);
> >      > - int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
> >      > - int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
> >     SOCK_STREAM;
> >      > - int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
> >      > - (void) on;
> >      > +#ifndef asm_clear
> >      > +#define asm_clear 0
> >      > +#endif
> >      > +#ifndef asm_set
> >      > +#define asm_set 0
> >      > +#endif
> >      > +#ifndef asm_add
> >      > +#define asm_add 0
> >      > +#endif
> >      > +#ifndef asm_sub
> >      > +#define asm_sub 0
> >      > +#endif
> >      > +#ifndef asm_mult
> >      > +#define asm_mult 0
> >      > +#endif
> >      > +#ifndef asm_rshift1
> >      > +#define asm_rshift1 0
> >      > +#endif
> >      > +#ifndef asm_mmod_fast_secp256r1
> >      > +#define asm_mmod_fast_secp256r1 0
> >      > +#endif
> >      >
> >      > - if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
> >      > - MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
> >      > -#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
> >      > - (defined(LWIP_SOCKET) && SO_REUSE == 1))
> >      > - } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
> >      > - sizeof(on)) != 0) {
> >      > - // 1. SO_RESUSEADDR is not enabled on Windows because the
> >     semantics of
> >      > - // SO_REUSEADDR on UNIX and Windows is different. On Windows,
> >      > - // SO_REUSEADDR allows to bind a socket to a port without error
> >     even
> >      > - // if the port is already open by another program. This is not the
> >      > - // behavior SO_REUSEADDR was designed for, and leads to
> >     hard-to-track
> >      > - // failure scenarios. Therefore, SO_REUSEADDR was disabled on
> >     Windows
> >      > - // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
> >      > - // 2. In case of LWIP, SO_REUSEADDR should be explicitly
> >     enabled, by
> >      > - // defining
> >      > - // SO_REUSE (in lwipopts.h), otherwise the code below will compile
> >      > - // but won't work! (setsockopt will return EINVAL)
> >      > - MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
> >      > -#endif
> >      > -#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) &&
> >     !defined(WINCE)
> >      > - } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
> >     (char *) &on,
> >      > - sizeof(on)) != 0) {
> >      > - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
> >      > - MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
> >      > +#if defined(default_RNG_defined) && default_RNG_defined
> >      > +static MG_UECC_RNG_Function g_rng_function = &default_RNG;
> >      > +#else
> >      > +static MG_UECC_RNG_Function g_rng_function = 0;
> >      > #endif
> >      > - } else if (bind(fd, &usa.sa <http://usa.sa>, slen) != 0) {
> >      > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> >      > - } else if ((type == SOCK_STREAM &&
> >      > - listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
> >      > - // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
> >      > - // In case port was set to 0, get the real port number
> >      > - MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
> >      > - } else {
> >      > - setlocaddr(fd, &c->loc);
> >      > - mg_set_non_blocking_mode(fd);
> >      > - c->fd = S2PTR(fd);
> >      > - MG_EPOLL_ADD(c);
> >      > - success = true;
> >      > - }
> >      > - }
> >      > - if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
> >      > - return success;
> >      > +
> >      > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
> >      > + g_rng_function = rng_function;
> >      > +}
> >      > +
> >      > +MG_UECC_RNG_Function mg_uecc_get_rng(void) {
> >      > + return g_rng_function;
> >      > }
> >      >
> >      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > - long n = 0;
> >      > - if (c->is_udp) {
> >      > - union usa usa;
> >      > - socklen_t slen = tousa(&c->rem, &usa);
> >      > - n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
> >     <http://usa.sa>, &slen);
> >      > - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
> >      > - } else {
> >      > - n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
> >      > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
> >      > + return BITS_TO_BYTES(curve->num_n_bits);
> >      > +}
> >      > +
> >      > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
> >      > + return 2 * curve->num_bytes;
> >      > +}
> >      > +
> >      > +#if !asm_clear
> >      > +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
> >      > + wordcount_t num_words) {
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words; ++i) {
> >      > + vli[i] = 0;
> >      > }
> >      > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
> >      > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > }
> >      > +#endif /* !asm_clear */
> >      >
> >      > -// NOTE(lsm): do only one iteration of reads, cause some systems
> >      > -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
> >     no data
> >      > -static void read_conn(struct mg_connection *c) {
> >      > - long n = -1;
> >      > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
> >      > - mg_error(c, "max_recv_buf_size reached");
> >      > - } else if (c->recv.size <= c->recv.len &&
> >      > - !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
> >      > - mg_error(c, "oom");
> >      > - } else {
> >      > - char *buf = (char *) &c->recv.buf[c->recv.len];
> >      > - size_t len = c->recv.size - c->recv.len;
> >      > - n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf,
> >     len);
> >      > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
> >     c->fd,
> >      > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> >      > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
> >      > - iolog(c, buf, n, true);
> >      > +/* Constant-time comparison to zero - secure way to compare long
> >     integers */
> >      > +/* Returns 1 if vli == 0, 0 otherwise. */
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const
> >     mg_uecc_word_t *vli,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t bits = 0;
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words; ++i) {
> >      > + bits |= vli[i];
> >      > }
> >      > + return (bits == 0);
> >      > }
> >      >
> >      > -static void write_conn(struct mg_connection *c) {
> >      > - char *buf = (char *) c->send.buf;
> >      > - size_t len = c->send.len;
> >      > - long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
> >     buf, len);
> >      > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
> >     c->fd,
> >      > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
> >      > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
> >      > - iolog(c, buf, n, false);
> >      > +/* Returns nonzero if bit 'bit' of vli is set. */
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const
> >     mg_uecc_word_t *vli,
> >      > + bitcount_t bit) {
> >      > + return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
> >      > + ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
> >      > }
> >      >
> >      > -static void close_conn(struct mg_connection *c) {
> >      > - if (FD(c) != MG_INVALID_SOCKET) {
> >      > -#if MG_ENABLE_EPOLL
> >      > - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
> >      > -#endif
> >      > - closesocket(FD(c));
> >      > -#if MG_ENABLE_FREERTOS_TCP
> >      > - FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
> >      > -#endif
> >      > +/* Counts the number of words in vli. */
> >      > +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
> >      > + const wordcount_t max_words) {
> >      > + wordcount_t i;
> >      > + /* Search from the end until we find a non-zero digit.
> >      > + We do it in reverse because we expect that most digits will be
> >     nonzero. */
> >      > + for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
> >      > }
> >      > - mg_close_conn(c);
> >      > +
> >      > + return (i + 1);
> >      > }
> >      >
> >      > -static void connect_conn(struct mg_connection *c) {
> >      > - union usa usa;
> >      > - socklen_t n = sizeof(usa);
> >      > - // Use getpeername() to test whether we have connected
> >      > - if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
> >      > - c->is_connecting = 0;
> >      > - mg_call(c, MG_EV_CONNECT, NULL);
> >      > - MG_EPOLL_MOD(c, 0);
> >      > - if (c->is_tls_hs) mg_tls_handshake(c);
> >      > - } else {
> >      > - mg_error(c, "socket error");
> >      > +/* Counts the number of bits required to represent vli. */
> >      > +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const
> >     mg_uecc_word_t *vli,
> >      > + const wordcount_t max_words) {
> >      > + mg_uecc_word_t i;
> >      > + mg_uecc_word_t digit;
> >      > +
> >      > + wordcount_t num_digits = vli_numDigits(vli, max_words);
> >      > + if (num_digits == 0) {
> >      > + return 0;
> >      > }
> >      > -}
> >      >
> >      > -static void setsockopts(struct mg_connection *c) {
> >      > -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
> >      > - MG_ARCH == MG_ARCH_TIRTOS
> >      > - (void) c;
> >      > -#else
> >      > - int on = 1;
> >      > -#if !defined(SOL_TCP)
> >      > -#define SOL_TCP IPPROTO_TCP
> >      > -#endif
> >      > - if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
> >     sizeof(on)) != 0)
> >      > - (void) 0;
> >      > - if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
> >     sizeof(on)) !=
> >      > - 0)
> >      > - (void) 0;
> >      > -#endif
> >      > + digit = vli[num_digits - 1];
> >      > + for (i = 0; digit; ++i) {
> >      > + digit >>= 1;
> >      > + }
> >      > +
> >      > + return (((bitcount_t) ((num_digits - 1) <<
> >     MG_UECC_WORD_BITS_SHIFT)) +
> >      > + (bitcount_t) i);
> >      > }
> >      >
> >      > -void mg_connect_resolved(struct mg_connection *c) {
> >      > - int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
> >      > - int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
> >     resolved IP
> >      > - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
> >      > - c->is_resolving = 0; // Clear resolving flag
> >      > - if (FD(c) == MG_INVALID_SOCKET) {
> >      > - mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
> >      > - } else if (c->is_udp) {
> >      > - MG_EPOLL_ADD(c);
> >      > -#if MG_ARCH == MG_ARCH_TIRTOS
> >      > - union usa usa; // TI-RTOS NDK requires binding to receive on
> >     UDP sockets
> >      > - socklen_t slen = tousa(&c->loc, &usa);
> >      > - if (bind(c->fd, &usa.sa <http://usa.sa>, slen) != 0)
> >      > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
> >      > -#endif
> >      > - mg_call(c, MG_EV_RESOLVE, NULL);
> >      > - mg_call(c, MG_EV_CONNECT, NULL);
> >      > - } else {
> >      > - union usa usa;
> >      > - socklen_t slen = tousa(&c->rem, &usa);
> >      > - mg_set_non_blocking_mode(FD(c));
> >      > - setsockopts(c);
> >      > - MG_EPOLL_ADD(c);
> >      > - mg_call(c, MG_EV_RESOLVE, NULL);
> >      > - if ((rc = connect(FD(c), &usa.sa <http://usa.sa>, slen)) == 0) {
> >      > - mg_call(c, MG_EV_CONNECT, NULL);
> >      > - } else if (mg_sock_would_block()) {
> >      > - MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
> >      > - mg_ntohs(c->rem.port)));
> >      > - c->is_connecting = 1;
> >      > - } else {
> >      > - mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
> >      > +/* Sets dest = src. */
> >      > +#if !asm_set
> >      > +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
> >      > + const mg_uecc_word_t *src,
> >      > + wordcount_t num_words) {
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words; ++i) {
> >      > + dest[i] = src[i];
> >      > + }
> >      > +}
> >      > +#endif /* !asm_set */
> >      > +
> >      > +/* Returns sign of left - right. */
> >      > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
> >     *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + wordcount_t i;
> >      > + for (i = num_words - 1; i >= 0; --i) {
> >      > + if (left[i] > right[i]) {
> >      > + return 1;
> >      > + } else if (left[i] < right[i]) {
> >      > + return -1;
> >      > }
> >      > }
> >      > - (void) rc;
> >      > + return 0;
> >      > }
> >      >
> >      > -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
> >      > - socklen_t *len) {
> >      > - MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
> >      > - do {
> >      > - memset(usa, 0, sizeof(*usa));
> >      > - s = accept(sock, &usa->sa, len);
> >      > - } while (s == MG_INVALID_SOCKET && errno == EINTR);
> >      > - return s;
> >      > +/* Constant-time comparison function - secure way to compare
> >     long integers */
> >      > +/* Returns one if left == right, zero otherwise. */
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t diff = 0;
> >      > + wordcount_t i;
> >      > + for (i = num_words - 1; i >= 0; --i) {
> >      > + diff |= (left[i] ^ right[i]);
> >      > + }
> >      > + return (diff == 0);
> >      > +}
> >      > +
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Returns sign of left - right, in constant time. */
> >      > +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t
> >     *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right,
> >     num_words);
> >      > + mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
> >      > + return (cmpresult_t) (!equal - 2 * neg);
> >      > +}
> >      > +
> >      > +/* Computes vli = vli >> 1. */
> >      > +#if !asm_rshift1
> >      > +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t *end = vli;
> >      > + mg_uecc_word_t carry = 0;
> >      > +
> >      > + vli += num_words;
> >      > + while (vli-- > end) {
> >      > + mg_uecc_word_t temp = *vli;
> >      > + *vli = (temp >> 1) | carry;
> >      > + carry = temp << (MG_UECC_WORD_BITS - 1);
> >      > + }
> >      > }
> >      > +#endif /* !asm_rshift1 */
> >      > +
> >      > +/* Computes result = left + right, returning carry. Can modify
> >     in place. */
> >      > +#if !asm_add
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t carry = 0;
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words; ++i) {
> >      > + mg_uecc_word_t sum = left[i] + right[i] + carry;
> >      > + if (sum != left[i]) {
> >      > + carry = (sum < left[i]);
> >      > + }
> >      > + result[i] = sum;
> >      > + }
> >      > + return carry;
> >      > +}
> >      > +#endif /* !asm_add */
> >      > +
> >      > +/* Computes result = left - right, returning borrow. Can modify
> >     in place. */
> >      > +#if !asm_sub
> >      > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t borrow = 0;
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words; ++i) {
> >      > + mg_uecc_word_t diff = left[i] - right[i] - borrow;
> >      > + if (diff != left[i]) {
> >      > + borrow = (diff > left[i]);
> >      > + }
> >      > + result[i] = diff;
> >      > + }
> >      > + return borrow;
> >      > +}
> >      > +#endif /* !asm_sub */
> >      > +
> >      > +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) || \
> >      > + (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >     && \
> >      > + ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
> >      > +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b,
> >     mg_uecc_word_t *r0,
> >      > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> >      > +#if MG_UECC_WORD_SIZE == 8
> >      > + uint64_t a0 = a & 0xffffffff;
> >      > + uint64_t a1 = a >> 32;
> >      > + uint64_t b0 = b & 0xffffffff;
> >      > + uint64_t b1 = b >> 32;
> >      > +
> >      > + uint64_t i0 = a0 * b0;
> >      > + uint64_t i1 = a0 * b1;
> >      > + uint64_t i2 = a1 * b0;
> >      > + uint64_t i3 = a1 * b1;
> >      > +
> >      > + uint64_t p0, p1;
> >      > +
> >      > + i2 += (i0 >> 32);
> >      > + i2 += i1;
> >      > + if (i2 < i1) { /* overflow */
> >      > + i3 += 0x100000000;
> >      > + }
> >      >
> >      > -static void accept_conn(struct mg_mgr *mgr, struct mg_connection
> >     *lsn) {
> >      > - struct mg_connection *c = NULL;
> >      > - union usa usa;
> >      > - socklen_t sa_len = sizeof(usa);
> >      > - MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
> >      > - if (fd == MG_INVALID_SOCKET) {
> >      > -#if MG_ARCH == MG_ARCH_AZURERTOS
> >      > - // AzureRTOS, in non-block socket mode can mark listening
> >     socket readable
> >      > - // even it is not. See comment for 'select' func implementation in
> >      > - // nx_bsd.c That's not an error, just should try later
> >      > - if (MG_SOCKET_ERRNO != EAGAIN)
> >      > -#endif
> >      > - MG_ERROR(("%lu accept failed, errno %d", lsn->id,
> >     MG_SOCKET_ERRNO));
> >      > -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
> >      > - (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
> >      > - } else if ((long) fd >= FD_SETSIZE) {
> >      > - MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
> >      > - closesocket(fd);
> >      > + p0 = (i0 & 0xffffffff) | (i2 << 32);
> >      > + p1 = i3 + (i2 >> 32);
> >      > +
> >      > + *r0 += p0;
> >      > + *r1 += (p1 + (*r0 < p0));
> >      > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> >      > +#else
> >      > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> >      > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
> >     MG_UECC_WORD_BITS) | *r0;
> >      > + r01 += p;
> >      > + *r2 += (r01 < p);
> >      > + *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
> >      > + *r0 = (mg_uecc_word_t) r01;
> >      > #endif
> >      > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
> >      > - MG_ERROR(("%lu OOM", lsn->id));
> >      > - closesocket(fd);
> >      > - } else {
> >      > - tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
> >      > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
> >      > - c->fd = S2PTR(fd);
> >      > - MG_EPOLL_ADD(c);
> >      > - mg_set_non_blocking_mode(FD(c));
> >      > - setsockopts(c);
> >      > - c->is_accepted = 1;
> >      > - c->is_hexdumping = lsn->is_hexdumping;
> >      > - c->loc = lsn->loc;
> >      > - c->pfn = lsn->pfn;
> >      > - c->pfn_data = lsn->pfn_data;
> >      > - c->fn = lsn->fn;
> >      > - c->fn_data = lsn->fn_data;
> >      > - MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4,
> >     &c->rem.ip,
> >      > - mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
> >      > - mg_call(c, MG_EV_OPEN, NULL);
> >      > - mg_call(c, MG_EV_ACCEPT, NULL);
> >      > +}
> >      > +#endif /* muladd needed */
> >      > +
> >      > +#if !asm_mult
> >      > +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t r0 = 0;
> >      > + mg_uecc_word_t r1 = 0;
> >      > + mg_uecc_word_t r2 = 0;
> >      > + wordcount_t i, k;
> >      > +
> >      > + /* Compute each digit of result in sequence, maintaining the
> >     carries. */
> >      > + for (k = 0; k < num_words; ++k) {
> >      > + for (i = 0; i <= k; ++i) {
> >      > + muladd(left[i], right[k - i], &r0, &r1, &r2);
> >      > + }
> >      > + result[k] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + r2 = 0;
> >      > }
> >      > + for (k = num_words; k < num_words * 2 - 1; ++k) {
> >      > + for (i = (wordcount_t) ((k + 1) - num_words); i < num_words;
> >     ++i) {
> >      > + muladd(left[i], right[k - i], &r0, &r1, &r2);
> >      > + }
> >      > + result[k] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + r2 = 0;
> >      > + }
> >      > + result[num_words * 2 - 1] = r0;
> >      > }
> >      > +#endif /* !asm_mult */
> >      >
> >      > -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa
> >     usa[2], bool udp) {
> >      > - MG_SOCKET_TYPE sock;
> >      > - socklen_t n = sizeof(usa[0].sin);
> >      > - bool success = false;
> >      > +#if MG_UECC_SQUARE_FUNC
> >      >
> >      > - sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > - (void) memset(&usa[0], 0, sizeof(usa[0]));
> >      > - usa[0].sin.sin_family = AF_INET;
> >      > - *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
> >     127.0.0.1
> >      > - usa[1] = usa[0];
> >      > +#if !asm_square
> >      > +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b,
> >     mg_uecc_word_t *r0,
> >      > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
> >      > +#if MG_UECC_WORD_SIZE == 8
> >      > + uint64_t a0 = a & 0xffffffffull;
> >      > + uint64_t a1 = a >> 32;
> >      > + uint64_t b0 = b & 0xffffffffull;
> >      > + uint64_t b1 = b >> 32;
> >      >
> >      > - if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
> >     MG_INVALID_SOCKET &&
> >      > - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
> >      > - bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n)
> >     == 0 &&
> >      > - getsockname(sp[0], &usa[0].sa, &n) == 0 &&
> >      > - getsockname(sp[1], &usa[1].sa, &n) == 0 &&
> >      > - connect(sp[0], &usa[1].sa, n) == 0 &&
> >      > - connect(sp[1], &usa[0].sa, n) == 0) {
> >      > - success = true;
> >      > - } else if (!udp &&
> >      > - (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> >      > - bind(sock, &usa[0].sa, n) == 0 &&
> >      > - listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
> >      > - getsockname(sock, &usa[0].sa, &n) == 0 &&
> >      > - (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
> >      > - connect(sp[0], &usa[0].sa, n) == 0 &&
> >      > - (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
> >      > - success = true;
> >      > - }
> >      > - if (success) {
> >      > - mg_set_non_blocking_mode(sp[1]);
> >      > - } else {
> >      > - if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
> >      > - if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
> >      > - sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > + uint64_t i0 = a0 * b0;
> >      > + uint64_t i1 = a0 * b1;
> >      > + uint64_t i2 = a1 * b0;
> >      > + uint64_t i3 = a1 * b1;
> >      > +
> >      > + uint64_t p0, p1;
> >      > +
> >      > + i2 += (i0 >> 32);
> >      > + i2 += i1;
> >      > + if (i2 < i1) { /* overflow */
> >      > + i3 += 0x100000000ull;
> >      > }
> >      > - if (sock != MG_INVALID_SOCKET) closesocket(sock);
> >      > - return success;
> >      > +
> >      > + p0 = (i0 & 0xffffffffull) | (i2 << 32);
> >      > + p1 = i3 + (i2 >> 32);
> >      > +
> >      > + *r2 += (p1 >> 63);
> >      > + p1 = (p1 << 1) | (p0 >> 63);
> >      > + p0 <<= 1;
> >      > +
> >      > + *r0 += p0;
> >      > + *r1 += (p1 + (*r0 < p0));
> >      > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
> >      > +#else
> >      > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
> >      > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
> >     MG_UECC_WORD_BITS) | *r0;
> >      > + *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
> >      > + p *= 2;
> >      > + r01 += p;
> >      > + *r2 += (r01 < p);
> >      > + *r1 = r01 >> MG_UECC_WORD_BITS;
> >      > + *r0 = (mg_uecc_word_t) r01;
> >      > +#endif
> >      > }
> >      >
> >      > -int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void
> >     *fn_data,
> >      > - bool udp) {
> >      > - union usa usa[2];
> >      > - MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
> >      > - struct mg_connection *c = NULL;
> >      > - if (!mg_socketpair(sp, usa, udp)) {
> >      > - MG_ERROR(("Cannot create socket pair"));
> >      > - } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) ==
> >     NULL) {
> >      > - closesocket(sp[0]);
> >      > - closesocket(sp[1]);
> >      > - sp[0] = sp[1] = MG_INVALID_SOCKET;
> >      > - } else {
> >      > - tomgaddr(&usa[0], &c->rem, false);
> >      > - MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
> >     sp[0]));
> >      > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t r0 = 0;
> >      > + mg_uecc_word_t r1 = 0;
> >      > + mg_uecc_word_t r2 = 0;
> >      > +
> >      > + wordcount_t i, k;
> >      > +
> >      > + for (k = 0; k < num_words * 2 - 1; ++k) {
> >      > + mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
> >      > + for (i = min; i <= k && i <= k - i; ++i) {
> >      > + if (i < k - i) {
> >      > + mul2add(left[i], left[k - i], &r0, &r1, &r2);
> >      > + } else {
> >      > + muladd(left[i], left[k - i], &r0, &r1, &r2);
> >      > + }
> >      > + }
> >      > + result[k] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + r2 = 0;
> >      > }
> >      > - return (int) sp[0];
> >      > -}
> >      >
> >      > -static bool can_read(const struct mg_connection *c) {
> >      > - return c->is_full == false;
> >      > + result[num_words * 2 - 1] = r0;
> >      > }
> >      > +#endif /* !asm_square */
> >      >
> >      > -static bool can_write(const struct mg_connection *c) {
> >      > - return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
> >      > -}
> >      > +#else /* MG_UECC_SQUARE_FUNC */
> >      >
> >      > -static bool skip_iotest(const struct mg_connection *c) {
> >      > - return (c->is_closing || c->is_resolving || FD(c) ==
> >     MG_INVALID_SOCKET) ||
> >      > - (can_read(c) == false && can_write(c) == false);
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_vli_mult(result, left, left, num_words);
> >      > }
> >      > +#endif /* MG_UECC_ENABLE_VLI_API */
> >      >
> >      > -static void mg_iotest(struct mg_mgr *mgr, int ms) {
> >      > -#if MG_ENABLE_FREERTOS_TCP
> >      > - struct mg_connection *c;
> >      > - for (c = mgr->conns; c != NULL; c = c->next) {
> >      > - c->is_readable = c->is_writable = 0;
> >      > - if (skip_iotest(c)) continue;
> >      > - if (can_read(c))
> >      > - FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
> >      > - if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
> >      > - }
> >      > - FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
> >      > - for (c = mgr->conns; c != NULL; c = c->next) {
> >      > - EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
> >      > - c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
> >      > - c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
> >      > - FreeRTOS_FD_CLR(c->fd, mgr->ss,
> >      > - eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
> >      > +#endif /* MG_UECC_SQUARE_FUNC */
> >      > +
> >      > +/* Computes result = (left + right) % mod.
> >      > + Assumes that left < mod and right < mod, and that result does
> >     not overlap
> >      > + mod. */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right,
> >     num_words);
> >      > + if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) !=
> >     1) {
> >      > + /* result > mod (result = mod + remainder), so subtract mod to get
> >      > + * remainder. */
> >      > + mg_uecc_vli_sub(result, result, mod, num_words);
> >      > }
> >      > -#elif MG_ENABLE_EPOLL
> >      > - size_t max = 1;
> >      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > - c->is_readable = c->is_writable = 0;
> >      > - if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
> >      > - if (can_write(c)) MG_EPOLL_MOD(c, 1);
> >      > - max++;
> >      > +}
> >      > +
> >      > +/* Computes result = (left - right) % mod.
> >      > + Assumes that left < mod and right < mod, and that result does
> >     not overlap
> >      > + mod. */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right,
> >     num_words);
> >      > + if (l_borrow) {
> >      > + /* In this case, result == -diff == (max int) - diff. Since -x
> >     % d == d - x,
> >      > + we can get the correct result from result + mod (with
> >     overflow). */
> >      > + mg_uecc_vli_add(result, result, mod, num_words);
> >      > }
> >      > - struct epoll_event *evs = (struct epoll_event *) alloca(max *
> >     sizeof(evs[0]));
> >      > - int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
> >      > - for (int i = 0; i < n; i++) {
> >      > - struct mg_connection *c = (struct mg_connection *)
> >     evs[i].data.ptr;
> >      > - if (evs[i].events & EPOLLERR) {
> >      > - mg_error(c, "socket error");
> >      > - } else if (c->is_readable == 0) {
> >      > - bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
> >      > - bool wr = evs[i].events & EPOLLOUT;
> >      > - c->is_readable = can_read(c) && rd ? 1U : 0;
> >      > - c->is_writable = can_write(c) && wr ? 1U : 0;
> >      > +}
> >      > +
> >      > +/* Computes result = product % mod, where product is 2N words
> >     long. */
> >      > +/* Currently only designed to work for curve_p or curve_n. */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t *v[2] = {tmp, product};
> >      > + mg_uecc_word_t index;
> >      > +
> >      > + /* Shift mod so its highest set bit is at the maximum position. */
> >      > + bitcount_t shift = (bitcount_t) ((num_words * 2 *
> >     MG_UECC_WORD_BITS) -
> >      > + mg_uecc_vli_numBits(mod, num_words));
> >      > + wordcount_t word_shift = (wordcount_t) (shift /
> >     MG_UECC_WORD_BITS);
> >      > + wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
> >      > + mg_uecc_word_t carry = 0;
> >      > + mg_uecc_vli_clear(mod_multiple, word_shift);
> >      > + if (bit_shift > 0) {
> >      > + for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
> >      > + mod_multiple[(mg_uecc_word_t) word_shift + index] =
> >      > + (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
> >      > + carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
> >      > }
> >      > + } else {
> >      > + mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
> >      > }
> >      > - (void) skip_iotest;
> >      > -#elif MG_ENABLE_POLL
> >      > - nfds_t n = 0;
> >      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) n++;
> >      > - struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
> >      > - memset(fds, 0, n * sizeof(fds[0]));
> >      > - n = 0;
> >      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > - c->is_readable = c->is_writable = 0;
> >      > - if (skip_iotest(c)) {
> >      > - // Socket not valid, ignore
> >      > - } else if (mg_tls_pending(c) > 0) {
> >      > - ms = 1; // Don't wait if TLS is ready
> >      > - } else {
> >      > - fds[n].fd = FD(c);
> >      > - if (can_read(c)) fds[n].events |= POLLIN;
> >      > - if (can_write(c)) fds[n].events |= POLLOUT;
> >      > - n++;
> >      > +
> >      > + for (index = 1; shift >= 0; --shift) {
> >      > + mg_uecc_word_t borrow = 0;
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_words * 2; ++i) {
> >      > + mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
> >      > + if (diff != v[index][i]) {
> >      > + borrow = (diff > v[index][i]);
> >      > + }
> >      > + v[1 - index][i] = diff;
> >      > }
> >      > + index = !(index ^ borrow); /* Swap the index if there was no
> >     borrow */
> >      > + mg_uecc_vli_rshift1(mod_multiple, num_words);
> >      > + mod_multiple[num_words - 1] |= mod_multiple[num_words]
> >      > + << (MG_UECC_WORD_BITS - 1);
> >      > + mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
> >      > + }
> >      > + mg_uecc_vli_set(result, v[index], num_words);
> >      > +}
> >      > +
> >      > +/* Computes result = (left * right) % mod. */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_vli_mult(product, left, right, num_words);
> >      > + mg_uecc_vli_mmod(result, product, mod, num_words);
> >      > +}
> >      > +
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_vli_mult(product, left, right, curve->num_words);
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + curve->mmod_fast(result, product);
> >      > +#else
> >      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> >      > +#endif
> >      > +}
> >      > +
> >      > +#if MG_UECC_SQUARE_FUNC
> >      > +
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +/* Computes result = left^2 % mod. */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_vli_square(product, left, num_words);
> >      > + mg_uecc_vli_mmod(result, product, mod, num_words);
> >      > +}
> >      > +#endif /* MG_UECC_ENABLE_VLI_API */
> >      > +
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
> >      > + mg_uecc_vli_square(product, left, curve->num_words);
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + curve->mmod_fast(result, product);
> >      > +#else
> >      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> >      > +#endif
> >      > +}
> >      > +
> >      > +#else /* MG_UECC_SQUARE_FUNC */
> >      > +
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_vli_modMult(result, left, left, mod, num_words);
> >      > +}
> >      > +#endif /* MG_UECC_ENABLE_VLI_API */
> >      > +
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
> >     *result,
> >      > + const mg_uecc_word_t *left,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_vli_modMult_fast(result, left, left, curve);
> >      > +}
> >      > +
> >      > +#endif /* MG_UECC_SQUARE_FUNC */
> >      > +
> >      > +#define EVEN(vli) (!(vli[0] & 1))
> >      > +static void vli_modInv_update(mg_uecc_word_t *uv, const
> >     mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t carry = 0;
> >      > + if (!EVEN(uv)) {
> >      > + carry = mg_uecc_vli_add(uv, uv, mod, num_words);
> >      > }
> >      > + mg_uecc_vli_rshift1(uv, num_words);
> >      > + if (carry) {
> >      > + uv[num_words - 1] |= HIGH_BIT_SET;
> >      > + }
> >      > +}
> >      >
> >      > - // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
> >      > - if (poll(fds, n, ms) < 0) {
> >      > -#if MG_ARCH == MG_ARCH_WIN32
> >      > - if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
> >      > -#endif
> >      > - memset(fds, 0, n * sizeof(fds[0]));
> >      > +/* Computes result = (1 / input) % mod. All VLIs are the same size.
> >      > + See "From Euclid's GCD to Montgomery Multiplication to the
> >     Great Divide" */
> >      > +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *input,
> >      > + const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
> >      > + u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
> >      > + cmpresult_t cmpResult;
> >      > +
> >      > + if (mg_uecc_vli_isZero(input, num_words)) {
> >      > + mg_uecc_vli_clear(result, num_words);
> >      > + return;
> >      > }
> >      > - n = 0;
> >      > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
> >     c->next) {
> >      > - if (skip_iotest(c)) {
> >      > - // Socket not valid, ignore
> >      > - } else if (mg_tls_pending(c) > 0) {
> >      > - c->is_readable = 1;
> >      > +
> >      > + mg_uecc_vli_set(a, input, num_words);
> >      > + mg_uecc_vli_set(b, mod, num_words);
> >      > + mg_uecc_vli_clear(u, num_words);
> >      > + u[0] = 1;
> >      > + mg_uecc_vli_clear(v, num_words);
> >      > + while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) !=
> >     0) {
> >      > + if (EVEN(a)) {
> >      > + mg_uecc_vli_rshift1(a, num_words);
> >      > + vli_modInv_update(u, mod, num_words);
> >      > + } else if (EVEN(b)) {
> >      > + mg_uecc_vli_rshift1(b, num_words);
> >      > + vli_modInv_update(v, mod, num_words);
> >      > + } else if (cmpResult > 0) {
> >      > + mg_uecc_vli_sub(a, a, b, num_words);
> >      > + mg_uecc_vli_rshift1(a, num_words);
> >      > + if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
> >      > + mg_uecc_vli_add(u, u, mod, num_words);
> >      > + }
> >      > + mg_uecc_vli_sub(u, u, v, num_words);
> >      > + vli_modInv_update(u, mod, num_words);
> >      > } else {
> >      > - if (fds[n].revents & POLLERR) {
> >      > - mg_error(c, "socket error");
> >      > - } else {
> >      > - c->is_readable =
> >      > - (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
> >      > - c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
> >      > + mg_uecc_vli_sub(b, b, a, num_words);
> >      > + mg_uecc_vli_rshift1(b, num_words);
> >      > + if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
> >      > + mg_uecc_vli_add(v, v, mod, num_words);
> >      > }
> >      > - n++;
> >      > + mg_uecc_vli_sub(v, v, u, num_words);
> >      > + vli_modInv_update(v, mod, num_words);
> >      > }
> >      > }
> >      > -#else
> >      > - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
> >     {0, 0};
> >      > - struct mg_connection *c;
> >      > - fd_set rset, wset, eset;
> >      > - MG_SOCKET_TYPE maxfd = 0;
> >      > - int rc;
> >      > + mg_uecc_vli_set(result, u, num_words);
> >      > +}
> >      >
> >      > - FD_ZERO(&rset);
> >      > - FD_ZERO(&wset);
> >      > - FD_ZERO(&eset);
> >      > - for (c = mgr->conns; c != NULL; c = c->next) {
> >      > - c->is_readable = c->is_writable = 0;
> >      > - if (skip_iotest(c)) continue;
> >      > - FD_SET(FD(c), &eset);
> >      > - if (can_read(c)) FD_SET(FD(c), &rset);
> >      > - if (can_write(c)) FD_SET(FD(c), &wset);
> >      > - if (mg_tls_pending(c) > 0) tv = tv_zero;
> >      > - if (FD(c) > maxfd) maxfd = FD(c);
> >      > +/* ------ Point operations ------ */
> >      > +
> >      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
> >     2-clause license. */
> >      > +
> >      > +#ifndef _UECC_CURVE_SPECIFIC_H_
> >      > +#define _UECC_CURVE_SPECIFIC_H_
> >      > +
> >      > +#define num_bytes_secp160r1 20
> >      > +#define num_bytes_secp192r1 24
> >      > +#define num_bytes_secp224r1 28
> >      > +#define num_bytes_secp256r1 32
> >      > +#define num_bytes_secp256k1 32
> >      > +
> >      > +#if (MG_UECC_WORD_SIZE == 1)
> >      > +
> >      > +#define num_words_secp160r1 20
> >      > +#define num_words_secp192r1 24
> >      > +#define num_words_secp224r1 28
> >      > +#define num_words_secp256r1 32
> >      > +#define num_words_secp256k1 32
> >      > +
> >      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
> >      > + 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
> >      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
> >      > +
> >      > +#elif (MG_UECC_WORD_SIZE == 4)
> >      > +
> >      > +#define num_words_secp160r1 5
> >      > +#define num_words_secp192r1 6
> >      > +#define num_words_secp224r1 7
> >      > +#define num_words_secp256r1 8
> >      > +#define num_words_secp256k1 8
> >      > +
> >      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a,
> >     0x##h##g##f##e
> >      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
> >      > +
> >      > +#elif (MG_UECC_WORD_SIZE == 8)
> >      > +
> >      > +#define num_words_secp160r1 3
> >      > +#define num_words_secp192r1 3
> >      > +#define num_words_secp224r1 4
> >      > +#define num_words_secp256r1 4
> >      > +#define num_words_secp256k1 4
> >      > +
> >      > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h)
> >     0x##h##g##f##e##d##c##b##a##U
> >      > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
> >      > +
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> >      > + MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
> >      > +static void double_jacobian_default(mg_uecc_word_t *X1,
> >     mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> >      > + /* t1 = X, t2 = Y, t3 = Z */
> >      > + mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + if (mg_uecc_vli_isZero(Z1, num_words)) {
> >      > + return;
> >      > }
> >      >
> >      > - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) <
> >     0) {
> >      > -#if MG_ARCH == MG_ARCH_WIN32
> >      > - if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
> >     sockets
> >      > + mg_uecc_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */
> >      > + mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 =
> >     A */
> >      > + mg_uecc_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */
> >      > + mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
> >      > + mg_uecc_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */
> >      > +
> >      > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1
> >     + z1^2 */
> >      > + mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 =
> >     2*z1^2 */
> >      > + mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1
> >     - z1^2 */
> >      > + mg_uecc_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 -
> >     z1^4 */
> >      > +
> >      > + mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
> >      > + num_words); /* t3 = 2*(x1^2 - z1^4) */
> >      > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
> >      > + num_words); /* t1 = 3*(x1^2 - z1^4) */
> >      > + if (mg_uecc_vli_testBit(X1, 0)) {
> >      > + mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p,
> >     num_words);
> >      > + mg_uecc_vli_rshift1(X1, num_words);
> >      > + X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
> >      > + } else {
> >      > + mg_uecc_vli_rshift1(X1, num_words);
> >      > + }
> >      > + /* t1 = 3/2*(x1^2 - z1^4) = B */
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */
> >      > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
> >     B^2 - A */
> >      > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
> >     B^2 - 2A = x3 */
> >      > + mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A
> >     - x3 */
> >      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A -
> >     x3) */
> >      > + mg_uecc_vli_modSub(t4, X1, t4, curve->p,
> >      > + num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
> >      > +
> >      > + mg_uecc_vli_set(X1, Z1, num_words);
> >      > + mg_uecc_vli_set(Z1, Y1, num_words);
> >      > + mg_uecc_vli_set(Y1, t4, num_words);
> >      > +}
> >      > +
> >      > +/* Computes result = x^3 + ax + b. result must not overlap x. */
> >      > +static void x_side_default(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *x,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
> >      > + mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /*
> >     r = x^2 - 3 */
> >      > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3
> >     - 3x */
> >      > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> >      > + num_words); /* r = x^3 - 3x + b */
> >      > +}
> >      > +#endif /* MG_UECC_SUPPORTS_secp... */
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
> >      > + MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
> >      > +/* Compute a = sqrt(a) (mod curve_p). */
> >      > +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve
> >     curve) {
> >      > + bitcount_t i;
> >      > + mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
> >      > + mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + /* When curve->p == 3 (mod 4), we can compute
> >      > + sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
> >      > + mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p +
> >     1 */
> >      > + for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
> >      > + mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
> >      > + if (mg_uecc_vli_testBit(p1, i)) {
> >      > + mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
> >      > + }
> >      > + }
> >      > + mg_uecc_vli_set(a, l_result, num_words);
> >      > +}
> >      > +#endif /* MG_UECC_SUPPORTS_secp... */
> >      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp160r1
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product);
> >      > +#endif
> >      > +
> >      > +static const struct MG_UECC_Curve_t curve_secp160r1 = {
> >      > + num_words_secp160r1,
> >      > + num_bytes_secp160r1,
> >      > + 161, /* num_n_bits */
> >      > + {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
> >      > + BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
> >      > + {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
> >      > + BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
> >      > + BYTES_TO_WORDS_4(68, B5, 96, 4A),
> >      > +
> >      > + BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
> >      > + BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
> >      > + BYTES_TO_WORDS_4(55, 28, A6, 23)},
> >      > + {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
> >      > + BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
> >      > + BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
> >      > + &double_jacobian_default,
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + &mod_sqrt_default,
> >      > +#endif
> >      > + &x_side_default,
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + &vli_mmod_fast_secp160r1
> >      > +#endif
> >      > +};
> >      > +
> >      > +MG_UECC_Curve mg_uecc_secp160r1(void) {
> >      > + return &curve_secp160r1;
> >      > +}
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
> >      > +/* Computes result = product % curve_p
> >      > + see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
> >     <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf> page 354
> >      > +
> >      > + Note that this only works if log2(omega) < log2(p) / 2 */
> >      > +static void omega_mult_secp160r1(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *right);
> >      > +#if MG_UECC_WORD_SIZE == 8
> >      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product) {
> >      > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
> >      > + mg_uecc_word_t copy;
> >      > +
> >      > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
> >      > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> >      > +
> >      > + omega_mult_secp160r1(tmp,
> >      > + product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
> >      > +
> >      > + product[num_words_secp160r1 - 1] &= 0xffffffff;
> >      > + copy = tmp[num_words_secp160r1 - 1];
> >      > + tmp[num_words_secp160r1 - 1] &= 0xffffffff;
> >      > + mg_uecc_vli_add(result, product, tmp,
> >      > + num_words_secp160r1); /* (C, r) = r + q */
> >      > + mg_uecc_vli_clear(product, num_words_secp160r1);
> >      > + tmp[num_words_secp160r1 - 1] = copy;
> >      > + omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1);
> >     /* Rq*c */
> >      > + mg_uecc_vli_add(result, result, product,
> >      > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
> >      > +
> >      > + while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
> >      > + num_words_secp160r1) > 0) {
> >      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
> >     num_words_secp160r1);
> >      > + }
> >      > +}
> >      > +
> >      > +static void omega_mult_secp160r1(uint64_t *result, const
> >     uint64_t *right) {
> >      > + uint32_t carry;
> >      > + unsigned i;
> >      > +
> >      > + /* Multiply by (2^31 + 1). */
> >      > + carry = 0;
> >      > + for (i = 0; i < num_words_secp160r1; ++i) {
> >      > + uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
> >      > + result[i] = (tmp << 31) + tmp + carry;
> >      > + carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i]
> >     == tmp));
> >      > + }
> >      > + result[i] = carry;
> >      > +}
> >      > #else
> >      > - MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
> >      > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product) {
> >      > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
> >      > + mg_uecc_word_t carry;
> >      > +
> >      > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
> >      > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
> >      > +
> >      > + omega_mult_secp160r1(tmp,
> >      > + product + num_words_secp160r1); /* (Rq, q) = q * c */
> >      > +
> >      > + carry = mg_uecc_vli_add(result, product, tmp,
> >      > + num_words_secp160r1); /* (C, r) = r + q */
> >      > + mg_uecc_vli_clear(product, num_words_secp160r1);
> >      > + omega_mult_secp160r1(product, tmp + num_words_secp160r1); /*
> >     Rq*c */
> >      > + carry += mg_uecc_vli_add(result, result, product,
> >      > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
> >      > +
> >      > + while (carry > 0) {
> >      > + --carry;
> >      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
> >     num_words_secp160r1);
> >      > + }
> >      > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
> >     num_words_secp160r1) >
> >      > + 0) {
> >      > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
> >     num_words_secp160r1);
> >      > + }
> >      > +}
> >      > #endif
> >      > - FD_ZERO(&rset);
> >      > - FD_ZERO(&wset);
> >      > - FD_ZERO(&eset);
> >      > +
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +static void omega_mult_secp160r1(uint8_t *result, const uint8_t
> >     *right) {
> >      > + uint8_t carry;
> >      > + uint8_t i;
> >      > +
> >      > + /* Multiply by (2^31 + 1). */
> >      > + mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /*
> >     2^32 */
> >      > + mg_uecc_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */
> >      > + result[3] = right[0] << 7; /* get last bit from shift */
> >      > +
> >      > + carry = mg_uecc_vli_add(result, result, right,
> >      > + num_words_secp160r1); /* 2^31 + 1 */
> >      > + for (i = num_words_secp160r1; carry; ++i) {
> >      > + uint16_t sum = (uint16_t) result[i] + carry;
> >      > + result[i] = (uint8_t) sum;
> >      > + carry = sum >> 8;
> >      > }
> >      > +}
> >      > +#elif MG_UECC_WORD_SIZE == 4
> >      > +static void omega_mult_secp160r1(uint32_t *result, const
> >     uint32_t *right) {
> >      > + uint32_t carry;
> >      > + unsigned i;
> >      >
> >      > - for (c = mgr->conns; c != NULL; c = c->next) {
> >      > - if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
> >      > - mg_error(c, "socket error");
> >      > - } else {
> >      > - c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
> >     &rset);
> >      > - c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
> >     &wset);
> >      > - if (mg_tls_pending(c) > 0) c->is_readable = 1;
> >      > - }
> >      > + /* Multiply by (2^31 + 1). */
> >      > + mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /*
> >     2^32 */
> >      > + mg_uecc_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */
> >      > + result[0] = right[0] << 31; /* get last bit from shift */
> >      > +
> >      > + carry = mg_uecc_vli_add(result, result, right,
> >      > + num_words_secp160r1); /* 2^31 + 1 */
> >      > + for (i = num_words_secp160r1; carry; ++i) {
> >      > + uint64_t sum = (uint64_t) result[i] + carry;
> >      > + result[i] = (uint32_t) sum;
> >      > + carry = sum >> 32;
> >      > }
> >      > +}
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
> >     !asm_mmod_fast_secp160r1) */
> >      > +
> >      > +#endif /* MG_UECC_SUPPORTS_secp160r1 */
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp192r1
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product);
> >      > #endif
> >      > +
> >      > +static const struct MG_UECC_Curve_t curve_secp192r1 = {
> >      > + num_words_secp192r1,
> >      > + num_bytes_secp192r1,
> >      > + 192, /* num_n_bits */
> >      > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
> >      > + BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
> >      > + BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
> >      > + BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
> >      > +
> >      > + BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
> >      > + BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
> >      > + BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
> >      > + {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
> >      > + BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
> >      > + BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
> >      > + &double_jacobian_default,
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + &mod_sqrt_default,
> >      > +#endif
> >      > + &x_side_default,
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + &vli_mmod_fast_secp192r1
> >      > +#endif
> >      > +};
> >      > +
> >      > +MG_UECC_Curve mg_uecc_secp192r1(void) {
> >      > + return &curve_secp192r1;
> >      > }
> >      >
> >      > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> >      > - struct mg_connection *c, *tmp;
> >      > - uint64_t now;
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +/* Computes result = product % curve_p.
> >      > + See algorithm 5 and 6 from
> >     http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
> >     <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf>
> >      > + */
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t
> >     *product) {
> >      > + uint8_t tmp[num_words_secp192r1];
> >      > + uint8_t carry;
> >      > +
> >      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> >      > +
> >      > + mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
> >      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> >      > +
> >      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] =
> >     tmp[7] = 0;
> >      > + tmp[8] = product[24];
> >      > + tmp[9] = product[25];
> >      > + tmp[10] = product[26];
> >      > + tmp[11] = product[27];
> >      > + tmp[12] = product[28];
> >      > + tmp[13] = product[29];
> >      > + tmp[14] = product[30];
> >      > + tmp[15] = product[31];
> >      > + tmp[16] = product[32];
> >      > + tmp[17] = product[33];
> >      > + tmp[18] = product[34];
> >      > + tmp[19] = product[35];
> >      > + tmp[20] = product[36];
> >      > + tmp[21] = product[37];
> >      > + tmp[22] = product[38];
> >      > + tmp[23] = product[39];
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + tmp[0] = tmp[8] = product[40];
> >      > + tmp[1] = tmp[9] = product[41];
> >      > + tmp[2] = tmp[10] = product[42];
> >      > + tmp[3] = tmp[11] = product[43];
> >      > + tmp[4] = tmp[12] = product[44];
> >      > + tmp[5] = tmp[13] = product[45];
> >      > + tmp[6] = tmp[14] = product[46];
> >      > + tmp[7] = tmp[15] = product[47];
> >      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] =
> >     tmp[22] =
> >      > + tmp[23] = 0;
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> >      > + num_words_secp192r1) != 1) {
> >      > + carry -=
> >      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
> >     num_words_secp192r1);
> >      > + }
> >      > +}
> >      > +#elif MG_UECC_WORD_SIZE == 4
> >      > +static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t
> >     *product) {
> >      > + uint32_t tmp[num_words_secp192r1];
> >      > + int carry;
> >      >
> >      > - mg_iotest(mgr, ms);
> >      > - now = mg_millis();
> >      > - mg_timer_poll(&mgr->timers, now);
> >      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> >      >
> >      > - for (c = mgr->conns; c != NULL; c = tmp) {
> >      > - bool is_resp = c->is_resp;
> >      > - tmp = c->next;
> >      > - mg_call(c, MG_EV_POLL, &now);
> >      > - if (is_resp && !c->is_resp) {
> >      > - long n = 0;
> >      > - mg_call(c, MG_EV_READ, &n);
> >      > - }
> >      > - MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r'
> >     : '-',
> >      > - c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
> >      > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> >      > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> >      > - if (c->is_resolving || c->is_closing) {
> >      > - // Do nothing
> >      > - } else if (c->is_listening && c->is_udp == 0) {
> >      > - if (c->is_readable) accept_conn(mgr, c);
> >      > - } else if (c->is_connecting) {
> >      > - if (c->is_readable || c->is_writable) connect_conn(c);
> >      > - } else if (c->is_tls_hs) {
> >      > - if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
> >      > - } else {
> >      > - if (c->is_readable) read_conn(c);
> >      > - if (c->is_writable) write_conn(c);
> >      > - }
> >      > + mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
> >      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
> >      >
> >      > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> >      > - if (c->is_closing) close_conn(c);
> >      > + tmp[0] = tmp[1] = 0;
> >      > + tmp[2] = product[6];
> >      > + tmp[3] = product[7];
> >      > + tmp[4] = product[8];
> >      > + tmp[5] = product[9];
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + tmp[0] = tmp[2] = product[10];
> >      > + tmp[1] = tmp[3] = product[11];
> >      > + tmp[4] = tmp[5] = 0;
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> >      > + num_words_secp192r1) != 1) {
> >      > + carry -=
> >      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
> >     num_words_secp192r1);
> >      > + }
> >      > +}
> >      > +#else
> >      > +static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t
> >     *product) {
> >      > + uint64_t tmp[num_words_secp192r1];
> >      > + int carry;
> >      > +
> >      > + mg_uecc_vli_set(result, product, num_words_secp192r1);
> >      > +
> >      > + mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
> >      > + carry = (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + tmp[0] = 0;
> >      > + tmp[1] = product[3];
> >      > + tmp[2] = product[4];
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + tmp[0] = tmp[1] = product[5];
> >      > + tmp[2] = 0;
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp192r1);
> >      > +
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
> >      > + num_words_secp192r1) != 1) {
> >      > + carry -=
> >      > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
> >     num_words_secp192r1);
> >      > }
> >      > }
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
> >      > +
> >      > +#endif /* MG_UECC_SUPPORTS_secp192r1 */
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp224r1
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
> >     curve);
> >      > +#endif
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product);
> >      > #endif
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/ssi.c"
> >      > +static const struct MG_UECC_Curve_t curve_secp224r1 = {
> >      > + num_words_secp224r1,
> >      > + num_bytes_secp224r1,
> >      > + 224, /* num_n_bits */
> >      > + {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
> >      > + BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
> >      > + BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
> >      > + BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
> >      > + BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
> >      > +
> >      > + BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
> >      > + BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
> >      > + BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
> >      > + BYTES_TO_WORDS_4(88, 63, 37, BD)},
> >      > + {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
> >      > + BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
> >      > + BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
> >      > + BYTES_TO_WORDS_4(85, 0A, 05, B4)},
> >      > + &double_jacobian_default,
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + &mod_sqrt_secp224r1,
> >      > #endif
> >      > + &x_side_default,
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + &vli_mmod_fast_secp224r1
> >      > +#endif
> >      > +};
> >      > +
> >      > +MG_UECC_Curve mg_uecc_secp224r1(void) {
> >      > + return &curve_secp224r1;
> >      > +}
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +/* Routine 3.2.4 RS; from
> >     http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1,
> >     mg_uecc_word_t *e1,
> >      > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> >      > + const mg_uecc_word_t *e0,
> >      > + const mg_uecc_word_t *f0) {
> >      > + mg_uecc_word_t t[num_words_secp224r1];
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <--
> >     d0 ^ 2 */
> >      > + mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1
> >     <-- d0 * e0 */
> >      > + mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* d1 <-- t + f0 */
> >      > + mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* e1 <-- e1 + e1 */
> >      > + mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1
> >     <-- t * f0 */
> >      > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* f1 <-- f1 + f1 */
> >      > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* f1 <-- f1 + f1 */
> >      > +}
> >      > +
> >      > +/* Routine 3.2.5 RSS; from
> >     http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1,
> >     mg_uecc_word_t *e1,
> >      > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
> >      > + const mg_uecc_word_t *e0,
> >      > + const mg_uecc_word_t *f0,
> >      > + const bitcount_t j) {
> >      > + bitcount_t i;
> >      > +
> >      > + mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
> >      > + mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
> >      > + mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
> >      > + for (i = 1; i <= j; i++) {
> >      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS
> >     (d1,e1,f1,d1,e1,f1) */
> >      > + }
> >      > +}
> >      > +
> >      > +/* Routine 3.2.6 RM; from
> >     http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2,
> >     mg_uecc_word_t *e2,
> >      > + mg_uecc_word_t *f2, const mg_uecc_word_t *c,
> >      > + const mg_uecc_word_t *d0,
> >      > + const mg_uecc_word_t *e0,
> >      > + const mg_uecc_word_t *d1,
> >      > + const mg_uecc_word_t *e1) {
> >      > + mg_uecc_word_t t1[num_words_secp224r1];
> >      > + mg_uecc_word_t t2[num_words_secp224r1];
> >      > +
> >      > + mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1
> >     <-- e0 * e1 */
> >      > + mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1
> >     <-- t1 * c */
> >      > + /* t1 <-- p - t1 */
> >      > + mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2
> >     <-- d0 * d1 */
> >      > + mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* t2 <-- t2 + t1 */
> >      > + mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1
> >     <-- d0 * e1 */
> >      > + mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2
> >     <-- d1 * e0 */
> >      > + mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* e2 <-- e2 + t1 */
> >      > + mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <--
> >     e2^2 */
> >      > + mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2
> >     <-- f2 * c */
> >      > + /* f2 <-- p - f2 */
> >      > + mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
> >      > +}
> >      > +
> >      > +/* Routine 3.2.7 RP; from
> >     http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1,
> >     mg_uecc_word_t *e1,
> >      > + mg_uecc_word_t *f1, const mg_uecc_word_t *c,
> >      > + const mg_uecc_word_t *r) {
> >      > + wordcount_t i;
> >      > + wordcount_t pow2i = 1;
> >      > + mg_uecc_word_t d0[num_words_secp224r1];
> >      > + mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
> >      > + mg_uecc_word_t f0[num_words_secp224r1];
> >      > +
> >      > + mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
> >      > + /* f0 <-- p - c */
> >      > + mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + for (i = 0; i <= 6; i++) {
> >      > + mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
> >      > + pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
> >      > + mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
> >      > + e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
> >      > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> >      > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> >      > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> >      > + pow2i *= 2;
> >      > + }
> >      > +}
> >      >
> >      > +/* Compute a = sqrt(a) (mod curve_p). */
> >      > +/* Routine 3.2.8 mp_mod_sqrt_224; from
> >      > + * http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
> >     curve) {
> >      > + (void) curve;
> >      > + bitcount_t i;
> >      > + mg_uecc_word_t e1[num_words_secp224r1];
> >      > + mg_uecc_word_t f1[num_words_secp224r1];
> >      > + mg_uecc_word_t d0[num_words_secp224r1];
> >      > + mg_uecc_word_t e0[num_words_secp224r1];
> >      > + mg_uecc_word_t f0[num_words_secp224r1];
> >      > + mg_uecc_word_t d1[num_words_secp224r1];
> >      > +
> >      > + /* s = a; using constant instead of random value */
> >      > + mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c,
> >     s) */
> >      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> >      > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
> >      > + for (i = 1; i <= 95; i++) {
> >      > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
> >      > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
> >      > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
> >      > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
> >      > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
> >      > + if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 ==
> >     0 */
> >      > + break;
> >      > + }
> >      > + }
> >      > + mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
> >      > + num_words_secp224r1); /* f1 <-- 1 / e0 */
> >      > + mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <--
> >     d0 / e0 */
> >      > +}
> >      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +/* Computes result = product % curve_p
> >      > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t
> >     *product) {
> >      > + uint8_t tmp[num_words_secp224r1];
> >      > + int8_t carry;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> >      > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> >      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> >      > + tmp[12] = product[28];
> >      > + tmp[13] = product[29];
> >      > + tmp[14] = product[30];
> >      > + tmp[15] = product[31];
> >      > + tmp[16] = product[32];
> >      > + tmp[17] = product[33];
> >      > + tmp[18] = product[34];
> >      > + tmp[19] = product[35];
> >      > + tmp[20] = product[36];
> >      > + tmp[21] = product[37];
> >      > + tmp[22] = product[38];
> >      > + tmp[23] = product[39];
> >      > + tmp[24] = product[40];
> >      > + tmp[25] = product[41];
> >      > + tmp[26] = product[42];
> >      > + tmp[27] = product[43];
> >      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[12] = product[44];
> >      > + tmp[13] = product[45];
> >      > + tmp[14] = product[46];
> >      > + tmp[15] = product[47];
> >      > + tmp[16] = product[48];
> >      > + tmp[17] = product[49];
> >      > + tmp[18] = product[50];
> >      > + tmp[19] = product[51];
> >      > + tmp[20] = product[52];
> >      > + tmp[21] = product[53];
> >      > + tmp[22] = product[54];
> >      > + tmp[23] = product[55];
> >      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = product[28];
> >      > + tmp[1] = product[29];
> >      > + tmp[2] = product[30];
> >      > + tmp[3] = product[31];
> >      > + tmp[4] = product[32];
> >      > + tmp[5] = product[33];
> >      > + tmp[6] = product[34];
> >      > + tmp[7] = product[35];
> >      > + tmp[8] = product[36];
> >      > + tmp[9] = product[37];
> >      > + tmp[10] = product[38];
> >      > + tmp[11] = product[39];
> >      > + tmp[12] = product[40];
> >      > + tmp[13] = product[41];
> >      > + tmp[14] = product[42];
> >      > + tmp[15] = product[43];
> >      > + tmp[16] = product[44];
> >      > + tmp[17] = product[45];
> >      > + tmp[18] = product[46];
> >      > + tmp[19] = product[47];
> >      > + tmp[20] = product[48];
> >      > + tmp[21] = product[49];
> >      > + tmp[22] = product[50];
> >      > + tmp[23] = product[51];
> >      > + tmp[24] = product[52];
> >      > + tmp[25] = product[53];
> >      > + tmp[26] = product[54];
> >      > + tmp[27] = product[55];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = product[44];
> >      > + tmp[1] = product[45];
> >      > + tmp[2] = product[46];
> >      > + tmp[3] = product[47];
> >      > + tmp[4] = product[48];
> >      > + tmp[5] = product[49];
> >      > + tmp[6] = product[50];
> >      > + tmp[7] = product[51];
> >      > + tmp[8] = product[52];
> >      > + tmp[9] = product[53];
> >      > + tmp[10] = product[54];
> >      > + tmp[11] = product[55];
> >      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> >      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> >      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> >      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> >      > + num_words_secp224r1) != 1) {
> >      > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + }
> >      > + }
> >      > +}
> >      > +#elif MG_UECC_WORD_SIZE == 4
> >      > +static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t
> >     *product) {
> >      > + uint32_t tmp[num_words_secp224r1];
> >      > + int carry;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = tmp[1] = tmp[2] = 0;
> >      > + tmp[3] = product[7];
> >      > + tmp[4] = product[8];
> >      > + tmp[5] = product[9];
> >      > + tmp[6] = product[10];
> >      > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[3] = product[11];
> >      > + tmp[4] = product[12];
> >      > + tmp[5] = product[13];
> >      > + tmp[6] = 0;
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = product[7];
> >      > + tmp[1] = product[8];
> >      > + tmp[2] = product[9];
> >      > + tmp[3] = product[10];
> >      > + tmp[4] = product[11];
> >      > + tmp[5] = product[12];
> >      > + tmp[6] = product[13];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = product[11];
> >      > + tmp[1] = product[12];
> >      > + tmp[2] = product[13];
> >      > + tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> >      > + num_words_secp224r1) != 1) {
> >      > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + }
> >      > + }
> >      > +}
> >      > +#else
> >      > +static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t
> >     *product) {
> >      > + uint64_t tmp[num_words_secp224r1];
> >      > + int carry = 0;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp224r1);
> >      > + result[num_words_secp224r1 - 1] &= 0xffffffff;
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = 0;
> >      > + tmp[1] = product[3] & 0xffffffff00000000ull;
> >      > + tmp[2] = product[4];
> >      > + tmp[3] = product[5] & 0xffffffff;
> >      > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[1] = product[5] & 0xffffffff00000000ull;
> >      > + tmp[2] = product[6];
> >      > + tmp[3] = 0;
> >      > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = (product[3] >> 32) | (product[4] << 32);
> >      > + tmp[1] = (product[4] >> 32) | (product[5] << 32);
> >      > + tmp[2] = (product[5] >> 32) | (product[6] << 32);
> >      > + tmp[3] = product[6] >> 32;
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
> >      > + tmp[1] = product[6] >> 32;
> >      > + tmp[2] = tmp[3] = 0;
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp224r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
> >      > + num_words_secp224r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
> >      > + num_words_secp224r1) != 1) {
> >      > + mg_uecc_vli_sub(result, result, curve_secp224r1.p,
> >     num_words_secp224r1);
> >      > + }
> >      > + }
> >      > +}
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
> >      >
> >      > +#endif /* MG_UECC_SUPPORTS_secp224r1 */
> >      >
> >      > +#if MG_UECC_SUPPORTS_secp256r1
> >      >
> >      > -#ifndef MG_MAX_SSI_DEPTH
> >      > -#define MG_MAX_SSI_DEPTH 5
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product);
> >      > #endif
> >      >
> >      > -#ifndef MG_SSI_BUFSIZ
> >      > -#define MG_SSI_BUFSIZ 1024
> >      > +static const struct MG_UECC_Curve_t curve_secp256r1 = {
> >      > + num_words_secp256r1,
> >      > + num_bytes_secp256r1,
> >      > + 256, /* num_n_bits */
> >      > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
> >      > + BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
> >      > + BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
> >      > + BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
> >      > + BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
> >      > +
> >      > + BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
> >      > + BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
> >      > + BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
> >      > + BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
> >      > + {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
> >      > + BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
> >      > + BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
> >      > + BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
> >      > + &double_jacobian_default,
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + &mod_sqrt_default,
> >      > +#endif
> >      > + &x_side_default,
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + &vli_mmod_fast_secp256r1
> >      > #endif
> >      > +};
> >      >
> >      > -#if MG_ENABLE_SSI
> >      > -static char *mg_ssi(const char *path, const char *root, int
> >     depth) {
> >      > - struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
> >      > - FILE *fp = fopen(path, "rb");
> >      > - if (fp != NULL) {
> >      > - char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
> >      > - int ch, intag = 0;
> >      > - size_t len = 0;
> >      > - buf[0] = arg[0] = '\0';
> >      > - while ((ch = fgetc(fp)) != EOF) {
> >      > - if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
> >     == '-') {
> >      > - buf[len++] = (char) (ch & 0xff);
> >      > - buf[len] = '\0';
> >      > - if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
> >      > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
> >      > - *p = (char *) path + strlen(path), *data;
> >      > - while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
> >      > - mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
> >     arg);
> >      > - if (depth < MG_MAX_SSI_DEPTH &&
> >      > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> >      > - mg_iobuf_add(&b, b.len, data, strlen(data));
> >      > - free(data);
> >      > - } else {
> >      > - MG_ERROR(("%s: file=%s error or too deep", path, arg));
> >      > - }
> >      > - } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
> >      > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
> >      > - mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
> >      > - if (depth < MG_MAX_SSI_DEPTH &&
> >      > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
> >      > - mg_iobuf_add(&b, b.len, data, strlen(data));
> >      > - free(data);
> >      > - } else {
> >      > - MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
> >      > - }
> >      > - } else {
> >      > - // Unknown SSI tag
> >      > - MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
> >      > - mg_iobuf_add(&b, b.len, buf, len);
> >      > - }
> >      > - intag = 0;
> >      > - len = 0;
> >      > - } else if (ch == '<') {
> >      > - intag = 1;
> >      > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> >      > - len = 0;
> >      > - buf[len++] = (char) (ch & 0xff);
> >      > - } else if (intag) {
> >      > - if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
> >      > - intag = 0;
> >      > - } else if (len >= sizeof(buf) - 2) {
> >      > - MG_ERROR(("%s: SSI tag is too large", path));
> >      > - len = 0;
> >      > - }
> >      > - buf[len++] = (char) (ch & 0xff);
> >      > - } else {
> >      > - buf[len++] = (char) (ch & 0xff);
> >      > - if (len >= sizeof(buf)) {
> >      > - mg_iobuf_add(&b, b.len, buf, len);
> >      > - len = 0;
> >      > - }
> >      > - }
> >      > +MG_UECC_Curve mg_uecc_secp256r1(void) {
> >      > + return &curve_secp256r1;
> >      > +}
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
> >      > +/* Computes result = product % curve_p
> >      > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
> >     <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t
> >     *product) {
> >      > + uint8_t tmp[num_words_secp256r1];
> >      > + int8_t carry;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
> >      > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
> >      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> >      > + tmp[12] = product[44];
> >      > + tmp[13] = product[45];
> >      > + tmp[14] = product[46];
> >      > + tmp[15] = product[47];
> >      > + tmp[16] = product[48];
> >      > + tmp[17] = product[49];
> >      > + tmp[18] = product[50];
> >      > + tmp[19] = product[51];
> >      > + tmp[20] = product[52];
> >      > + tmp[21] = product[53];
> >      > + tmp[22] = product[54];
> >      > + tmp[23] = product[55];
> >      > + tmp[24] = product[56];
> >      > + tmp[25] = product[57];
> >      > + tmp[26] = product[58];
> >      > + tmp[27] = product[59];
> >      > + tmp[28] = product[60];
> >      > + tmp[29] = product[61];
> >      > + tmp[30] = product[62];
> >      > + tmp[31] = product[63];
> >      > + carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[12] = product[48];
> >      > + tmp[13] = product[49];
> >      > + tmp[14] = product[50];
> >      > + tmp[15] = product[51];
> >      > + tmp[16] = product[52];
> >      > + tmp[17] = product[53];
> >      > + tmp[18] = product[54];
> >      > + tmp[19] = product[55];
> >      > + tmp[20] = product[56];
> >      > + tmp[21] = product[57];
> >      > + tmp[22] = product[58];
> >      > + tmp[23] = product[59];
> >      > + tmp[24] = product[60];
> >      > + tmp[25] = product[61];
> >      > + tmp[26] = product[62];
> >      > + tmp[27] = product[63];
> >      > + tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
> >      > + carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s3 */
> >      > + tmp[0] = product[32];
> >      > + tmp[1] = product[33];
> >      > + tmp[2] = product[34];
> >      > + tmp[3] = product[35];
> >      > + tmp[4] = product[36];
> >      > + tmp[5] = product[37];
> >      > + tmp[6] = product[38];
> >      > + tmp[7] = product[39];
> >      > + tmp[8] = product[40];
> >      > + tmp[9] = product[41];
> >      > + tmp[10] = product[42];
> >      > + tmp[11] = product[43];
> >      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> >      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> >      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> >      > + tmp[24] = product[56];
> >      > + tmp[25] = product[57];
> >      > + tmp[26] = product[58];
> >      > + tmp[27] = product[59];
> >      > + tmp[28] = product[60];
> >      > + tmp[29] = product[61];
> >      > + tmp[30] = product[62];
> >      > + tmp[31] = product[63];
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s4 */
> >      > + tmp[0] = product[36];
> >      > + tmp[1] = product[37];
> >      > + tmp[2] = product[38];
> >      > + tmp[3] = product[39];
> >      > + tmp[4] = product[40];
> >      > + tmp[5] = product[41];
> >      > + tmp[6] = product[42];
> >      > + tmp[7] = product[43];
> >      > + tmp[8] = product[44];
> >      > + tmp[9] = product[45];
> >      > + tmp[10] = product[46];
> >      > + tmp[11] = product[47];
> >      > + tmp[12] = product[52];
> >      > + tmp[13] = product[53];
> >      > + tmp[14] = product[54];
> >      > + tmp[15] = product[55];
> >      > + tmp[16] = product[56];
> >      > + tmp[17] = product[57];
> >      > + tmp[18] = product[58];
> >      > + tmp[19] = product[59];
> >      > + tmp[20] = product[60];
> >      > + tmp[21] = product[61];
> >      > + tmp[22] = product[62];
> >      > + tmp[23] = product[63];
> >      > + tmp[24] = product[52];
> >      > + tmp[25] = product[53];
> >      > + tmp[26] = product[54];
> >      > + tmp[27] = product[55];
> >      > + tmp[28] = product[32];
> >      > + tmp[29] = product[33];
> >      > + tmp[30] = product[34];
> >      > + tmp[31] = product[35];
> >      > + carry += mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = product[44];
> >      > + tmp[1] = product[45];
> >      > + tmp[2] = product[46];
> >      > + tmp[3] = product[47];
> >      > + tmp[4] = product[48];
> >      > + tmp[5] = product[49];
> >      > + tmp[6] = product[50];
> >      > + tmp[7] = product[51];
> >      > + tmp[8] = product[52];
> >      > + tmp[9] = product[53];
> >      > + tmp[10] = product[54];
> >      > + tmp[11] = product[55];
> >      > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
> >      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> >      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> >      > + tmp[24] = product[32];
> >      > + tmp[25] = product[33];
> >      > + tmp[26] = product[34];
> >      > + tmp[27] = product[35];
> >      > + tmp[28] = product[40];
> >      > + tmp[29] = product[41];
> >      > + tmp[30] = product[42];
> >      > + tmp[31] = product[43];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = product[48];
> >      > + tmp[1] = product[49];
> >      > + tmp[2] = product[50];
> >      > + tmp[3] = product[51];
> >      > + tmp[4] = product[52];
> >      > + tmp[5] = product[53];
> >      > + tmp[6] = product[54];
> >      > + tmp[7] = product[55];
> >      > + tmp[8] = product[56];
> >      > + tmp[9] = product[57];
> >      > + tmp[10] = product[58];
> >      > + tmp[11] = product[59];
> >      > + tmp[12] = product[60];
> >      > + tmp[13] = product[61];
> >      > + tmp[14] = product[62];
> >      > + tmp[15] = product[63];
> >      > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
> >      > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
> >      > + tmp[24] = product[36];
> >      > + tmp[25] = product[37];
> >      > + tmp[26] = product[38];
> >      > + tmp[27] = product[39];
> >      > + tmp[28] = product[44];
> >      > + tmp[29] = product[45];
> >      > + tmp[30] = product[46];
> >      > + tmp[31] = product[47];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d3 */
> >      > + tmp[0] = product[52];
> >      > + tmp[1] = product[53];
> >      > + tmp[2] = product[54];
> >      > + tmp[3] = product[55];
> >      > + tmp[4] = product[56];
> >      > + tmp[5] = product[57];
> >      > + tmp[6] = product[58];
> >      > + tmp[7] = product[59];
> >      > + tmp[8] = product[60];
> >      > + tmp[9] = product[61];
> >      > + tmp[10] = product[62];
> >      > + tmp[11] = product[63];
> >      > + tmp[12] = product[32];
> >      > + tmp[13] = product[33];
> >      > + tmp[14] = product[34];
> >      > + tmp[15] = product[35];
> >      > + tmp[16] = product[36];
> >      > + tmp[17] = product[37];
> >      > + tmp[18] = product[38];
> >      > + tmp[19] = product[39];
> >      > + tmp[20] = product[40];
> >      > + tmp[21] = product[41];
> >      > + tmp[22] = product[42];
> >      > + tmp[23] = product[43];
> >      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> >      > + tmp[28] = product[48];
> >      > + tmp[29] = product[49];
> >      > + tmp[30] = product[50];
> >      > + tmp[31] = product[51];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d4 */
> >      > + tmp[0] = product[56];
> >      > + tmp[1] = product[57];
> >      > + tmp[2] = product[58];
> >      > + tmp[3] = product[59];
> >      > + tmp[4] = product[60];
> >      > + tmp[5] = product[61];
> >      > + tmp[6] = product[62];
> >      > + tmp[7] = product[63];
> >      > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
> >      > + tmp[12] = product[36];
> >      > + tmp[13] = product[37];
> >      > + tmp[14] = product[38];
> >      > + tmp[15] = product[39];
> >      > + tmp[16] = product[40];
> >      > + tmp[17] = product[41];
> >      > + tmp[18] = product[42];
> >      > + tmp[19] = product[43];
> >      > + tmp[20] = product[44];
> >      > + tmp[21] = product[45];
> >      > + tmp[22] = product[46];
> >      > + tmp[23] = product[47];
> >      > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
> >      > + tmp[28] = product[52];
> >      > + tmp[29] = product[53];
> >      > + tmp[30] = product[54];
> >      > + tmp[31] = product[55];
> >      > + carry -= mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> >      > + num_words_secp256r1) != 1) {
> >      > + carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > }
> >      > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
> >      > - if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
> >      > - fclose(fp);
> >      > }
> >      > - (void) depth;
> >      > - (void) root;
> >      > - return (char *) b.buf;
> >      > }
> >      > -
> >      > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> >      > - const char *fullpath) {
> >      > - const char *headers = "Content-Type: text/html;
> >     charset=utf-8\r\n";
> >      > - char *data = mg_ssi(fullpath, root, 0);
> >      > - mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
> >      > - free(data);
> >      > +#elif MG_UECC_WORD_SIZE == 4
> >      > +static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t
> >     *product) {
> >      > + uint32_t tmp[num_words_secp256r1];
> >      > + int carry;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = tmp[1] = tmp[2] = 0;
> >      > + tmp[3] = product[11];
> >      > + tmp[4] = product[12];
> >      > + tmp[5] = product[13];
> >      > + tmp[6] = product[14];
> >      > + tmp[7] = product[15];
> >      > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[3] = product[12];
> >      > + tmp[4] = product[13];
> >      > + tmp[5] = product[14];
> >      > + tmp[6] = product[15];
> >      > + tmp[7] = 0;
> >      > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
> >     num_words_secp256r1);
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s3 */
> >      > + tmp[0] = product[8];
> >      > + tmp[1] = product[9];
> >      > + tmp[2] = product[10];
> >      > + tmp[3] = tmp[4] = tmp[5] = 0;
> >      > + tmp[6] = product[14];
> >      > + tmp[7] = product[15];
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s4 */
> >      > + tmp[0] = product[9];
> >      > + tmp[1] = product[10];
> >      > + tmp[2] = product[11];
> >      > + tmp[3] = product[13];
> >      > + tmp[4] = product[14];
> >      > + tmp[5] = product[15];
> >      > + tmp[6] = product[13];
> >      > + tmp[7] = product[8];
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = product[11];
> >      > + tmp[1] = product[12];
> >      > + tmp[2] = product[13];
> >      > + tmp[3] = tmp[4] = tmp[5] = 0;
> >      > + tmp[6] = product[8];
> >      > + tmp[7] = product[10];
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = product[12];
> >      > + tmp[1] = product[13];
> >      > + tmp[2] = product[14];
> >      > + tmp[3] = product[15];
> >      > + tmp[4] = tmp[5] = 0;
> >      > + tmp[6] = product[9];
> >      > + tmp[7] = product[11];
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d3 */
> >      > + tmp[0] = product[13];
> >      > + tmp[1] = product[14];
> >      > + tmp[2] = product[15];
> >      > + tmp[3] = product[8];
> >      > + tmp[4] = product[9];
> >      > + tmp[5] = product[10];
> >      > + tmp[6] = 0;
> >      > + tmp[7] = product[12];
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d4 */
> >      > + tmp[0] = product[14];
> >      > + tmp[1] = product[15];
> >      > + tmp[2] = 0;
> >      > + tmp[3] = product[9];
> >      > + tmp[4] = product[10];
> >      > + tmp[5] = product[11];
> >      > + tmp[6] = 0;
> >      > + tmp[7] = product[13];
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> >      > + num_words_secp256r1) != 1) {
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > + }
> >      > + }
> >      > }
> >      > #else
> >      > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
> >      > - const char *fullpath) {
> >      > - mg_http_reply(c, 501, NULL, "SSI not enabled");
> >      > - (void) root, (void) fullpath;
> >      > +static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t
> >     *product) {
> >      > + uint64_t tmp[num_words_secp256r1];
> >      > + int carry;
> >      > +
> >      > + /* t */
> >      > + mg_uecc_vli_set(result, product, num_words_secp256r1);
> >      > +
> >      > + /* s1 */
> >      > + tmp[0] = 0;
> >      > + tmp[1] = product[5] & 0xffffffff00000000U;
> >      > + tmp[2] = product[6];
> >      > + tmp[3] = product[7];
> >      > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s2 */
> >      > + tmp[1] = product[6] << 32;
> >      > + tmp[2] = (product[6] >> 32) | (product[7] << 32);
> >      > + tmp[3] = product[7] >> 32;
> >      > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
> >     num_words_secp256r1);
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s3 */
> >      > + tmp[0] = product[4];
> >      > + tmp[1] = product[5] & 0xffffffff;
> >      > + tmp[2] = 0;
> >      > + tmp[3] = product[7];
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* s4 */
> >      > + tmp[0] = (product[4] >> 32) | (product[5] << 32);
> >      > + tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
> >      > + tmp[2] = product[7];
> >      > + tmp[3] = (product[6] >> 32) | (product[4] << 32);
> >      > + carry += (int) mg_uecc_vli_add(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d1 */
> >      > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
> >      > + tmp[1] = (product[6] >> 32);
> >      > + tmp[2] = 0;
> >      > + tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d2 */
> >      > + tmp[0] = product[6];
> >      > + tmp[1] = product[7];
> >      > + tmp[2] = 0;
> >      > + tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d3 */
> >      > + tmp[0] = (product[6] >> 32) | (product[7] << 32);
> >      > + tmp[1] = (product[7] >> 32) | (product[4] << 32);
> >      > + tmp[2] = (product[4] >> 32) | (product[5] << 32);
> >      > + tmp[3] = (product[6] << 32);
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + /* d4 */
> >      > + tmp[0] = product[7];
> >      > + tmp[1] = product[4] & 0xffffffff00000000U;
> >      > + tmp[2] = product[5];
> >      > + tmp[3] = product[6] & 0xffffffff00000000U;
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
> >     num_words_secp256r1);
> >      > +
> >      > + if (carry < 0) {
> >      > + do {
> >      > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > + } while (carry < 0);
> >      > + } else {
> >      > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
> >      > + num_words_secp256r1) != 1) {
> >      > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
> >      > + num_words_secp256r1);
> >      > + }
> >      > + }
> >      > }
> >      > -#endif
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
> >     !asm_mmod_fast_secp256r1) */
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/str.c"
> >      > +#endif /* MG_UECC_SUPPORTS_secp256r1 */
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp256k1
> >      > +
> >      > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
> >     mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
> >      > +static void x_side_secp256k1(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *x,
> >      > + MG_UECC_Curve curve);
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product);
> >      > #endif
> >      >
> >      > +static const struct MG_UECC_Curve_t curve_secp256k1 = {
> >      > + num_words_secp256k1,
> >      > + num_bytes_secp256k1,
> >      > + 256, /* num_n_bits */
> >      > + {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
> >      > + BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
> >      > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
> >      > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
> >      > + {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
> >      > + BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
> >      > + BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
> >      > + BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
> >      > +
> >      > + BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
> >      > + BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
> >      > + BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
> >      > + BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
> >      > + {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
> >      > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
> >      > + &double_jacobian_secp256k1,
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > + &mod_sqrt_default,
> >      > +#endif
> >      > + &x_side_secp256k1,
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + &vli_mmod_fast_secp256k1
> >      > +#endif
> >      > +};
> >      >
> >      > -struct mg_str mg_str_s(const char *s) {
> >      > - struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
> >      > - return str;
> >      > +MG_UECC_Curve mg_uecc_secp256k1(void) {
> >      > + return &curve_secp256k1;
> >      > }
> >      >
> >      > -struct mg_str mg_str_n(const char *s, size_t n) {
> >      > - struct mg_str str = {s, n};
> >      > - return str;
> >      > -}
> >      > +/* Double in place */
> >      > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
> >     mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
> >      > + /* t1 = X, t2 = Y, t3 = Z */
> >      > + mg_uecc_word_t t4[num_words_secp256k1];
> >      > + mg_uecc_word_t t5[num_words_secp256k1];
> >      >
> >      > -int mg_lower(const char *s) {
> >      > - int c = *s;
> >      > - if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
> >      > - return c;
> >      > -}
> >      > + if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
> >      > + return;
> >      > + }
> >      >
> >      > -int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
> >      > - int diff = 0;
> >      > - if (len > 0) do {
> >      > - diff = mg_lower(s1++) - mg_lower(s2++);
> >      > - } while (diff == 0 && s1[-1] != '\0' && --len > 0);
> >      > - return diff;
> >      > + mg_uecc_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */
> >      > + mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 =
> >     A */
> >      > + mg_uecc_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */
> >      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */
> >      > + mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
> >      > +
> >      > + mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
> >      > + num_words_secp256k1); /* t2 = 2*x1^2 */
> >      > + mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
> >      > + num_words_secp256k1); /* t2 = 3*x1^2 */
> >      > + if (mg_uecc_vli_testBit(Y1, 0)) {
> >      > + mg_uecc_word_t carry =
> >      > + mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
> >      > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> >      > + Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
> >      > + } else {
> >      > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
> >      > + }
> >      > + /* t2 = 3/2*(x1^2) = B */
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
> >      > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> >      > + num_words_secp256k1); /* t1 = B^2 - A */
> >      > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
> >      > + num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
> >      > +
> >      > + mg_uecc_vli_modSub(t4, t4, X1, curve->p,
> >      > + num_words_secp256k1); /* t4 = A - x3 */
> >      > + mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A -
> >     x3) */
> >      > + mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
> >      > + num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
> >      > +}
> >      > +
> >      > +/* Computes result = x^3 + b. result must not overlap x. */
> >      > +static void x_side_secp256k1(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *x,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
> >      > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
> >      > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
> >      > + num_words_secp256k1); /* r = x^3 + b */
> >      > +}
> >      > +
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
> >      > +static void omega_mult_secp256k1(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *right);
> >      > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
> >      > + mg_uecc_word_t *product) {
> >      > + mg_uecc_word_t tmp[2 * num_words_secp256k1];
> >      > + mg_uecc_word_t carry;
> >      > +
> >      > + mg_uecc_vli_clear(tmp, num_words_secp256k1);
> >      > + mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
> >      > +
> >      > + omega_mult_secp256k1(tmp,
> >      > + product + num_words_secp256k1); /* (Rq, q) = q * c */
> >      > +
> >      > + carry = mg_uecc_vli_add(result, product, tmp,
> >      > + num_words_secp256k1); /* (C, r) = r + q */
> >      > + mg_uecc_vli_clear(product, num_words_secp256k1);
> >      > + omega_mult_secp256k1(product, tmp + num_words_secp256k1); /*
> >     Rq*c */
> >      > + carry += mg_uecc_vli_add(result, result, product,
> >      > + num_words_secp256k1); /* (C1, r) = r + Rq*c */
> >      > +
> >      > + while (carry > 0) {
> >      > + --carry;
> >      > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
> >     num_words_secp256k1);
> >      > + }
> >      > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p,
> >     num_words_secp256k1) >
> >      > + 0) {
> >      > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
> >     num_words_secp256k1);
> >      > + }
> >      > }
> >      >
> >      > -int mg_casecmp(const char *s1, const char *s2) {
> >      > - return mg_ncasecmp(s1, s2, (size_t) ~0);
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +static void omega_mult_secp256k1(uint8_t *result, const uint8_t
> >     *right) {
> >      > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> >      > + mg_uecc_word_t r0 = 0;
> >      > + mg_uecc_word_t r1 = 0;
> >      > + mg_uecc_word_t r2 = 0;
> >      > + wordcount_t k;
> >      > +
> >      > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> >      > + muladd(0xD1, right[0], &r0, &r1, &r2);
> >      > + result[0] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + /* r2 is still 0 */
> >      > +
> >      > + for (k = 1; k < num_words_secp256k1; ++k) {
> >      > + muladd(0x03, right[k - 1], &r0, &r1, &r2);
> >      > + muladd(0xD1, right[k], &r0, &r1, &r2);
> >      > + result[k] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + r2 = 0;
> >      > + }
> >      > + muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
> >      > + result[num_words_secp256k1] = r0;
> >      > + result[num_words_secp256k1 + 1] = r1;
> >      > + /* add the 2^32 multiple */
> >      > + result[4 + num_words_secp256k1] =
> >      > + mg_uecc_vli_add(result + 4, result + 4, right,
> >     num_words_secp256k1);
> >      > +}
> >      > +#elif MG_UECC_WORD_SIZE == 4
> >      > +static void omega_mult_secp256k1(uint32_t *result, const
> >     uint32_t *right) {
> >      > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> >      > + uint32_t carry = 0;
> >      > + wordcount_t k;
> >      > +
> >      > + for (k = 0; k < num_words_secp256k1; ++k) {
> >      > + uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
> >      > + result[k] = (uint32_t) p;
> >      > + carry = p >> 32;
> >      > + }
> >      > + result[num_words_secp256k1] = carry;
> >      > + /* add the 2^32 multiple */
> >      > + result[1 + num_words_secp256k1] =
> >      > + mg_uecc_vli_add(result + 1, result + 1, right,
> >     num_words_secp256k1);
> >      > }
> >      > -
> >      > -int mg_vcmp(const struct mg_str *s1, const char *s2) {
> >      > - size_t n2 = strlen(s2), n1 = s1->len;
> >      > - int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
> >      > - if (r == 0) return (int) (n1 - n2);
> >      > - return r;
> >      > +#else
> >      > +static void omega_mult_secp256k1(uint64_t *result, const
> >     uint64_t *right) {
> >      > + mg_uecc_word_t r0 = 0;
> >      > + mg_uecc_word_t r1 = 0;
> >      > + mg_uecc_word_t r2 = 0;
> >      > + wordcount_t k;
> >      > +
> >      > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
> >      > + for (k = 0; k < num_words_secp256k1; ++k) {
> >      > + muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
> >      > + result[k] = r0;
> >      > + r0 = r1;
> >      > + r1 = r2;
> >      > + r2 = 0;
> >      > + }
> >      > + result[num_words_secp256k1] = r0;
> >      > }
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && &&
> >     !asm_mmod_fast_secp256k1) */
> >      >
> >      > -int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
> >      > - size_t n2 = strlen(str2), n1 = str1->len;
> >      > - int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
> >      > - if (r == 0) return (int) (n1 - n2);
> >      > - return r;
> >      > +#endif /* MG_UECC_SUPPORTS_secp256k1 */
> >      > +
> >      > +#endif /* _UECC_CURVE_SPECIFIC_H_ */
> >      > +
> >      > +/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
> >      > +#define EccPoint_isZero(point, curve) \
> >      > + mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words *
> >     2))
> >      > +
> >      > +/* Point multiplication algorithm using Montgomery's ladder with
> >     co-Z
> >      > +coordinates. From http://eprint.iacr.org/2011/338.pdf
> >     <http://eprint.iacr.org/2011/338.pdf>
> >      > +*/
> >      > +
> >      > +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
> >      > +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> >      > + const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(t1, Z, curve); /* z^2 */
> >      > + mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
> >      > + mg_uecc_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */
> >      > + mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
> >      > }
> >      >
> >      > -struct mg_str mg_strdup(const struct mg_str s) {
> >      > - struct mg_str r = {NULL, 0};
> >      > - if (s.len > 0 && s.ptr != NULL) {
> >      > - char *sc = (char *) calloc(1, s.len + 1);
> >      > - if (sc != NULL) {
> >      > - memcpy(sc, s.ptr, s.len);
> >      > - sc[s.len] = '\0';
> >      > - r.ptr = sc;
> >      > - r.len = s.len;
> >      > - }
> >      > +/* P = (x1, y1) => 2P, (x2, y2) => P' */
> >      > +static void XYcZ_initial_double(mg_uecc_word_t *X1,
> >     mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> >      > + const mg_uecc_word_t *const initial_Z,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> >      > + wordcount_t num_words = curve->num_words;
> >      > + if (initial_Z) {
> >      > + mg_uecc_vli_set(z, initial_Z, num_words);
> >      > + } else {
> >      > + mg_uecc_vli_clear(z, num_words);
> >      > + z[0] = 1;
> >      > }
> >      > - return r;
> >      > -}
> >      >
> >      > -int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
> >      > - size_t i = 0;
> >      > - while (i < str1.len && i < str2.len) {
> >      > - int c1 = str1.ptr[i];
> >      > - int c2 = str2.ptr[i];
> >      > - if (c1 < c2) return -1;
> >      > - if (c1 > c2) return 1;
> >      > - i++;
> >      > + mg_uecc_vli_set(X2, X1, num_words);
> >      > + mg_uecc_vli_set(Y2, Y1, num_words);
> >      > +
> >      > + apply_z(X1, Y1, z, curve);
> >      > + curve->double_jacobian(X1, Y1, z, curve);
> >      > + apply_z(X2, Y2, z, curve);
> >      > +}
> >      > +
> >      > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> >      > + Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
> >      > + or P => P', Q => P + Q
> >      > +*/
> >      > +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> >     mg_uecc_word_t *X2,
> >      > + mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
> >      > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> >      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
> >     - x1 */
> >      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
> >     = A */
> >      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> >      > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> >      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
> >     - y1 */
> >      > + mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2
> >     = D */
> >      > +
> >      > + mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D
> >     - B */
> >      > + mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D
> >     - B - C = x3 */
> >      > + mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C
> >     - B */
> >      > + mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */
> >      > + mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B
> >     - x3 */
> >      > + mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 -
> >     y1)*(B - x3) */
> >      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
> >      > +
> >      > + mg_uecc_vli_set(X2, t5, num_words);
> >      > +}
> >      > +
> >      > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
> >      > + Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
> >      > + or P => P - Q, Q => P + Q
> >      > +*/
> >      > +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
> >      > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
> >      > + MG_UECC_Curve curve) {
> >      > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
> >      > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
> >      > + mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
> >     - x1 */
> >      > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
> >     = A */
> >      > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
> >      > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
> >      > + mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2
> >     + y1 */
> >      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
> >     - y1 */
> >      > +
> >      > + mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C
> >     - B */
> >      > + mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C -
> >     B) = E */
> >      > + mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B
> >     + C */
> >      > + mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2
> >     = D */
> >      > + mg_uecc_vli_modSub(X2, X2, t6, curve->p,
> >      > + num_words); /* t3 = D - (B + C) = x3 */
> >      > +
> >      > + mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B
> >     - x3 */
> >      > + mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 -
> >     y1)*(B - x3) */
> >      > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
> >      > + num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2
> >     = F */
> >      > + mg_uecc_vli_modSub(t7, t7, t6, curve->p,
> >      > + num_words); /* t7 = F - (B + C) = x3' */
> >      > + mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 =
> >     x3' - B */
> >      > + mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 =
> >     (y2+y1)*(x3' - B) */
> >      > + mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
> >      > + num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
> >      > +
> >      > + mg_uecc_vli_set(X1, t7, num_words);
> >      > +}
> >      > +
> >      > +/* result may overlap point. */
> >      > +static void EccPoint_mult(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *point,
> >      > + const mg_uecc_word_t *scalar,
> >      > + const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
> >      > + MG_UECC_Curve curve) {
> >      > + /* R0 and R1 */
> >      > + mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> >      > + bitcount_t i;
> >      > + mg_uecc_word_t nb;
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + mg_uecc_vli_set(Rx[1], point, num_words);
> >      > + mg_uecc_vli_set(Ry[1], point + num_words, num_words);
> >      > +
> >      > + XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
> >      > +
> >      > + for (i = num_bits - 2; i > 0; --i) {
> >      > + nb = !mg_uecc_vli_testBit(scalar, i);
> >      > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> >      > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> >      > + }
> >      > +
> >      > + nb = !mg_uecc_vli_testBit(scalar, 0);
> >      > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
> >      > +
> >      > + /* Find final 1/Z value. */
> >      > + mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1
> >     - X0 */
> >      > + mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1
> >     - X0) */
> >      > + mg_uecc_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1
> >     - X0) */
> >      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb
> >     * (X1 - X0)) */
> >      > + /* yP / (xP * Yb * (X1 - X0)) */
> >      > + mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
> >      > + mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
> >      > + curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
> >      > + /* End 1/Z calculation */
> >      > +
> >      > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
> >      > + apply_z(Rx[0], Ry[0], z, curve);
> >      > +
> >      > + mg_uecc_vli_set(result, Rx[0], num_words);
> >      > + mg_uecc_vli_set(result + num_words, Ry[0], num_words);
> >      > +}
> >      > +
> >      > +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
> >      > + mg_uecc_word_t *k0, mg_uecc_word_t *k1,
> >      > + MG_UECC_Curve curve) {
> >      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> >      > + bitcount_t num_n_bits = curve->num_n_bits;
> >      > + mg_uecc_word_t carry =
> >      > + mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
> >      > + (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE *
> >     8) &&
> >      > + mg_uecc_vli_testBit(k0, num_n_bits));
> >      > + mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
> >      > + return carry;
> >      > +}
> >      > +
> >      > +/* Generates a random integer in the range 0 < random < top.
> >      > + Both random and top have num_words words. */
> >      > +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t
> >     *random,
> >      > + const mg_uecc_word_t *top,
> >      > + wordcount_t num_words) {
> >      > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> >      > + mg_uecc_word_t tries;
> >      > + bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
> >      > +
> >      > + if (!g_rng_function) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> >      > + if (!g_rng_function((uint8_t *) random,
> >      > + (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
> >      > + return 0;
> >      > + }
> >      > + random[num_words - 1] &=
> >      > + mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 -
> >     num_bits));
> >      > + if (!mg_uecc_vli_isZero(random, num_words) &&
> >      > + mg_uecc_vli_cmp(top, random, num_words) == 1) {
> >      > + return 1;
> >      > + }
> >      > }
> >      > - if (i < str1.len) return 1;
> >      > - if (i < str2.len) return -1;
> >      > return 0;
> >      > }
> >      >
> >      > -const char *mg_strstr(const struct mg_str haystack,
> >      > - const struct mg_str needle) {
> >      > - size_t i;
> >      > - if (needle.len > haystack.len) return NULL;
> >      > - for (i = 0; i <= haystack.len - needle.len; i++) {
> >      > - if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
> >      > - return haystack.ptr + i;
> >      > +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t
> >     *result,
> >      > + mg_uecc_word_t *private_key,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> >      > + mg_uecc_word_t *initial_Z = 0;
> >      > + mg_uecc_word_t carry;
> >      > +
> >      > + /* Regularize the bitcount for the private key so that
> >     attackers cannot use a
> >      > + side channel attack to learn the number of leading zeros. */
> >      > + carry = regularize_k(private_key, tmp1, tmp2, curve);
> >      > +
> >      > + /* If an RNG function was specified, try to get a random
> >     initial Z value to
> >      > + improve protection against side-channel attacks. */
> >      > + if (g_rng_function) {
> >      > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
> >     curve->num_words)) {
> >      > + return 0;
> >      > }
> >      > + initial_Z = p2[carry];
> >      > }
> >      > - return NULL;
> >      > + EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
> >      > + (bitcount_t) (curve->num_n_bits + 1), curve);
> >      > +
> >      > + if (EccPoint_isZero(result, curve)) {
> >      > + return 0;
> >      > + }
> >      > + return 1;
> >      > }
> >      >
> >      > -static bool is_space(int c) {
> >      > - return c == ' ' || c == '\r' || c == '\n' || c == '\t';
> >      > +#if MG_UECC_WORD_SIZE == 1
> >      > +
> >      > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
> >     int num_bytes,
> >      > + const uint8_t *native) {
> >      > + wordcount_t i;
> >      > + for (i = 0; i < num_bytes; ++i) {
> >      > + bytes[i] = native[(num_bytes - 1) - i];
> >      > + }
> >      > }
> >      >
> >      > -struct mg_str mg_strstrip(struct mg_str s) {
> >      > - while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
> >      > - while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
> >      > - return s;
> >      > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
> >      > + const uint8_t *bytes,
> >      > + int num_bytes) {
> >      > + mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
> >      > }
> >      >
> >      > -bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
> >     *caps) {
> >      > - size_t i = 0, j = 0, ni = 0, nj = 0;
> >      > - if (caps) caps->ptr = NULL, caps->len = 0;
> >      > - while (i < p.len || j < s.len) {
> >      > - if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] ==
> >     p.ptr[i])) {
> >      > - if (caps == NULL) {
> >      > - } else if (p.ptr[i] == '?') {
> >      > - caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap
> >      > - caps++, caps->ptr = NULL, caps->len = 0; // Init next cap
> >      > - } else if (caps->ptr != NULL && caps->len == 0) {
> >      > - caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize
> >     current cap
> >      > - caps++, caps->len = 0, caps->ptr = NULL; // Init next cap
> >      > - }
> >      > - i++, j++;
> >      > - } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
> >      > - if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];
> >     // Init cap
> >      > - ni = i++, nj = j + 1;
> >      > - } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' ||
> >     s.ptr[j] != '/')) {
> >      > - i = ni, j = nj;
> >      > - if (caps && caps->ptr == NULL && caps->len == 0) {
> >      > - caps--, caps->len = 0; // Restart previous cap
> >      > - }
> >      > - } else {
> >      > - return false;
> >      > - }
> >      > - }
> >      > - if (caps && caps->ptr && caps->len == 0) {
> >      > - caps->len = (size_t) (&s.ptr[j] - caps->ptr);
> >      > +#else
> >      > +
> >      > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
> >     int num_bytes,
> >      > + const mg_uecc_word_t *native) {
> >      > + int i;
> >      > + for (i = 0; i < num_bytes; ++i) {
> >      > + unsigned b = (unsigned) (num_bytes - 1 - i);
> >      > + bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
> >      > + (8 * (b % MG_UECC_WORD_SIZE)));
> >      > }
> >      > - return true;
> >      > }
> >      >
> >      > -bool mg_globmatch(const char *s1, size_t n1, const char *s2,
> >     size_t n2) {
> >      > - return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
> >      > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t
> >     *native,
> >      > + const uint8_t *bytes,
> >      > + int num_bytes) {
> >      > + int i;
> >      > + mg_uecc_vli_clear(native,
> >      > + (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
> >      > + MG_UECC_WORD_SIZE));
> >      > + for (i = 0; i < num_bytes; ++i) {
> >      > + unsigned b = (unsigned) (num_bytes - 1 - i);
> >      > + native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
> >      > + << (8 * (b % MG_UECC_WORD_SIZE));
> >      > + }
> >      > }
> >      >
> >      > -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t
> >     *koff,
> >      > - size_t *klen, size_t *voff, size_t *vlen, char delim) {
> >      > - size_t kvlen, kl;
> >      > - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;)
> >     kvlen++;
> >      > - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
> >      > - if (koff != NULL) *koff = ofs;
> >      > - if (klen != NULL) *klen = kl;
> >      > - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
> >      > - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
> >      > - ofs += kvlen + 1;
> >      > - return ofs > n ? n : ofs;
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +
> >      > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> >      > + MG_UECC_Curve curve) {
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> >      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> >      > +#else
> >      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> >      > +#endif
> >      > + mg_uecc_word_t tries;
> >      > +
> >      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> >      > + if (!mg_uecc_generate_random_int(_private, curve->n,
> >      > + BITS_TO_WORDS(curve->num_n_bits))) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + if (EccPoint_compute_public_key(_public, _private, curve)) {
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_nativeToBytes(private_key,
> >     BITS_TO_BYTES(curve->num_n_bits),
> >      > + _private);
> >      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> >      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
> >     curve->num_bytes,
> >      > + _public + curve->num_words);
> >      > +#endif
> >      > + return 1;
> >      > + }
> >      > + }
> >      > + return 0;
> >      > }
> >      >
> >      > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
> >     *v, char sep) {
> >      > - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
> >      > - if (s->ptr == NULL || s->len == 0) return 0;
> >      > - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
> >      > - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
> >      > - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
> >      > - *s = mg_str_n(s->ptr + off, s->len - off);
> >      > - return off > 0;
> >      > +int mg_uecc_shared_secret(const uint8_t *public_key, const
> >     uint8_t *private_key,
> >      > + uint8_t *secret, MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> >      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> >      > +
> >      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t *p2[2] = {_private, tmp};
> >      > + mg_uecc_word_t *initial_Z = 0;
> >      > + mg_uecc_word_t carry;
> >      > + wordcount_t num_words = curve->num_words;
> >      > + wordcount_t num_bytes = curve->num_bytes;
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) _private, private_key, num_bytes);
> >      > + bcopy((uint8_t *) _public, public_key, num_bytes * 2);
> >      > +#else
> >      > + mg_uecc_vli_bytesToNative(_private, private_key,
> >      > + BITS_TO_BYTES(curve->num_n_bits));
> >      > + mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
> >      > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
> >     num_bytes,
> >      > + num_bytes);
> >      > +#endif
> >      > +
> >      > + /* Regularize the bitcount for the private key so that
> >     attackers cannot use a
> >      > + side channel attack to learn the number of leading zeros. */
> >      > + carry = regularize_k(_private, _private, tmp, curve);
> >      > +
> >      > + /* If an RNG function was specified, try to get a random
> >     initial Z value to
> >      > + improve protection against side-channel attacks. */
> >      > + if (g_rng_function) {
> >      > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
> >     num_words)) {
> >      > + return 0;
> >      > + }
> >      > + initial_Z = p2[carry];
> >      > + }
> >      > +
> >      > + EccPoint_mult(_public, _public, p2[!carry], initial_Z,
> >      > + (bitcount_t) (curve->num_n_bits + 1), curve);
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
> >      > +#else
> >      > + mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
> >      > +#endif
> >      > + return !EccPoint_isZero(_public, curve);
> >      > }
> >      >
> >      > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
> >     mg_str *v) {
> >      > - return mg_split(s, k, v, ',');
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
> >     *compressed,
> >      > + MG_UECC_Curve curve) {
> >      > + wordcount_t i;
> >      > + for (i = 0; i < curve->num_bytes; ++i) {
> >      > + compressed[i + 1] = public_key[i];
> >      > + }
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
> >      > +#else
> >      > + compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
> >      > +#endif
> >      > }
> >      >
> >      > -char *mg_hex(const void *buf, size_t len, char *to) {
> >      > - const unsigned char *p = (const unsigned char *) buf;
> >      > - const char *hex = "0123456789abcdef";
> >      > - size_t i = 0;
> >      > - for (; len--; p++) {
> >      > - to[i++] = hex[p[0] >> 4];
> >      > - to[i++] = hex[p[0] & 0x0f];
> >      > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
> >     *public_key,
> >      > + MG_UECC_Curve curve) {
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
> >      > +#else
> >      > + mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
> >      > +#endif
> >      > + mg_uecc_word_t *y = point + curve->num_words;
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy(public_key, compressed + 1, curve->num_bytes);
> >      > +#else
> >      > + mg_uecc_vli_bytesToNative(point, compressed + 1,
> >     curve->num_bytes);
> >      > +#endif
> >      > + curve->x_side(y, point, curve);
> >      > + curve->mod_sqrt(y, curve);
> >      > +
> >      > + if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
> >      > + mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
> >      > }
> >      > - to[i] = '\0';
> >      > - return to;
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
> >      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
> >     curve->num_bytes, y);
> >      > +#endif
> >      > }
> >      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> >      > +
> >      > +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t
> >     *point,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> >      > + wordcount_t num_words = curve->num_words;
> >      > +
> >      > + /* The point at infinity is invalid. */
> >      > + if (EccPoint_isZero(point, curve)) {
> >      > + return 0;
> >      > + }
> >      >
> >      > -static unsigned char mg_unhex_nimble(unsigned char c) {
> >      > - return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
> >      > - : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
> >      > - : (unsigned char) (c - 'W');
> >      > + /* x and y must be smaller than p. */
> >      > + if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
> >      > + mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words)
> >     != 1) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
> >      > + curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
> >      > +
> >      > + /* Make sure that y^2 == x^3 + ax + b */
> >      > + return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
> >      > }
> >      >
> >      > -unsigned long mg_unhexn(const char *s, size_t len) {
> >      > - unsigned long i = 0, v = 0;
> >      > - for (i = 0; i < len; i++) v <<= 4, v |=
> >     mg_unhex_nimble(((uint8_t *) s)[i]);
> >      > - return v;
> >      > +int mg_uecc_valid_public_key(const uint8_t *public_key,
> >     MG_UECC_Curve curve) {
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> >      > +#else
> >      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> >      > +#endif
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> >      > + mg_uecc_vli_bytesToNative(_public + curve->num_words,
> >      > + public_key + curve->num_bytes, curve->num_bytes);
> >      > +#endif
> >      > + return mg_uecc_valid_point(_public, curve);
> >      > }
> >      >
> >      > -void mg_unhex(const char *buf, size_t len, unsigned char *to) {
> >      > - size_t i;
> >      > - for (i = 0; i < len; i += 2) {
> >      > - to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
> >      > +int mg_uecc_compute_public_key(const uint8_t *private_key,
> >     uint8_t *public_key,
> >      > + MG_UECC_Curve curve) {
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
> >      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> >      > +#else
> >      > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> >      > +#endif
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_bytesToNative(_private, private_key,
> >      > + BITS_TO_BYTES(curve->num_n_bits));
> >      > +#endif
> >      > +
> >      > + /* Make sure the private key is in the range [1, n-1]. */
> >      > + if (mg_uecc_vli_isZero(_private,
> >     BITS_TO_WORDS(curve->num_n_bits))) {
> >      > + return 0;
> >      > }
> >      > -}
> >      >
> >      > -uint64_t mg_tou64(struct mg_str str) {
> >      > - uint64_t result = 0;
> >      > - size_t i = 0;
> >      > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
> >     '\t')) i++;
> >      > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> >      > - result *= 10;
> >      > - result += (unsigned) (str.ptr[i] - '0');
> >      > - i++;
> >      > + if (mg_uecc_vli_cmp(curve->n, _private,
> >     BITS_TO_WORDS(curve->num_n_bits)) !=
> >      > + 1) {
> >      > + return 0;
> >      > }
> >      > - return result;
> >      > +
> >      > + /* Compute public key. */
> >      > + if (!EccPoint_compute_public_key(_public, _private, curve)) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
> >      > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
> >     curve->num_bytes,
> >      > + _public + curve->num_words);
> >      > +#endif
> >      > + return 1;
> >      > }
> >      >
> >      > -int64_t mg_to64(struct mg_str str) {
> >      > - int64_t result = 0, neg = 1, max = 922337203685477570 /*
> >     INT64_MAX/10-10 */;
> >      > - size_t i = 0;
> >      > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
> >     '\t')) i++;
> >      > - if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
> >      > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
> >      > - if (result > max) return 0;
> >      > - result *= 10;
> >      > - result += (str.ptr[i] - '0');
> >      > - i++;
> >      > +/* -------- ECDSA code -------- */
> >      > +
> >      > +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
> >      > + unsigned bits_size, MG_UECC_Curve curve) {
> >      > + unsigned num_n_bytes = (unsigned)
> >     BITS_TO_BYTES(curve->num_n_bits);
> >      > + unsigned num_n_words = (unsigned)
> >     BITS_TO_WORDS(curve->num_n_bits);
> >      > + int shift;
> >      > + mg_uecc_word_t carry;
> >      > + mg_uecc_word_t *ptr;
> >      > +
> >      > + if (bits_size > num_n_bytes) {
> >      > + bits_size = num_n_bytes;
> >      > + }
> >      > +
> >      > + mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) native, bits, bits_size);
> >      > +#else
> >      > + mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
> >      > +#endif
> >      > + if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
> >      > + return;
> >      > + }
> >      > + shift = (int) bits_size * 8 - curve->num_n_bits;
> >      > + carry = 0;
> >      > + ptr = native + num_n_words;
> >      > + while (ptr-- > native) {
> >      > + mg_uecc_word_t temp = *ptr;
> >      > + *ptr = (temp >> shift) | carry;
> >      > + carry = temp << (MG_UECC_WORD_BITS - shift);
> >      > + }
> >      > +
> >      > + /* Reduce mod curve_n */
> >      > + if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t)
> >     num_n_words) !=
> >      > + 1) {
> >      > + mg_uecc_vli_sub(native, native, curve->n, (wordcount_t)
> >     num_n_words);
> >      > }
> >      > - return result * neg;
> >      > }
> >      >
> >      > -char *mg_remove_double_dots(char *s) {
> >      > - char *saved = s, *p = s;
> >      > - while (*s != '\0') {
> >      > - *p++ = *s++;
> >      > - if (s[-1] == '/' || s[-1] == '\\') {
> >      > - while (s[0] != '\0') {
> >      > - if (s[0] == '/' || s[0] == '\\') {
> >      > - s++;
> >      > - } else if (s[0] == '.' && s[1] == '.' &&
> >      > - (s[2] == '/' || s[2] == '\\')) {
> >      > - s += 2;
> >      > - } else {
> >      > - break;
> >      > - }
> >      > - }
> >      > +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
> >      > + const uint8_t *message_hash,
> >      > + unsigned hash_size, mg_uecc_word_t *k,
> >      > + uint8_t *signature,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t s[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t *k2[2] = {tmp, s};
> >      > + mg_uecc_word_t *initial_Z = 0;
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
> >      > +#else
> >      > + mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
> >      > +#endif
> >      > + mg_uecc_word_t carry;
> >      > + wordcount_t num_words = curve->num_words;
> >      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> >      > + bitcount_t num_n_bits = curve->num_n_bits;
> >      > +
> >      > + /* Make sure 0 < k < curve_n */
> >      > + if (mg_uecc_vli_isZero(k, num_words) ||
> >      > + mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + carry = regularize_k(k, tmp, s, curve);
> >      > + /* If an RNG function was specified, try to get a random
> >     initial Z value to
> >      > + improve protection against side-channel attacks. */
> >      > + if (g_rng_function) {
> >      > + if (!mg_uecc_generate_random_int(k2[carry], curve->p,
> >     num_words)) {
> >      > + return 0;
> >      > }
> >      > + initial_Z = k2[carry];
> >      > + }
> >      > + EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
> >      > + (bitcount_t) (num_n_bits + 1), curve);
> >      > + if (mg_uecc_vli_isZero(p, num_words)) {
> >      > + return 0;
> >      > }
> >      > - *p = '\0';
> >      > - return saved;
> >      > -}
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/timer.c"
> >      > + /* If an RNG function was specified, get a random number
> >      > + to prevent side channel analysis of k. */
> >      > + if (!g_rng_function) {
> >      > + mg_uecc_vli_clear(tmp, num_n_words);
> >      > + tmp[0] = 1;
> >      > + } else if (!mg_uecc_generate_random_int(tmp, curve->n,
> >     num_n_words)) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + /* Prevent side channel analysis of mg_uecc_vli_modInv() to
> >     determine
> >      > + bits of k / the private key by premultiplying by a random
> >     number */
> >      > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' =
> >     rand * k */
> >      > + mg_uecc_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */
> >      > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1
> >     / k */
> >      > +
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
> >      > + mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /*
> >     store r */
> >      > #endif
> >      >
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) tmp, private_key,
> >     BITS_TO_BYTES(curve->num_n_bits));
> >      > +#else
> >      > + mg_uecc_vli_bytesToNative(tmp, private_key,
> >      > + BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
> >      > +#endif
> >      >
> >      > + s[num_n_words - 1] = 0;
> >      > + mg_uecc_vli_set(s, p, num_words);
> >      > + mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s =
> >     r*d */
> >      >
> >      > -#define MG_TIMER_CALLED 4
> >      > + bits2int(tmp, message_hash, hash_size, curve);
> >      > + mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e
> >     + r*d */
> >      > + mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e
> >     + r*d) / k */
> >      > + if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t)
> >     curve->num_bytes * 8) {
> >      > + return 0;
> >      > + }
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
> >      > + curve->num_bytes);
> >      > +#else
> >      > + mg_uecc_vli_nativeToBytes(signature + curve->num_bytes,
> >     curve->num_bytes, s);
> >      > +#endif
> >      > + return 1;
> >      > +}
> >      >
> >      > -void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
> >     uint64_t ms,
> >      > - unsigned flags, void (*fn)(void *), void *arg) {
> >      > - t->id = 0, t->period_ms = ms, t->expire = 0;
> >      > - t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
> >      > - *head = t;
> >      > +#if 0
> >      > +/* For testing - sign with an explicitly specified k value */
> >      > +int mg_uecc_sign_with_k(const uint8_t *private_key, const
> >     uint8_t *message_hash,
> >      > + unsigned hash_size, const uint8_t *k, uint8_t *signature,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
> >      > + bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits),
> >     curve);
> >      > + return mg_uecc_sign_with_k_internal(private_key, message_hash,
> >     hash_size, k2,
> >      > + signature, curve);
> >      > }
> >      > +#endif
> >      >
> >      > -void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
> >      > - while (*head && *head != t) head = &(*head)->next;
> >      > - if (*head) *head = t->next;
> >      > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
> >     *message_hash,
> >      > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t k[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tries;
> >      > +
> >      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> >      > + if (!mg_uecc_generate_random_int(k, curve->n,
> >      > + BITS_TO_WORDS(curve->num_n_bits))) {
> >      > + return 0;
> >      > + }
> >      > +
> >      > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
> >     hash_size, k,
> >      > + signature, curve)) {
> >      > + return 1;
> >      > + }
> >      > + }
> >      > + return 0;
> >      > }
> >      >
> >      > -// t: expiration time, prd: period, now: current time. Return
> >     true if expired
> >      > -bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
> >      > - if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
> >      > - if (*t == 0) *t = now + prd; // Firt poll? Set expiration
> >      > - if (*t > now) return false; // Not expired yet, return
> >      > - *t = (now - *t) > prd ? now + prd : *t + prd; // Next
> >     expiration time
> >      > - return true; // Expired, return true
> >      > +/* Compute an HMAC using K as a key (as in RFC 6979). Note that
> >     K is always
> >      > + the same size as the hash result size. */
> >      > +static void HMAC_init(const MG_UECC_HashContext *hash_context,
> >      > + const uint8_t *K) {
> >      > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> >      > + unsigned i;
> >      > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
> >     0x36;
> >      > + for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
> >      > +
> >      > + hash_context->init_hash(hash_context);
> >      > + hash_context->update_hash(hash_context, pad,
> >     hash_context->block_size);
> >      > }
> >      >
> >      > -void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
> >      > - struct mg_timer *t, *tmp;
> >      > - for (t = *head; t != NULL; t = tmp) {
> >      > - bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
> >      > - !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
> >      > - bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
> >      > - tmp = t->next;
> >      > - if (!once && !expired) continue;
> >      > - if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
> >     MG_TIMER_CALLED)) {
> >      > - t->fn(t->arg);
> >      > +static void HMAC_update(const MG_UECC_HashContext *hash_context,
> >      > + const uint8_t *message, unsigned message_size) {
> >      > + hash_context->update_hash(hash_context, message, message_size);
> >      > +}
> >      > +
> >      > +static void HMAC_finish(const MG_UECC_HashContext *hash_context,
> >      > + const uint8_t *K, uint8_t *result) {
> >      > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
> >      > + unsigned i;
> >      > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
> >     0x5c;
> >      > + for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
> >      > +
> >      > + hash_context->finish_hash(hash_context, result);
> >      > +
> >      > + hash_context->init_hash(hash_context);
> >      > + hash_context->update_hash(hash_context, pad,
> >     hash_context->block_size);
> >      > + hash_context->update_hash(hash_context, result,
> >     hash_context->result_size);
> >      > + hash_context->finish_hash(hash_context, result);
> >      > +}
> >      > +
> >      > +/* V = HMAC_K(V) */
> >      > +static void update_V(const MG_UECC_HashContext *hash_context,
> >     uint8_t *K,
> >      > + uint8_t *V) {
> >      > + HMAC_init(hash_context, K);
> >      > + HMAC_update(hash_context, V, hash_context->result_size);
> >      > + HMAC_finish(hash_context, K, V);
> >      > +}
> >      > +
> >      > +/* Deterministic signing, similar to RFC 6979. Differences are:
> >      > + * We just use H(m) directly rather than bits2octets(H(m))
> >      > + (it is not reduced modulo curve_n).
> >      > + * We generate a value for k (aka T) directly rather than
> >     converting
> >      > + endianness.
> >      > +
> >      > + Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped
> >     0x00 or 0x01) /
> >      > + <HMAC pad> */
> >      > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> >      > + const uint8_t *message_hash, unsigned hash_size,
> >      > + const MG_UECC_HashContext *hash_context,
> >      > + uint8_t *signature, MG_UECC_Curve curve) {
> >      > + uint8_t *K = hash_context->tmp;
> >      > + uint8_t *V = K + hash_context->result_size;
> >      > + wordcount_t num_bytes = curve->num_bytes;
> >      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> >      > + bitcount_t num_n_bits = curve->num_n_bits;
> >      > + mg_uecc_word_t tries;
> >      > + unsigned i;
> >      > + for (i = 0; i < hash_context->result_size; ++i) {
> >      > + V[i] = 0x01;
> >      > + K[i] = 0;
> >      > + }
> >      > +
> >      > + /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
> >      > + HMAC_init(hash_context, K);
> >      > + V[hash_context->result_size] = 0x00;
> >      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> >      > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> >      > + HMAC_update(hash_context, message_hash, hash_size);
> >      > + HMAC_finish(hash_context, K, K);
> >      > +
> >      > + update_V(hash_context, K, V);
> >      > +
> >      > + /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
> >      > + HMAC_init(hash_context, K);
> >      > + V[hash_context->result_size] = 0x01;
> >      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> >      > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
> >      > + HMAC_update(hash_context, message_hash, hash_size);
> >      > + HMAC_finish(hash_context, K, K);
> >      > +
> >      > + update_V(hash_context, K, V);
> >      > +
> >      > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
> >      > + mg_uecc_word_t T[MG_UECC_MAX_WORDS];
> >      > + uint8_t *T_ptr = (uint8_t *) T;
> >      > + wordcount_t T_bytes = 0;
> >      > + for (;;) {
> >      > + update_V(hash_context, K, V);
> >      > + for (i = 0; i < hash_context->result_size; ++i) {
> >      > + T_ptr[T_bytes++] = V[i];
> >      > + if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
> >      > + goto filled;
> >      > + }
> >      > + }
> >      > + }
> >      > + filled:
> >      > + if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 >
> >     num_n_bits) {
> >      > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
> >      > + T[num_n_words - 1] &=
> >      > + mask >>
> >      > + ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
> >      > + }
> >      > +
> >      > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
> >     hash_size, T,
> >      > + signature, curve)) {
> >      > + return 1;
> >      > }
> >      > - t->flags |= MG_TIMER_CALLED;
> >      > +
> >      > + /* K = HMAC_K(V || 0x00) */
> >      > + HMAC_init(hash_context, K);
> >      > + V[hash_context->result_size] = 0x00;
> >      > + HMAC_update(hash_context, V, hash_context->result_size + 1);
> >      > + HMAC_finish(hash_context, K, K);
> >      > +
> >      > + update_V(hash_context, K, V);
> >      > }
> >      > + return 0;
> >      > }
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/tls_dummy.c"
> >      > +static bitcount_t smax(bitcount_t a, bitcount_t b) {
> >      > + return (a > b ? a : b);
> >      > +}
> >      > +
> >      > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
> >     *message_hash,
> >      > + unsigned hash_size, const uint8_t *signature,
> >      > + MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
> >      > + mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
> >      > + const mg_uecc_word_t *points[4];
> >      > + const mg_uecc_word_t *point;
> >      > + bitcount_t num_bits;
> >      > + bitcount_t i;
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
> >      > +#else
> >      > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
> >      > #endif
> >      > + mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
> >      > + wordcount_t num_words = curve->num_words;
> >      > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
> >      >
> >      > + rx[num_n_words - 1] = 0;
> >      > + r[num_n_words - 1] = 0;
> >      > + s[num_n_words - 1] = 0;
> >      >
> >      > -#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL &&
> >     !MG_ENABLE_CUSTOM_TLS
> >      > -void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > - (void) opts;
> >      > - mg_error(c, "TLS is not enabled");
> >      > -}
> >      > -void mg_tls_handshake(struct mg_connection *c) {
> >      > - (void) c;
> >      > -}
> >      > -void mg_tls_free(struct mg_connection *c) {
> >      > - (void) c;
> >      > -}
> >      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> >      > -}
> >      > -long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
> >      > -}
> >      > -size_t mg_tls_pending(struct mg_connection *c) {
> >      > - (void) c;
> >      > - return 0;
> >      > -}
> >      > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > + bcopy((uint8_t *) r, signature, curve->num_bytes);
> >      > + bcopy((uint8_t *) s, signature + curve->num_bytes,
> >     curve->num_bytes);
> >      > +#else
> >      > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
> >      > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
> >     curve->num_bytes,
> >      > + curve->num_bytes);
> >      > + mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
> >      > + mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes,
> >     curve->num_bytes);
> >      > #endif
> >      >
> >      > -#ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/tls_mbed.c"
> >      > -#endif
> >      > + /* r, s must not be 0. */
> >      > + if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s,
> >     num_words)) {
> >      > + return 0;
> >      > + }
> >      >
> >      > + /* r, s must be < n. */
> >      > + if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
> >      > + mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
> >      > + return 0;
> >      > + }
> >      >
> >      > + /* Calculate u1 and u2. */
> >      > + mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
> >      > + u1[num_n_words - 1] = 0;
> >      > + bits2int(u1, message_hash, hash_size, curve);
> >      > + mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 =
> >     e/s */
> >      > + mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 =
> >     r/s */
> >      > +
> >      > + /* Calculate sum = G + Q. */
> >      > + mg_uecc_vli_set(sum, _public, num_words);
> >      > + mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
> >      > + mg_uecc_vli_set(tx, curve->G, num_words);
> >      > + mg_uecc_vli_set(ty, curve->G + num_words, num_words);
> >      > + mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2
> >     - x1 */
> >      > + XYcZ_add(tx, ty, sum, sum + num_words, curve);
> >      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
> >      > + apply_z(sum, sum + num_words, z, curve);
> >      > +
> >      > + /* Use Shamir's trick to calculate u1*G + u2*Q */
> >      > + points[0] = 0;
> >      > + points[1] = curve->G;
> >      > + points[2] = _public;
> >      > + points[3] = sum;
> >      > + num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
> >      > + mg_uecc_vli_numBits(u2, num_n_words));
> >      > + point =
> >      > + points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
> >      > + ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
> >      > + mg_uecc_vli_set(rx, point, num_words);
> >      > + mg_uecc_vli_set(ry, point + num_words, num_words);
> >      > + mg_uecc_vli_clear(z, num_words);
> >      > + z[0] = 1;
> >      > +
> >      > + for (i = num_bits - 2; i >= 0; --i) {
> >      > + mg_uecc_word_t index;
> >      > + curve->double_jacobian(rx, ry, z, curve);
> >      > +
> >      > + index = (!!mg_uecc_vli_testBit(u1, i)) |
> >      > + (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
> >      > + point = points[index];
> >      > + if (point) {
> >      > + mg_uecc_vli_set(tx, point, num_words);
> >      > + mg_uecc_vli_set(ty, point + num_words, num_words);
> >      > + apply_z(tx, ty, z, curve);
> >      > + mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2
> >     - x1 */
> >      > + XYcZ_add(tx, ty, rx, ry, curve);
> >      > + mg_uecc_vli_modMult_fast(z, z, tz, curve);
> >      > + }
> >      > + }
> >      >
> >      > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
> >      > + apply_z(rx, ry, z, curve);
> >      >
> >      > -#if MG_ENABLE_MBEDTLS
> >      > + /* v = x1 (mod n) */
> >      > + if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
> >      > + mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
> >      > + }
> >      >
> >      > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
> >     0x03000000
> >      > -#define MGRNG , rng_get, NULL
> >      > -#else
> >      > -#define MGRNG
> >      > -#endif
> >      > + /* Accept only if v == r. */
> >      > + return (int) (mg_uecc_vli_equal(rx, r, num_words));
> >      > +}
> >      >
> >      > -void mg_tls_free(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - if (tls != NULL) {
> >      > - free(tls->cafile);
> >      > - mbedtls_ssl_free(&tls->ssl);
> >      > - mbedtls_pk_free(&tls->pk);
> >      > - mbedtls_x509_crt_free(&tls->ca);
> >      > - mbedtls_x509_crt_free(&tls->cert);
> >      > - mbedtls_ssl_config_free(&tls->conf);
> >      > - free(tls);
> >      > - c->tls = NULL;
> >      > - }
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +
> >      > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
> >      > + return curve->num_words;
> >      > }
> >      >
> >      > -static int mg_net_send(void *ctx, const unsigned char *buf,
> >     size_t len) {
> >      > - long n = mg_io_send((struct mg_connection *) ctx, buf, len);
> >      > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> >      > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> >      > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> >      > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
> >      > - return (int) n;
> >      > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
> >      > + return curve->num_bytes;
> >      > }
> >      >
> >      > -static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
> >      > - long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
> >      > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
> >      > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
> >      > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
> >      > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
> >      > - return (int) n;
> >      > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
> >      > + return curve->num_bytes * 8;
> >      > }
> >      >
> >      > -void mg_tls_handshake(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - int rc = mbedtls_ssl_handshake(&tls->ssl);
> >      > - if (rc == 0) { // Success
> >      > - MG_DEBUG(("%lu success", c->id));
> >      > - c->is_tls_hs = 0;
> >      > - mg_call(c, MG_EV_TLS_HS, NULL);
> >      > - } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
> >      > - rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
> >      > - MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
> >     c->is_connecting,
> >      > - c->is_tls_hs, rc, -rc));
> >      > - } else {
> >      > - mg_error(c, "TLS handshake: -%#x", -rc); // Error
> >      > - }
> >      > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
> >      > + return BITS_TO_WORDS(curve->num_n_bits);
> >      > }
> >      >
> >      > -static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
> >      > - mg_random(buf, len);
> >      > - (void) ctx;
> >      > - return 0;
> >      > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
> >      > + return BITS_TO_BYTES(curve->num_n_bits);
> >      > }
> >      >
> >      > -static void debug_cb(void *c, int lev, const char *s, int n,
> >     const char *s2) {
> >      > - n = (int) strlen(s2) - 1;
> >      > - MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id,
> >     lev, n, s2));
> >      > - (void) s;
> >      > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
> >      > + return curve->num_n_bits;
> >      > }
> >      >
> >      > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
> >     0x03000000
> >      > -static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
> >      > - (void) p_rng;
> >      > - mg_random(buf, len);
> >      > - return 0;
> >      > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
> >      > + return curve->p;
> >      > }
> >      > -#endif
> >      >
> >      > -static struct mg_str mg_loadfile(struct mg_fs *fs, const char
> >     *path) {
> >      > - size_t n = 0;
> >      > - if (path[0] == '-') return mg_str(path);
> >      > - char *p = mg_file_read(fs, path, &n);
> >      > - return mg_str_n(p, n);
> >      > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
> >      > + return curve->n;
> >      > }
> >      >
> >      > -void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
> >      > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> >      > - int rc = 0;
> >      > - c->tls = tls;
> >      > - if (c->tls == NULL) {
> >      > - mg_error(c, "TLS OOM");
> >      > - goto fail;
> >      > - }
> >      > - MG_DEBUG(("%lu Setting TLS", c->id));
> >      > - mbedtls_ssl_init(&tls->ssl);
> >      > - mbedtls_ssl_config_init(&tls->conf);
> >      > - mbedtls_x509_crt_init(&tls->ca);
> >      > - mbedtls_x509_crt_init(&tls->cert);
> >      > - mbedtls_pk_init(&tls->pk);
> >      > - mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
> >      > -#if defined(MG_MBEDTLS_DEBUG_LEVEL)
> >      > - mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
> >      > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
> >      > + return curve->G;
> >      > +}
> >      > +
> >      > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
> >      > + return curve->b;
> >      > +}
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
> >      > + curve->mod_sqrt(a, curve);
> >      > +}
> >      > #endif
> >      > - if ((rc = mbedtls_ssl_config_defaults(
> >      > - &tls->conf,
> >      > - c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
> >      > - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
> >      > - mg_error(c, "tls defaults %#x", -rc);
> >      > - goto fail;
> >      > - }
> >      > - mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
> >      > - if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
> >      > - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
> >      > - } else if (opts->ca != NULL && opts->ca[0] != '\0') {
> >      > -#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
> >      > - tls->cafile = strdup(opts->ca);
> >      > - rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile,
> >     NULL);
> >      > - if (rc != 0) {
> >      > - mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
> >      > - goto fail;
> >      > - }
> >      > +
> >      > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
> >     mg_uecc_word_t *product,
> >      > + MG_UECC_Curve curve) {
> >      > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
> >      > + curve->mmod_fast(result, product);
> >      > #else
> >      > - struct mg_str s = mg_loadfile(fs, opts->ca);
> >      > - rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len
> >     + 1);
> >      > - if (opts->ca[0] != '-') free((char *) s.ptr);
> >      > - if (rc != 0) {
> >      > - mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
> >      > - goto fail;
> >      > - }
> >      > - mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
> >      > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
> >      > #endif
> >      > - if (opts->srvname.len > 0) {
> >      > - char *x = mg_mprintf("%.*s", (int) opts->srvname.len,
> >     opts->srvname.ptr);
> >      > - mbedtls_ssl_set_hostname(&tls->ssl, x);
> >      > - free(x);
> >      > - }
> >      > - mbedtls_ssl_conf_authmode(&tls->conf,
> >     MBEDTLS_SSL_VERIFY_REQUIRED);
> >      > - }
> >      > - if (opts->cert != NULL && opts->cert[0] != '\0') {
> >      > - struct mg_str s = mg_loadfile(fs, opts->cert);
> >      > - const char *key = opts->certkey == NULL ? opts->cert :
> >     opts->certkey;
> >      > - rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr,
> >     s.len + 1);
> >      > - if (opts->cert[0] != '-') free((char *) s.ptr);
> >      > - if (rc != 0) {
> >      > - mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
> >      > - goto fail;
> >      > - }
> >      > - s = mg_loadfile(fs, key);
> >      > - rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len +
> >     1, NULL,
> >      > - 0 MGRNG);
> >      > - if (key[0] != '-') free((char *) s.ptr);
> >      > - if (rc != 0) {
> >      > - mg_error(c, "tls key(%s) %#x", key, -rc);
> >      > - goto fail;
> >      > - }
> >      > - rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
> >      > - if (rc != 0) {
> >      > - mg_error(c, "own cert %#x", -rc);
> >      > - goto fail;
> >      > - }
> >      > - }
> >      > - if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
> >      > - mg_error(c, "setup err %#x", -rc);
> >      > - goto fail;
> >      > - }
> >      > - c->tls = tls;
> >      > - c->is_tls = 1;
> >      > - c->is_tls_hs = 1;
> >      > - mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
> >      > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
> >     0) {
> >      > - mg_tls_handshake(c);
> >      > - }
> >      > - return;
> >      > -fail:
> >      > - mg_tls_free(c);
> >      > }
> >      >
> >      > -size_t mg_tls_pending(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
> >      > -}
> >      > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *point,
> >      > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
> >      > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
> >      > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
> >      > + mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
> >      >
> >      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
> >      > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
> >     MBEDTLS_ERR_SSL_WANT_WRITE)
> >      > - return MG_IO_WAIT;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > + EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits +
> >     1, curve);
> >      > }
> >      >
> >      > -long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
> >      > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
> >     MBEDTLS_ERR_SSL_WANT_WRITE)
> >      > - return MG_IO_WAIT;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > -}
> >      > -#endif
> >      > +#endif /* MG_UECC_ENABLE_VLI_API */
> >      > +#endif // MG_TLS_BUILTIN
> >      > +// End of uecc BSD-2
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "src/tls_openssl.c"
> >      > +#line 1 "src/tls_x25519.c"
> >      > #endif
> >      > +/**
> >      > + * Adapted from STROBE: https://strobe.sourceforge.io/
> >     <https://strobe.sourceforge.io/>
> >      > + * Copyright (c) 2015-2016 Cryptography Research, Inc.
> >      > + * Author: Mike Hamburg
> >      > + * License: MIT License
> >      > + */
> >      >
> >      >
> >      >
> >      > -#if MG_ENABLE_OPENSSL
> >      > -static int mg_tls_err(struct mg_tls *tls, int res) {
> >      > - int err = SSL_get_error(tls->ssl, res);
> >      > - // We've just fetched the last error from the queue.
> >      > - // Now we need to clear the error queue. If we do not, then the
> >     following
> >      > - // can happen (actually reported):
> >      > - // - A new connection is accept()-ed with cert error (e.g.
> >     self-signed cert)
> >      > - // - Since all accept()-ed connections share listener's context,
> >      > - // - *ALL* SSL accepted connection report read error on the
> >     next poll cycle.
> >      > - // Thus a single errored connection can close all the rest,
> >     unrelated ones.
> >      > - // Clearing the error keeps the shared SSL_CTX in an OK state.
> >      > +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
> >      >
> >      > - if (err != 0) ERR_print_errors_fp(stderr);
> >      > - ERR_clear_error();
> >      > - if (err == SSL_ERROR_WANT_READ) return 0;
> >      > - if (err == SSL_ERROR_WANT_WRITE) return 0;
> >      > - return err;
> >      > -}
> >      > +#define X25519_WBITS 32
> >      >
> >      > -void mg_tls_init(struct mg_connection *c, const struct
> >     mg_tls_opts *opts) {
> >      > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
> >      > - const char *id = "mongoose";
> >      > - static unsigned char s_initialised = 0;
> >      > - int rc;
> >      > +typedef uint32_t limb_t;
> >      > +typedef uint64_t dlimb_t;
> >      > +typedef int64_t sdlimb_t;
> >      >
> >      > - if (tls == NULL) {
> >      > - mg_error(c, "TLS OOM");
> >      > - goto fail;
> >      > - }
> >      > +#define NLIMBS (256 / X25519_WBITS)
> >      > +typedef limb_t mg_fe[NLIMBS];
> >      >
> >      > - if (!s_initialised) {
> >      > - SSL_library_init();
> >      > - s_initialised++;
> >      > - }
> >      > - MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
> >      > - opts->ca == NULL ? "null" : opts->ca,
> >      > - opts->cert == NULL ? "null" : opts->cert,
> >      > - opts->certkey == NULL ? "null" : opts->certkey));
> >      > - tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
> >      > - : SSL_CTX_new(SSLv23_server_method());
> >      > - if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
> >      > - mg_error(c, "SSL_new");
> >      > - goto fail;
> >      > - }
> >      > - SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
> >      > - (unsigned) strlen(id));
> >      > - // Disable deprecated protocols
> >      > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
> >      > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
> >      > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
> >      > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
> >      > -#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
> >      > - SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
> >      > -#endif
> >      > -#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
> >      > - SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
> >      > -#endif
> >      > +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand,
> >     limb_t mier) {
> >      > + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
> >      > + *carry = (limb_t) (tmp >> X25519_WBITS);
> >      > + return (limb_t) tmp;
> >      > +}
> >      >
> >      > - if (opts->ca != NULL && opts->ca[0] != '\0') {
> >      > - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
> >     SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
> >      > - NULL);
> >      > - if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca,
> >     NULL)) != 1) {
> >      > - mg_error(c, "load('%s') %d err %d", opts->ca, rc,
> >     mg_tls_err(tls, rc));
> >      > - goto fail;
> >      > - }
> >      > - }
> >      > - if (opts->cert != NULL && opts->cert[0] != '\0') {
> >      > - const char *key = opts->certkey;
> >      > - if (key == NULL) key = opts->cert;
> >      > - if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) !=
> >     1) {
> >      > - mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
> >      > - goto fail;
> >      > - } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) !=
> >     1) {
> >      > - mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
> >      > - goto fail;
> >      > -#if OPENSSL_VERSION_NUMBER > 0x10100000L
> >      > - } else if ((rc = SSL_use_certificate_chain_file(tls->ssl,
> >     opts->cert)) !=
> >      > - 1) {
> >      > - mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
> >      > - goto fail;
> >      > -#endif
> >      > - } else {
> >      > - SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
> >      > -#if OPENSSL_VERSION_NUMBER > 0x10002000L
> >      > - SSL_set_ecdh_auto(tls->ssl, 1);
> >      > -#endif
> >      > - }
> >      > - }
> >      > - if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl,
> >     opts->ciphers);
> >      > -#if OPENSSL_VERSION_NUMBER >= 0x10100000L
> >      > - if (opts->srvname.len > 0) {
> >      > - char *s = mg_mprintf("%.*s", (int) opts->srvname.len,
> >     opts->srvname.ptr);
> >      > - SSL_set1_host(tls->ssl, s);
> >      > - free(s);
> >      > - }
> >      > -#endif
> >      > - c->tls = tls;
> >      > - c->is_tls = 1;
> >      > - c->is_tls_hs = 1;
> >      > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
> >     0) {
> >      > - mg_tls_handshake(c);
> >      > - }
> >      > - MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
> >     "client"));
> >      > - return;
> >      > -fail:
> >      > - c->is_closing = 1;
> >      > - free(tls);
> >      > +// These functions are implemented in terms of umaal on ARM
> >      > +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
> >      > + dlimb_t total = (dlimb_t) *carry + acc + mand;
> >      > + *carry = (limb_t) (total >> X25519_WBITS);
> >      > + return (limb_t) total;
> >      > +}
> >      > +
> >      > +static limb_t adc0(limb_t *carry, limb_t acc) {
> >      > + dlimb_t total = (dlimb_t) *carry + acc;
> >      > + *carry = (limb_t) (total >> X25519_WBITS);
> >      > + return (limb_t) total;
> >      > +}
> >      > +
> >      > +// - Precondition: carry is small.
> >      > +// - Invariant: result of propagate is < 2^255 + 1 word
> >      > +// - In particular, always less than 2p.
> >      > +// - Also, output x >= min(x,19)
> >      > +static void propagate(mg_fe x, limb_t over) {
> >      > + unsigned i;
> >      > + limb_t carry;
> >      > + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
> >      > + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
> >      > +
> >      > + carry = over * 19;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + x[i] = adc0(&carry, x[i]);
> >      > + }
> >      > }
> >      >
> >      > -void mg_tls_handshake(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - int rc;
> >      > - SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
> >      > - rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
> >      > - if (rc == 1) {
> >      > - MG_DEBUG(("%lu success", c->id));
> >      > - c->is_tls_hs = 0;
> >      > - mg_call(c, MG_EV_TLS_HS, NULL);
> >      > - } else {
> >      > - int code = mg_tls_err(tls, rc);
> >      > - if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
> >      > +static void add(mg_fe out, const mg_fe a, const mg_fe b) {
> >      > + unsigned i;
> >      > + limb_t carry = 0;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + out[i] = adc(&carry, a[i], b[i]);
> >      > }
> >      > + propagate(out, carry);
> >      > }
> >      >
> >      > -void mg_tls_free(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - if (tls == NULL) return;
> >      > - SSL_free(tls->ssl);
> >      > - SSL_CTX_free(tls->ctx);
> >      > - free(tls);
> >      > - c->tls = NULL;
> >      > +static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
> >      > + unsigned i;
> >      > + sdlimb_t carry = -38;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + carry = carry + a[i] - b[i];
> >      > + out[i] = (limb_t) carry;
> >      > + carry >>= X25519_WBITS;
> >      > + }
> >      > + propagate(out, (limb_t) (1 + carry));
> >      > +}
> >      > +
> >      > +// `b` can contain less than 8 limbs, thus we use `limb_t *`
> >     instead of `mg_fe`
> >      > +// to avoid build warnings
> >      > +static void mul(mg_fe out, const mg_fe a, const limb_t *b,
> >     unsigned nb) {
> >      > + limb_t accum[2 * NLIMBS] = {0};
> >      > + unsigned i, j;
> >      > +
> >      > + limb_t carry2;
> >      > + for (i = 0; i < nb; i++) {
> >      > + limb_t mand = b[i];
> >      > + carry2 = 0;
> >      > + for (j = 0; j < NLIMBS; j++) {
> >      > + limb_t tmp; // "a" may be misaligned
> >      > + memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy
> >      > + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
> >      > + }
> >      > + accum[i + j] = carry2;
> >      > + }
> >      > +
> >      > + carry2 = 0;
> >      > + for (j = 0; j < NLIMBS; j++) {
> >      > + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
> >      > + }
> >      > + propagate(out, carry2);
> >      > }
> >      >
> >      > -size_t mg_tls_pending(struct mg_connection *c) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
> >      > +static void sqr(mg_fe out, const mg_fe a) {
> >      > + mul(out, a, a, NLIMBS);
> >      > +}
> >      > +static void mul1(mg_fe out, const mg_fe a) {
> >      > + mul(out, a, out, NLIMBS);
> >      > +}
> >      > +static void sqr1(mg_fe a) {
> >      > + mul1(a, a);
> >      > }
> >      >
> >      > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - int n = SSL_read(tls->ssl, buf, (int) len);
> >      > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
> >      > + limb_t doswap) {
> >      > + unsigned i;
> >      > + for (i = 0; i < 2 * NLIMBS; i++) {
> >      > + limb_t xor_ab = (a[i] ^ b[i]) & doswap;
> >      > + a[i] ^= xor_ab;
> >      > + b[i] ^= xor_ab;
> >      > + }
> >      > }
> >      >
> >      > -long mg_tls_send(struct mg_connection *c, const void *buf,
> >     size_t len) {
> >      > - struct mg_tls *tls = (struct mg_tls *) c->tls;
> >      > - int n = SSL_write(tls->ssl, buf, (int) len);
> >      > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
> >      > - if (n <= 0) return MG_IO_ERR;
> >      > - return n;
> >      > +// Canonicalize a field element x, reducing it to the least
> >     residue which is
> >      > +// congruent to it mod 2^255-19
> >      > +// - Precondition: x < 2^255 + 1 word
> >      > +static limb_t canon(mg_fe x) {
> >      > + // First, add 19.
> >      > + unsigned i;
> >      > + limb_t carry0 = 19;
> >      > + limb_t res;
> >      > + sdlimb_t carry;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + x[i] = adc0(&carry0, x[i]);
> >      > + }
> >      > + propagate(x, carry0);
> >      > +
> >      > + // Here, 19 <= x2 < 2^255
> >      > + // - This is because we added 19, so before propagate it can't
> >     be less
> >      > + // than 19. After propagate, it still can't be less than 19,
> >     because if
> >      > + // propagate does anything it adds 19.
> >      > + // - We know that the high bit must be clear, because either
> >     the input was ~
> >      > + // 2^255 + one word + 19 (in which case it propagates to at
> >     most 2 words) or
> >      > + // it was < 2^255. So now, if we subtract 19, we will get back
> >     to something in
> >      > + // [0,2^255-19).
> >      > + carry = -19;
> >      > + res = 0;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + carry += x[i];
> >      > + res |= x[i] = (limb_t) carry;
> >      > + carry >>= X25519_WBITS;
> >      > + }
> >      > + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
> >      > +}
> >      > +
> >      > +static const limb_t a24[1] = {121665};
> >      > +
> >      > +static void ladder_part1(mg_fe xs[5]) {
> >      > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
> >     = xs[4];
> >      > + add(t1, x2, z2); // t1 = A
> >      > + sub(z2, x2, z2); // z2 = B
> >      > + add(x2, x3, z3); // x2 = C
> >      > + sub(z3, x3, z3); // z3 = D
> >      > + mul1(z3, t1); // z3 = DA
> >      > + mul1(x2, z2); // x3 = BC
> >      > + add(x3, z3, x2); // x3 = DA+CB
> >      > + sub(z3, z3, x2); // z3 = DA-CB
> >      > + sqr1(t1); // t1 = AA
> >      > + sqr1(z2); // z2 = BB
> >      > + sub(x2, t1, z2); // x2 = E = AA-BB
> >      > + mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24
> >      > + add(z2, z2, t1); // z2 = E*a24 + AA
> >      > +}
> >      > +
> >      > +static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
> >      > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
> >     = xs[4];
> >      > + sqr1(z3); // z3 = (DA-CB)^2
> >      > + mul1(z3, x1); // z3 = x1 * (DA-CB)^2
> >      > + sqr1(x3); // x3 = (DA+CB)^2
> >      > + mul1(z2, x2); // z2 = AA*(E*a24+AA)
> >      > + sub(x2, t1, x2); // x2 = BB again
> >      > + mul1(x2, t1); // x2 = AA*BB
> >      > +}
> >      > +
> >      > +static void x25519_core(mg_fe xs[5], const uint8_t
> >     scalar[X25519_BYTES],
> >      > + const uint8_t *x1, int clamp) {
> >      > + int i;
> >      > + mg_fe x1_limbs;
> >      > + limb_t swap = 0;
> >      > + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
> >      > + memset(xs, 0, 4 * sizeof(mg_fe));
> >      > + x2[0] = z3[0] = 1;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + x3[i] = x1_limbs[i] =
> >      > + MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
> >      > + }
> >      > +
> >      > + for (i = 255; i >= 0; i--) {
> >      > + uint8_t bytei = scalar[i / 8];
> >      > + limb_t doswap;
> >      > + if (clamp) {
> >      > + if (i / 8 == 0) {
> >      > + bytei &= (uint8_t) ~7U;
> >      > + } else if (i / 8 == X25519_BYTES - 1) {
> >      > + bytei &= 0x7F;
> >      > + bytei |= 0x40;
> >      > + }
> >      > + }
> >      > + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
> >      > + condswap(x2, x3, swap ^ doswap);
> >      > + swap = doswap;
> >      > +
> >      > + ladder_part1(xs);
> >      > + ladder_part2(xs, (const limb_t *) x1_limbs);
> >      > + }
> >      > + condswap(x2, x3, swap);
> >      > +}
> >      > +
> >      > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
> >     scalar[X25519_BYTES],
> >      > + const uint8_t x1[X25519_BYTES], int clamp) {
> >      > + int i, ret;
> >      > + mg_fe xs[5], out_limbs;
> >      > + limb_t *x2, *z2, *z3, *prev;
> >      > + static const struct {
> >      > + uint8_t a, c, n;
> >      > + } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3,
> >     1, 1},
> >      > + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
> >      > + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}};
> >      > + x25519_core(xs, scalar, x1, clamp);
> >      > +
> >      > + // Precomputed inversion chain
> >      > + x2 = xs[0];
> >      > + z2 = xs[1];
> >      > + z3 = xs[3];
> >      > +
> >      > + prev = z2;
> >      > + for (i = 0; i < 13; i++) {
> >      > + int j;
> >      > + limb_t *a = xs[steps[i].a];
> >      > + for (j = steps[i].n; j > 0; j--) {
> >      > + sqr(a, prev);
> >      > + prev = a;
> >      > + }
> >      > + mul1(a, xs[steps[i].c]);
> >      > + }
> >      > +
> >      > + // Here prev = z3
> >      > + // x2 /= z2
> >      > + mul(out_limbs, x2, z3, NLIMBS);
> >      > + ret = (int) canon(out_limbs);
> >      > + if (!clamp) ret = 0;
> >      > + for (i = 0; i < NLIMBS; i++) {
> >      > + uint32_t n = out_limbs[i];
> >      > + out[i * 4] = (uint8_t) (n & 0xff);
> >      > + out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
> >      > + out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
> >      > + out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
> >      > + }
> >      > + return ret;
> >      > }
> >      > -#endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > #line 1 "src/url.c"
> >      > @@ -5382,7 +14797,7 @@ struct url {
> >      > int mg_url_is_ssl(const char *url) {
> >      > return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6)
> >     == 0 ||
> >      > strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
> >      > - strncmp(url, "tls:", 4) == 0;
> >      > + strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
> >      > }
> >      >
> >      > static struct url urlparse(const char *url) {
> >      > @@ -5464,6 +14879,14 @@ struct mg_str mg_url_pass(const char *url) {
> >      > #endif
> >      >
> >      >
> >      > +// Not using memset for zeroing memory, cause it can be dropped
> >     by compiler
> >      > +// See https://github.com/cesanta/mongoose/pull/1265
> >     <https://github.com/cesanta/mongoose/pull/1265>
> >      > +void mg_bzero(volatile unsigned char *buf, size_t len) {
> >      > + if (buf != NULL) {
> >      > + while (len--) *buf++ = 0;
> >      > + }
> >      > +}
> >      > +
> >      > #if MG_ENABLE_CUSTOM_RANDOM
> >      > #else
> >      > void mg_random(void *buf, size_t len) {
> >      > @@ -5512,11 +14935,16 @@ uint16_t mg_ntohs(uint16_t net) {
> >      > }
> >      >
> >      > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
> >      > - int i;
> >      > + static const uint32_t crclut[16] = {
> >      > + // table for polynomial 0xEDB88320 (reflected)
> >      > + 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190,
> >     0x6B6B51F4,
> >      > + 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8,
> >     0xCB61B38C,
> >      > + 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
> >      > crc = ~crc;
> >      > while (len--) {
> >      > - crc ^= *(unsigned char *) buf++;
> >      > - for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320
> >     : crc >> 1;
> >      > + uint8_t b = *(uint8_t *) buf++;
> >      > + crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
> >      > + crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
> >      > }
> >      > return ~crc;
> >      > }
> >      > @@ -5539,18 +14967,36 @@ static int parse_net(const char *spec,
> >     uint32_t *net, uint32_t *mask) {
> >      > return len;
> >      > }
> >      >
> >      > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
> >      > - struct mg_str k, v;
> >      > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
> >      > + struct mg_str entry;
> >      > int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set,
> >     deny by default
> >      > - while (mg_commalist(&acl, &k, &v)) {
> >      > - uint32_t net, mask;
> >      > - if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
> >      > - if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
> >      > - if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
> >      > + uint32_t remote_ip4;
> >      > + if (remote_ip->is_ip6) {
> >      > + return -1; // TODO(): handle IPv6 ACL and addresses
> >      > + } else { // IPv4
> >      > + memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
> >      > + while (mg_span(acl, &entry, &acl, ',')) {
> >      > + uint32_t net, mask;
> >      > + if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
> >      > + if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
> >      > + if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
> >      > + }
> >      > }
> >      > return allowed == '+';
> >      > }
> >      >
> >      > +bool mg_path_is_sane(const struct mg_str path) {
> >      > + const char *s = path.buf;
> >      > + size_t n = path.len;
> >      > + if (path.buf[0] == '.' && path.buf[1] == '.') return false; //
> >     Starts with ..
> >      > + for (; s[0] != '\0' && n > 0; s++, n--) {
> >      > + if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir?
> >      > + if (s[1] == '.' && s[2] == '.') return false; // Starts with ..
> >      > + }
> >      > + }
> >      > + return true;
> >      > +}
> >      > +
> >      > #if MG_ENABLE_CUSTOM_MILLIS
> >      > #else
> >      > uint64_t mg_millis(void) {
> >      > @@ -5558,9 +15004,8 @@ uint64_t mg_millis(void) {
> >      > return GetTickCount();
> >      > #elif MG_ARCH == MG_ARCH_RP2040
> >      > return time_us_64() / 1000;
> >      > -#elif MG_ARCH == MG_ARCH_ESP32
> >      > - return esp_timer_get_time() / 1000;
> >      > -#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
> >      > +#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
> >      > + MG_ARCH == MG_ARCH_FREERTOS
> >      > return xTaskGetTickCount() * portTICK_PERIOD_MS;
> >      > #elif MG_ARCH == MG_ARCH_AZURERTOS
> >      > return tx_time_get() * (1000 /* MS per SEC */ /
> >     TX_TIMER_TICKS_PER_SECOND);
> >      > @@ -5568,6 +15013,12 @@ uint64_t mg_millis(void) {
> >      > return (uint64_t) Clock_getTicks();
> >      > #elif MG_ARCH == MG_ARCH_ZEPHYR
> >      > return (uint64_t) k_uptime_get();
> >      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
> >      > + return (uint64_t) rt_time_get();
> >      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> >      > + return (uint64_t) ((osKernelGetTickCount() * 1000) /
> >     osKernelGetTickFreq());
> >      > +#elif MG_ARCH == MG_ARCH_RTTHREAD
> >      > + return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
> >      > #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
> >      > // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on
> >     linux
> >      > // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on
> >     linux
> >      > @@ -5595,7 +15046,6 @@ uint64_t mg_millis(void) {
> >      > }
> >      > #endif
> >      >
> >      > -
> >      > #ifdef MG_ENABLE_LINES
> >      > #line 1 "src/ws.c"
> >      > #endif
> >      > @@ -5609,6 +15059,7 @@ uint64_t mg_millis(void) {
> >      >
> >      >
> >      >
> >      > +
> >      > struct ws_msg {
> >      > uint8_t flags;
> >      > size_t header_len;
> >      > @@ -5640,10 +15091,10 @@ static void ws_handshake(struct
> >     mg_connection *c, const struct mg_str *wskey,
> >      >
> >      > mg_sha1_ctx sha_ctx;
> >      > mg_sha1_init(&sha_ctx);
> >      > - mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr,
> >     wskey->len);
> >      > + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf,
> >     wskey->len);
> >      > mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
> >      > mg_sha1_final(sha, &sha_ctx);
> >      > - mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
> >      > + mg_base64_encode(sha, sizeof(sha), (char *) b64_sha,
> >     sizeof(b64_sha));
> >      > mg_xprintf(mg_pfn_iobuf, &c->send,
> >      > "HTTP/1.1 101 Switching Protocols\r\n"
> >      > "Upgrade: websocket\r\n"
> >      > @@ -5653,7 +15104,7 @@ static void ws_handshake(struct
> >     mg_connection *c, const struct mg_str *wskey,
> >      > if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
> >      > if (wsproto != NULL) {
> >      > mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
> >      > - wsproto->ptr);
> >      > + wsproto->buf);
> >      > }
> >      > mg_send(c, "\r\n", 2);
> >      > }
> >      > @@ -5746,12 +15197,15 @@ static bool
> >     mg_ws_client_handshake(struct mg_connection *c) {
> >      > mg_error(c, "not http"); // Some just, not an HTTP request
> >      > } else if (n > 0) {
> >      > if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
> >      > - mg_error(c, "handshake error");
> >      > + mg_error(c, "ws handshake error");
> >      > } else {
> >      > struct mg_http_message hm;
> >      > - mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
> >      > - c->is_websocket = 1;
> >      > - mg_call(c, MG_EV_WS_OPEN, &hm);
> >      > + if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
> >      > + c->is_websocket = 1;
> >      > + mg_call(c, MG_EV_WS_OPEN, &hm);
> >      > + } else {
> >      > + mg_error(c, "ws handshake error");
> >      > + }
> >      > }
> >      > mg_iobuf_del(&c->recv, 0, (size_t) n);
> >      > } else {
> >      > @@ -5760,8 +15214,7 @@ static bool mg_ws_client_handshake(struct
> >     mg_connection *c) {
> >      > return false; // Continue event handler
> >      > }
> >      >
> >      > -static void mg_ws_cb(struct mg_connection *c, int ev, void
> >     *ev_data,
> >      > - void *fn_data) {
> >      > +static void mg_ws_cb(struct mg_connection *c, int ev, void
> >     *ev_data) {
> >      > struct ws_msg msg;
> >      > size_t ofs = (size_t) c->pfn_data;
> >      >
> >      > @@ -5775,7 +15228,7 @@ static void mg_ws_cb(struct mg_connection
> >     *c, int ev, void *ev_data,
> >      > size_t len = msg.header_len + msg.data_len;
> >      > uint8_t final = msg.flags & 128, op = msg.flags & 15;
> >      > // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
> >      > - // (int) m.data.len, (int) m.data.len, m.data.ptr));
> >      > + // (int) m.data.len, (int) m.data.len, m.data.buf));
> >      > switch (op) {
> >      > case WEBSOCKET_OP_CONTINUE:
> >      > mg_call(c, MG_EV_WS_CTL, &m);
> >      > @@ -5796,7 +15249,7 @@ static void mg_ws_cb(struct mg_connection
> >     *c, int ev, void *ev_data,
> >      > MG_DEBUG(("%lu WS CLOSE", c->id));
> >      > mg_call(c, MG_EV_WS_CTL, &m);
> >      > // Echo the payload of the received CLOSE message back to the sender
> >      > - mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
> >      > + mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
> >      > c->is_draining = 1;
> >      > break;
> >      > default:
> >      > @@ -5827,7 +15280,6 @@ static void mg_ws_cb(struct mg_connection
> >     *c, int ev, void *ev_data,
> >      > }
> >      > }
> >      > }
> >      > - (void) fn_data;
> >      > (void) ev_data;
> >      > }
> >      >
> >      > @@ -5839,7 +15291,7 @@ struct mg_connection *mg_ws_connect(struct
> >     mg_mgr *mgr, const char *url,
> >      > char nonce[16], key[30];
> >      > struct mg_str host = mg_url_host(url);
> >      > mg_random(nonce, sizeof(nonce));
> >      > - mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
> >      > + mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key,
> >     sizeof(key));
> >      > mg_xprintf(mg_pfn_iobuf, &c->send,
> >      > "GET %s HTTP/1.1\r\n"
> >      > "Upgrade: websocket\r\n"
> >      > @@ -5847,7 +15299,7 @@ struct mg_connection *mg_ws_connect(struct
> >     mg_mgr *mgr, const char *url,
> >      > "Connection: Upgrade\r\n"
> >      > "Sec-WebSocket-Version: 13\r\n"
> >      > "Sec-WebSocket-Key: %s\r\n",
> >      > - mg_url_uri(url), (int) host.len, host.ptr, key);
> >      > + mg_url_uri(url), (int) host.len, host.buf, key);
> >      > if (fmt != NULL) {
> >      > va_list ap;
> >      > va_start(ap, fmt);
> >      > @@ -5896,1591 +15348,2247 @@ size_t mg_ws_wrap(struct
> >     mg_connection *c, size_t len, int op) {
> >      > }
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "mip/driver_stm32.c"
> >      > +#line 1 "src/drivers/cmsis.c"
> >      > #endif
> >      > +// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
> >     <https://arm-software.github.io/CMSIS_5/Driver/html/index.html>
> >      >
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
> >     MG_ENABLE_DRIVER_CMSIS
> >      >
> >      > -#if MG_ENABLE_MIP && \
> >      > - (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
> >      > -struct stm32_eth {
> >      > - volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
> >     MACMIIDR, MACFCR,
> >      > - MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
> >     MACDBGR, MACSR,
> >      > - MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
> >     MACA3HR,
> >      > - MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> >      > - RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> >      > - RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> >      > - RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
> >     PTPTSLUR,
> >      > - PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
> >     RESERVED9[564],
> >      > - DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
> >     DMAIER,
> >      > - DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> >      > - DMACHRBAR;
> >      > -};
> >      > -#undef ETH
> >      > -#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
> >      >
> >      > -#undef BIT
> >      > -#define BIT(x) ((uint32_t) 1 << (x))
> >      > -#define ETH_PKT_SIZE 1540 // Max frame size
> >      > -#define ETH_DESC_CNT 4 // Descriptors count
> >      > -#define ETH_DS 4 // Descriptor size (words)
> >      >
> >      > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
> >     ethernet buffers
> >      > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
> >     ethernet buffers
> >      > -static struct mip_if *s_ifp; // MIP interface
> >      > -enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 }; // PHY constants
> >      >
> >      > -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > - ETH->MACMIIAR &= (7 << 2);
> >      > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> >      > - ETH->MACMIIAR |= BIT(0);
> >      > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
> >      > - return ETH->MACMIIDR;
> >      > -}
> >      >
> >      > -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t
> >     val) {
> >      > - ETH->MACMIIDR = val;
> >      > - ETH->MACMIIAR &= (7 << 2);
> >      > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
> >     6) | BIT(1);
> >      > - ETH->MACMIIAR |= BIT(0);
> >      > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
> >      > -}
> >      > +extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
> >      > +extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
> >      >
> >      > -static uint32_t get_hclk(void) {
> >      > - struct rcc {
> >      > - volatile uint32_t CR, PLLCFGR, CFGR;
> >      > - } *rcc = (struct rcc *) 0x40023800;
> >      > - uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
> >     8MHz */;
> >      > +static struct mg_tcpip_if *s_ifp;
> >      >
> >      > - if (rcc->CFGR & (1 << 2)) {
> >      > - clk = hse;
> >      > - } else if (rcc->CFGR & (1 << 3)) {
> >      > - uint32_t vco, m, n, p;
> >      > - m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> >      > - n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> >      > - p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> >      > - clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> >      > - vco = (uint32_t) ((uint64_t) clk * n / m);
> >      > - clk = vco / p;
> >      > - } else {
> >      > - clk = hsi;
> >      > +static void mac_cb(uint32_t);
> >      > +static bool cmsis_init(struct mg_tcpip_if *);
> >      > +static bool cmsis_up(struct mg_tcpip_if *);
> >      > +static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
> >      > +static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
> >      > +
> >      > +struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init,
> >     cmsis_tx, NULL,
> >      > + cmsis_up};
> >      > +
> >      > +static bool cmsis_init(struct mg_tcpip_if *ifp) {
> >      > + ARM_ETH_MAC_ADDR addr;
> >      > + s_ifp = ifp;
> >      > +
> >      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> >      > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> >      > + ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
> >      > + if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
> >      > + if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) !=
> >     ARM_DRIVER_OK)
> >      > + return false;
> >      > + if (cap.event_rx_frame == 0) // polled mode driver
> >      > + mg_tcpip_driver_cmsis.rx = cmsis_rx;
> >      > + mac->PowerControl(ARM_POWER_FULL);
> >      > + if (cap.mac_address) { // driver provides MAC address
> >      > + mac->GetMacAddress(&addr);
> >      > + memcpy(ifp->mac, &addr, sizeof(ifp->mac));
> >      > + } else { // we provide MAC address
> >      > + memcpy(&addr, ifp->mac, sizeof(addr));
> >      > + mac->SetMacAddress(&addr);
> >      > }
> >      > - uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> >      > - if (hpre < 8) return clk;
> >      > + phy->PowerControl(ARM_POWER_FULL);
> >      > + phy->SetInterface(cap.media_interface);
> >      > + phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
> >      > + return true;
> >      > +}
> >      >
> >      > - uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> >      > - return ((uint32_t) clk) >> ahbptab[hpre - 8];
> >      > +static size_t cmsis_tx(const void *buf, size_t len, struct
> >     mg_tcpip_if *ifp) {
> >      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> >      > + if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
> >      > + ifp->nerr++;
> >      > + return 0;
> >      > + }
> >      > + ifp->nsent++;
> >      > + return len;
> >      > }
> >      >
> >      > -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
> >     as per 802.3,
> >      > -// it must not exceed 2.5MHz As the AHB clock can be (and
> >     usually is) derived
> >      > -// from the HSI (internal RC), and it can go above specs, the
> >     datasheets
> >      > -// specify a range of frequencies and activate one of a series
> >     of dividers to
> >      > -// keep the MDC clock safely below 2.5MHz. We guess a divider
> >     setting based on
> >      > -// HCLK with a +5% drift. If the user uses a different clock
> >     from our
> >      > -// defaults, needs to set the macros on top Valid for
> >     STM32F74xxx/75xxx
> >      > -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
> >     drift)
> >      > -static int guess_mdc_cr(void) {
> >      > - uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
> >      > - uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
> >     dividers
> >      > - uint32_t hclk = get_hclk(); // Guess system HCLK
> >      > - int result = -1; // Invalid CR value
> >      > - if (hclk < 25000000) {
> >      > - MG_ERROR(("HCLK too low"));
> >      > - } else {
> >      > - for (int i = 0; i < 6; i++) {
> >      > - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > - result = crs[i];
> >      > - break;
> >      > +static bool cmsis_up(struct mg_tcpip_if *ifp) {
> >      > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
> >      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> >      > + bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0; //
> >     link state
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // just went up
> >      > + ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
> >      > + mac->Control(ARM_ETH_MAC_CONFIGURE,
> >      > + (st.speed << ARM_ETH_MAC_SPEED_Pos) |
> >      > + (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
> >      > + ARM_ETH_MAC_ADDRESS_BROADCAST);
> >      > + MG_DEBUG(("Link is %uM %s-duplex",
> >      > + (st.speed == 2) ? 1000
> >      > + : st.speed ? 100
> >      > + : 10,
> >      > + st.duplex ? "full" : "half"));
> >      > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
> >      > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
> >      > + } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) { //
> >     just went down
> >      > + mac->Control(ARM_ETH_MAC_FLUSH,
> >      > + ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
> >      > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
> >      > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
> >      > + }
> >      > + return up;
> >      > +}
> >      > +
> >      > +static void mac_cb(uint32_t ev) {
> >      > + if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
> >      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> >      > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
> >      > + if (len >= 60 && len <= 1518) { // proper frame
> >      > + char *p;
> >      > + if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) { //
> >     have room
> >      > + if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) { // copy
> >     succeeds
> >      > + mg_queue_add(&s_ifp->recv_queue, len);
> >      > + s_ifp->nrecv++;
> >      > }
> >      > + return;
> >      > }
> >      > - if (result < 0) MG_ERROR(("HCLK too high"));
> >      > + s_ifp->ndrop++;
> >      > }
> >      > - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> >      > - return result;
> >      > + mac->ReadFrame(NULL, 0); // otherwise, discard
> >      > +}
> >      > +
> >      > +static size_t cmsis_rx(void *buf, size_t buflen, struct
> >     mg_tcpip_if *ifp) {
> >      > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
> >      > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
> >      > + if (len >= 60 && len <= 1518 &&
> >      > + ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
> >      > + return len;
> >      > + if (len > 0) mac->ReadFrame(NULL, 0); // discard bad frames
> >      > + (void) ifp;
> >      > + return 0;
> >      > }
> >      >
> >      > -static bool mip_driver_stm32_init(struct mip_if *ifp) {
> >      > - struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data
> >     *) ifp->driver_data;
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/imxrt.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
> >     MG_ENABLE_DRIVER_IMXRT
> >      > +struct imxrt_enet {
> >      > + volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR,
> >     RESERVED2[3],
> >      > + ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC,
> >     RESERVED5[7], RCR,
> >      > + RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0,
> >     TXIC1, TXIC2,
> >      > + RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR,
> >     GALR,
> >      > + RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL,
> >     RSEM,
> >      > + RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC,
> >     RACC,
> >      > + RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
> >      > + RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
> >      > + RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127,
> >     RMON_T_P128TO255,
> >      > + RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048,
> >     RMON_T_GTE2048,
> >      > + RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL,
> >     IEEE_T_MCOL,
> >      > + IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR,
> >     IEEE_T_CSERR,
> >      > + IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3],
> >     RMON_R_PACKETS,
> >      > + RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
> >      > + RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
> >      > + RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511,
> >     RMON_R_P512TO1023,
> >      > + RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
> >      > + IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR,
> >     IEEE_R_FDXFC,
> >      > + IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER,
> >     ATCOR, ATINC,
> >      > + ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1,
> >     TCSR2, TCCR2,
> >      > + TCSR3;
> >      > +};
> >      > +
> >      > +#undef ENET
> >      > +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
> >      > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
> >      > +#define ETH_DESC_CNT 5 // Descriptors count
> >      > +#else
> >      > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#endif
> >      > +
> >      > +#define ETH_PKT_SIZE 1536 // Max frame size, 64-bit aligned
> >      > +
> >      > +struct enet_desc {
> >      > + uint16_t length; // Data length
> >      > + uint16_t control; // Control and status
> >      > + uint32_t *buffer; // Data ptr
> >      > +};
> >      > +
> >      > +// TODO(): handle these in a portable compiler-independent
> >     CMSIS-friendly way
> >      > +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
> >      > +
> >      > +// Descriptors: in non-cached area (TODO(scaprile)),
> >     (37.5.1.22.2 37.5.1.23.2)
> >      > +// Buffers: 64-byte aligned (37.3.14)
> >      > +static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT]
> >     MG_64BYTE_ALIGNED;
> >      > +static volatile struct enet_desc s_txdesc[ETH_DESC_CNT]
> >     MG_64BYTE_ALIGNED;
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
> >     MG_64BYTE_ALIGNED;
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
> >     MG_64BYTE_ALIGNED;
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +
> >      > +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
> >      > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
> >      > + ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18)
> >     | (2 << 16);
> >      > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> >      > + return ENET->MMFR & 0xffff;
> >      > +}
> >      > +
> >      > +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
> >      > + ENET->MMFR =
> >      > + (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16)
> >     | val;
> >      > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
> >      > +}
> >      > +
> >      > +// MDC clock is generated from IPS Bus clock (ipg_clk); as per
> >     802.3,
> >      > +// it must not exceed 2.5MHz
> >      > +// The PHY receives the PLL6-generated 50MHz clock
> >      > +static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_imxrt_data *d =
> >      > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
> >      > s_ifp = ifp;
> >      >
> >      > // Init RX descriptors
> >      > for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > - s_rxdesc[i][0] = BIT(31); // Own
> >      > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
> >     chained
> >      > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
> >     data buffer
> >      > - s_rxdesc[i][3] =
> >      > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + s_rxdesc[i].control = MG_BIT(15); // Own (E)
> >      > + s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i]; // Point to data
> >     buffer
> >      > }
> >      > + s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
> >     descriptor
> >      >
> >      > // Init TX descriptors
> >      > for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> >      > - s_txdesc[i][3] =
> >      > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + // s_txdesc[i].control = MG_BIT(10); // Own (TC)
> >      > + s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
> >      > }
> >      > -
> >      > - ETH->DMABMR |= BIT(0); // Software reset
> >      > - while ((ETH->DMABMR & BIT(0)) != 0) (void) 0; // Wait until done
> >      > -
> >      > - // Set MDC clock divider. If user told us the value, use it.
> >     Otherwise, guess
> >      > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
> >     d->mdc_cr;
> >      > - ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
> >      > -
> >      > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
> >     not use
> >      > - // hardware checksum. Therefore, descriptor size is 4, not 8
> >      > - // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
> >      > - ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT
> >      > - ETH->MACFCR = BIT(7); // Disable zero quarta pause
> >      > - // ETH->MACFFR = BIT(31); // Receive all
> >      > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY
> >      > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12)); // Set autonegotiation
> >      > - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> >      > - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
> >      > - ETH->DMAIER = BIT(6) | BIT(16); // RIE, NISE
> >      > - ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
> >     Duplex, Fast
> >      > - ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST,
> >     TSF, RSF
> >      > -
> >      > - // MAC address filtering
> >      > - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> >      > - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> >      > - ((uint32_t) ifp->mac[2] << 16) |
> >      > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> >      > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
> >      > + s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
> >     descriptor
> >      > +
> >      > + ENET->ECR = MG_BIT(0); // Software reset, disable
> >      > + while ((ENET->ECR & MG_BIT(0))) (void) 0; // Wait until done
> >      > +
> >      > + // Set MDC clock divider. If user told us the value, use it.
> >      > + // TODO(): Otherwise, guess (currently assuming max freq)
> >      > + int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
> >      > + ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1); // HOLDTIME 2 clks
> >      > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
> >      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC
> >     clocks PHY
> >      > + // Select RMII mode, 100M, keep CRC, set max rx length, disable
> >     loop
> >      > + ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
> >      > + // ENET->RCR |= MG_BIT(3); // Receive all
> >      > + ENET->TCR = MG_BIT(2); // Full-duplex
> >      > + ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
> >      > + ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
> >      > + ENET->MRBR[0] = ETH_PKT_SIZE; // Same size for RX/TX buffers
> >      > + // MAC address filtering (bytes in reversed order)
> >      > + ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t)
> >     ifp->mac[5] << 16U;
> >      > + ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
> >      > + ((uint32_t) ifp->mac[1] << 16U) |
> >      > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> >      > + ENET->ECR = MG_BIT(8) | MG_BIT(1); // Little-endian CPU, Enable
> >      > + ENET->EIMR = MG_BIT(25); // Set interrupt mask
> >      > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
> >      > + ENET->TDAR = MG_BIT(24); // Transmit Descriptors have changed
> >      > + // ENET->OPD = 0x10014;
> >      > return true;
> >      > }
> >      >
> >      > -static uint32_t s_txno;
> >      > -static size_t mip_driver_stm32_tx(const void *buf, size_t len,
> >     struct mip_if *ifp) {
> >      > - if (len > sizeof(s_txbuf[s_txno])) {
> >      > +// Transmit frame
> >      > +static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + static int s_txno; // Current descriptor index
> >      > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
> >      > MG_ERROR(("Frame too big, %ld", (long) len));
> >      > - len = 0; // Frame is too big
> >      > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> >      > - MG_ERROR(("No free descriptors"));
> >      > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> >     ETH->DMASR);
> >      > - len = 0; // All descriptors are busy, fail
> >      > + len = (size_t) -1; // fail
> >      > + } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No descriptors available"));
> >      > + len = 0; // retry later
> >      > } else {
> >      > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> >      > - s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30); //
> >     Chain,FS,LS
> >      > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno].length = (uint16_t) len; // Set data len
> >      > + // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
> >      > + s_txdesc[s_txno].control |=
> >      > + (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
> >      > + ENET->TDAR = MG_BIT(24); // Descriptor ring updated
> >      > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > }
> >      > - ETH->DMASR = BIT(2) | BIT(5); // Clear any prior TBUS/TUS
> >      > - ETH->DMATPDR = 0; // and resume
> >      > - return len;
> >      > (void) ifp;
> >      > + return len;
> >      > }
> >      >
> >      > -static bool mip_driver_stm32_up(struct mip_if *ifp) {
> >      > - uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
> >      > - (void) ifp;
> >      > - return bsr & BIT(2) ? 1 : 0;
> >      > +static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_imxrt_data *d =
> >      > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
> >      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + // tmp = reg with flags set to the most likely situation: 100M
> >     full-duplex
> >      > + // if(link is slow or half) set flags otherwise
> >      > + // reg = tmp
> >      > + uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex
> >      > + uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M
> >      > + if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9); // 10M
> >      > + if (full_duplex == false) tcr &= ~MG_BIT(2); // Half-duplex
> >      > + ENET->TCR = tcr; // IRQ handler does not fiddle with these
> >     registers
> >      > + ENET->RCR = rcr;
> >      > + MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
> >      > + tcr & MG_BIT(2) ? "full" : "half"));
> >      > + }
> >      > + return up;
> >      > }
> >      >
> >      > -void ETH_IRQHandler(void);
> >      > +void ENET_IRQHandler(void);
> >      > static uint32_t s_rxno;
> >      > -void ETH_IRQHandler(void) {
> >      > - qp_mark(QP_IRQTRIGGERED, 0);
> >      > - if (ETH->DMASR & BIT(6)) { // Frame received, loop
> >      > - ETH->DMASR = BIT(16) | BIT(6); // Clear flag
> >      > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
> >      > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
> >     BIT(9))) &&
> >      > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
> >      > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> >      > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> >      > - // ETH->DMASR);
> >      > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > - }
> >      > - s_rxdesc[s_rxno][0] = BIT(31);
> >      > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > +void ENET_IRQHandler(void) {
> >      > + ENET->EIR = MG_BIT(25); // Ack IRQ
> >      > + // Frame received, loop
> >      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > + uint32_t r = s_rxdesc[s_rxno].control;
> >      > + if (r & MG_BIT(15)) break; // exit when done
> >      > + // skip partial/errored frames (Table 37-32)
> >      > + if ((r & MG_BIT(11)) &&
> >      > + !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) |
> >     MG_BIT(0)))) {
> >      > + size_t len = s_rxdesc[s_rxno].length;
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > }
> >      > + s_rxdesc[s_rxno].control |= MG_BIT(15);
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > }
> >      > - ETH->DMASR = BIT(7); // Clear possible RBUS while processing
> >      > - ETH->DMARPDR = 0; // and resume RX
> >      > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
> >      > + // If b24 == 0, descriptors were exhausted and probably frames
> >     were dropped
> >      > }
> >      >
> >      > -struct mip_driver mip_driver_stm32 = {
> >      > - mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx,
> >     mip_driver_stm32_up};
> >      > +struct mg_tcpip_driver mg_tcpip_driver_imxrt =
> >     {mg_tcpip_driver_imxrt_init,
> >      > + mg_tcpip_driver_imxrt_tx, NULL,
> >      > + mg_tcpip_driver_imxrt_up};
> >      > +
> >      > #endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "mip/driver_tm4c.c"
> >      > +#line 1 "src/drivers/phy.c"
> >      > #endif
> >      >
> >      >
> >      > -#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) &&
> >     MG_ENABLE_DRIVER_TM4C
> >      > -struct tm4c_emac {
> >      > - volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
> >     EMACHASHTBLL,
> >      > - EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
> >     EMACSTATUS,
> >      > - EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
> >     EMACADDR0H,
> >      > - EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
> >     EMACADDR3H,
> >      > - EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> >      > - EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> >      > - EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
> >     RESERVED6[4],
> >      > - EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
> >     EMACRXCNTCRCERR,
> >      > - EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> >      > - EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
> >     EMACSUBSECINC,
> >      > - EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> >      > - EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> >      > - RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> >      > - EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
> >     EMACTXDLADDR,
> >      > - EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> >      > - RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
> >     EMACHOSRXBA,
> >      > - RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> >      > - EMACEPHYIM, EMACEPHYIMSC;
> >      > +enum { // ID1 ID2
> >      > + MG_PHY_KSZ8x = 0x22, // 0022 1561 - KSZ8081RNB
> >      > + MG_PHY_DP83x = 0x2000, // 2000 a140 - TI DP83825I
> >      > + MG_PHY_DP83867 = 0xa231, // 2000 a231 - TI DP83867I
> >      > + MG_PHY_LAN87x = 0x7, // 0007 c0fx - LAN8720
> >      > + MG_PHY_RTL8201 = 0x1C // 001c c816 - RTL8201
> >      > +};
> >      > +
> >      > +enum {
> >      > + MG_PHY_REG_BCR = 0,
> >      > + MG_PHY_REG_BSR = 1,
> >      > + MG_PHY_REG_ID1 = 2,
> >      > + MG_PHY_REG_ID2 = 3,
> >      > + MG_PHY_DP83x_REG_PHYSTS = 16,
> >      > + MG_PHY_DP83867_REG_PHYSTS = 17,
> >      > + MG_PHY_DP83x_REG_RCSR = 23,
> >      > + MG_PHY_DP83x_REG_LEDCR = 24,
> >      > + MG_PHY_KSZ8x_REG_PC1R = 30,
> >      > + MG_PHY_KSZ8x_REG_PC2R = 31,
> >      > + MG_PHY_LAN87x_REG_SCSR = 31,
> >      > + MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
> >      > + MG_PHY_RTL8201_REG_PAGESEL = 31
> >      > +};
> >      > +
> >      > +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
> >      > + switch (id1) {
> >      > + case MG_PHY_DP83x:
> >      > + switch (id2) {
> >      > + case MG_PHY_DP83867:
> >      > + return "DP83867";
> >      > + default:
> >      > + return "DP83x";
> >      > + }
> >      > + case MG_PHY_KSZ8x:
> >      > + return "KSZ8x";
> >      > + case MG_PHY_LAN87x:
> >      > + return "LAN87x";
> >      > + case MG_PHY_RTL8201:
> >      > + return "RTL8201";
> >      > + default:
> >      > + return "unknown";
> >      > + }
> >      > + (void) id2;
> >      > +}
> >      > +
> >      > +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t
> >     config) {
> >      > + uint16_t id1, id2;
> >      > + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15)); // Reset PHY
> >      > + while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15))
> >     (void) 0;
> >      > + // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw
> >     says otherwise
> >      > +
> >      > + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> >      > + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> >      > + MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2,
> >     mg_phy_id_to_str(id1, id2)));
> >      > +
> >      > + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
> >      > + phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to
> >     IO_MUX_CFG (0x0170)
> >      > + phy->write_reg(phy_addr, 0x0e, 0x170);
> >      > + phy->write_reg(phy_addr, 0x0d, 0x401f);
> >      > + phy->write_reg(phy_addr, 0x0e, 0x10d);
> >      > + }
> >      > +
> >      > + if (config & MG_PHY_CLOCKS_MAC) {
> >      > + // Use PHY crystal oscillator (preserve defaults)
> >      > + // nothing to do
> >      > + } else { // MAC clocks PHY, PHY has no xtal
> >      > + // Enable 50 MHz external ref clock at XI (preserve defaults)
> >      > + if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
> >      > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) |
> >     MG_BIT(0));
> >      > + } else if (id1 == MG_PHY_KSZ8x) {
> >      > + phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
> >      > + MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
> >      > + } else if (id1 == MG_PHY_LAN87x) {
> >      > + // nothing to do
> >      > + } else if (id1 == MG_PHY_RTL8201) {
> >      > + // assume PHY has been hardware strapped properly
> >      > +#if 0
> >      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7); //
> >     Select page 7
> >      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
> >      > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0); //
> >     Select page 0
> >      > +#endif
> >      > + }
> >      > + }
> >      > +
> >      > + if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
> >      > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
> >      > + MG_BIT(9) | MG_BIT(7)); // LED status, active high
> >      > + } // Other PHYs do not support this feature
> >      > +}
> >      > +
> >      > +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool
> >     *full_duplex,
> >      > + uint8_t *speed) {
> >      > + bool up = false;
> >      > + uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
> >      > + if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2))) // some PHYs latch
> >     down events
> >      > + bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); // read again
> >      > + up = bsr & MG_BIT(2);
> >      > + if (up && full_duplex != NULL && speed != NULL) {
> >      > + uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
> >      > + if (id1 == MG_PHY_DP83x) {
> >      > + uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
> >      > + if (id2 == MG_PHY_DP83867) {
> >      > + uint16_t physts = phy->read_reg(phy_addr,
> >     MG_PHY_DP83867_REG_PHYSTS);
> >      > + *full_duplex = physts & MG_BIT(13);
> >      > + *speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
> >      > + : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
> >      > + : MG_PHY_SPEED_10M;
> >      > + } else {
> >      > + uint16_t physts = phy->read_reg(phy_addr,
> >     MG_PHY_DP83x_REG_PHYSTS);
> >      > + *full_duplex = physts & MG_BIT(2);
> >      > + *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M :
> >     MG_PHY_SPEED_100M;
> >      > + }
> >      > + } else if (id1 == MG_PHY_KSZ8x) {
> >      > + uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
> >      > + *full_duplex = pc1r & MG_BIT(2);
> >      > + *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
> >      > + } else if (id1 == MG_PHY_LAN87x) {
> >      > + uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
> >      > + *full_duplex = scsr & MG_BIT(4);
> >      > + *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M :
> >     MG_PHY_SPEED_10M;
> >      > + } else if (id1 == MG_PHY_RTL8201) {
> >      > + uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
> >      > + *full_duplex = bcr & MG_BIT(8);
> >      > + *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M :
> >     MG_PHY_SPEED_10M;
> >      > + }
> >      > + }
> >      > + return up;
> >      > +}
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/ra.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
> >     MG_ENABLE_DRIVER_RA
> >      > +struct ra_etherc {
> >      > + volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR,
> >     RESERVED2, ECSIPR,
> >      > + RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR,
> >     RESERVED6[3], IPGR,
> >      > + APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR,
> >     RESERVED8[20], MAHR,
> >      > + RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR,
> >     RESERVED11, CEFCR,
> >      > + FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
> >      > };
> >      > -#undef EMAC
> >      > -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
> >      >
> >      > -#undef BIT
> >      > -#define BIT(x) ((uint32_t) 1 << (x))
> >      > -#define ETH_PKT_SIZE 1540 // Max frame size
> >      > +struct ra_edmac {
> >      > + volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR,
> >     RESERVED2, TDLAR,
> >      > + RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR,
> >     RESERVED6, TRSCER,
> >      > + RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10,
> >     RMCR,
> >      > + RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR,
> >     TRIMD,
> >      > + RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
> >      > +};
> >      > +
> >      > +#undef ETHERC
> >      > +#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
> >      > +#undef EDMAC
> >      > +#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
> >      > +#undef RASYSC
> >      > +#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
> >      > +#undef ICU_IELSR
> >      > +#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
> >      > +
> >      > +#define ETH_PKT_SIZE 1536 // Max frame size, multiple of 32
> >      > #define ETH_DESC_CNT 4 // Descriptors count
> >      > -#define ETH_DS 4 // Descriptor size (words)
> >      >
> >      > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
> >     ethernet buffers
> >      > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
> >     ethernet buffers
> >      > -static struct mip_if *s_ifp; // MIP interface
> >      > -enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 }; // PHY
> >     constants
> >      > +// TODO(): handle these in a portable compiler-independent
> >     CMSIS-friendly way
> >      > +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
> >      > +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
> >      >
> >      > -static inline void tm4cspin(volatile uint32_t count) {
> >      > +// Descriptors: 16-byte aligned
> >      > +// Buffers: 32-byte aligned (27.3.1)
> >      > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4]
> >     MG_16BYTE_ALIGNED;
> >      > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][4]
> >     MG_16BYTE_ALIGNED;
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
> >     MG_32BYTE_ALIGNED;
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
> >     MG_32BYTE_ALIGNED;
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +
> >      > +// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or
> >     equivalent
> >      > +static inline void raspin(volatile uint32_t count) {
> >      > while (count--) (void) 0;
> >      > }
> >      > -
> >      > -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> >      > - EMAC->EMACMIIADDR &= (0xf << 2);
> >      > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
> >     << 6);
> >      > - EMAC->EMACMIIADDR |= BIT(0);
> >      > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> >      > - return EMAC->EMACMIIDATA;
> >      > +// count to get the 200ns SMC semi-cycle period (2.5MHz) calling
> >     raspin():
> >      > +// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
> >      > +static uint32_t s_smispin;
> >      > +
> >      > +// Bit-banged SMI
> >      > +static void smi_preamble(void) {
> >      > + unsigned int i = 32;
> >      > + uint32_t pir = MG_BIT(1) | MG_BIT(2); // write, mdio = 1, mdc = 0
> >      > + ETHERC->PIR = pir;
> >      > + while (i--) {
> >      > + pir &= ~MG_BIT(0); // mdc = 0
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + pir |= MG_BIT(0); // mdc = 1
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + }
> >      > }
> >      > -
> >      > -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
> >     val) {
> >      > - EMAC->EMACMIIDATA = val;
> >      > - EMAC->EMACMIIADDR &= (0xf << 2);
> >      > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
> >     << 6) | BIT(1);
> >      > - EMAC->EMACMIIADDR |= BIT(0);
> >      > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
> >      > +static void smi_wr(uint16_t header, uint16_t data) {
> >      > + uint32_t word = (header << 16) | data;
> >      > + smi_preamble();
> >      > + unsigned int i = 32;
> >      > + while (i--) {
> >      > + uint32_t pir = MG_BIT(1) |
> >      > + (word & 0x80000000 ? MG_BIT(2) : 0); // write, mdc = 0, data
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + pir |= MG_BIT(0); // mdc = 1
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + word <<= 1;
> >      > + }
> >      > }
> >      > -
> >      > -static uint32_t get_sysclk(void) {
> >      > - struct sysctl {
> >      > - volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
> >     PLLFREQ0,
> >      > - PLLFREQ1;
> >      > - } *sysctl = (struct sysctl *) 0x400FE000;
> >      > - uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
> >     25000000 /* 25MHz */;
> >      > - if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
> >      > - uint32_t fin, vco, mdiv, n, q, psysdiv;
> >      > - uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> >      > - if (pllsrc == 0) {
> >      > - clk = piosc;
> >      > - } else if (pllsrc == 3) {
> >      > - clk = mosc;
> >      > - } else {
> >      > - MG_ERROR(("Unsupported clock source"));
> >      > - }
> >      > - q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> >      > - n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> >      > - fin = clk / ((q + 1) * (n + 1));
> >      > - mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> >      > - 0; // mint + (mfrac / 1024); MFRAC not supported
> >      > - psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> >      > - vco = (uint32_t) ((uint64_t) fin * mdiv);
> >      > - return vco / (psysdiv + 1);
> >      > +static uint16_t smi_rd(uint16_t header) {
> >      > + smi_preamble();
> >      > + unsigned int i = 16; // 2 LSb as turnaround
> >      > + uint32_t pir;
> >      > + while (i--) {
> >      > + pir = (i > 1 ? MG_BIT(1) : 0) |
> >      > + (header & 0x8000
> >      > + ? MG_BIT(2)
> >      > + : 0); // mdc = 0, header, set read direction at turnaround
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + pir |= MG_BIT(0); // mdc = 1
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > + header <<= 1;
> >      > }
> >      > - uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> >      > - if (oscsrc == 0) {
> >      > - clk = piosc;
> >      > - } else if (oscsrc == 3) {
> >      > - clk = mosc;
> >      > - } else {
> >      > - MG_ERROR(("Unsupported clock source"));
> >      > + i = 16;
> >      > + uint16_t data = 0;
> >      > + while (i--) {
> >      > + data <<= 1;
> >      > + pir = 0; // read, mdc = 0
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin / 2); // 1/4 clock period, 300ns max access time
> >      > + data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0); // read mdio
> >      > + raspin(s_smispin / 2); // 1/4 clock period
> >      > + pir |= MG_BIT(0); // mdc = 1
> >      > + ETHERC->PIR = pir;
> >      > + raspin(s_smispin);
> >      > }
> >      > - uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> >      > - return clk / (osysdiv + 1);
> >      > + return data;
> >      > }
> >      >
> >      > -// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
> >     (AHB); as per
> >      > -// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
> >     clock can be
> >      > -// derived from the PIOSC (internal RC), and it can go above
> >     specs, the
> >      > -// datasheets specify a range of frequencies and activate one of
> >     a series of
> >      > -// dividers to keep the MDC clock safely below 2.5MHz. We guess
> >     a divider
> >      > -// setting based on SYSCLK with a +5% drift. If the user uses a
> >     different clock
> >      > -// from our defaults, needs to set the macros on top Valid for
> >     TM4C129x (20.7)
> >      > -// (4.5% worst case drift)
> >      > -// The PHY receives the main oscillator (MOSC) (20.3.1)
> >      > -static int guess_mdc_cr(void) {
> >      > - uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
> >      > - uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
> >      > - uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
> >      > - int result = -1; // Invalid CR value
> >      > - if (sysclk < 25000000) {
> >      > - MG_ERROR(("SYSCLK too low"));
> >      > - } else {
> >      > - for (int i = 0; i < 4; i++) {
> >      > - if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > - result = crs[i];
> >      > - break;
> >      > - }
> >      > - }
> >      > - if (result < 0) MG_ERROR(("SYSCLK too high"));
> >      > - }
> >      > - MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> >      > - return result;
> >      > +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) |
> >     (reg << 2) | (2 << 0)));
> >      > }
> >      >
> >      > -static bool mip_driver_tm4c_init(struct mip_if *ifp) {
> >      > - struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data
> >     *) ifp->driver_data;
> >      > +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg <<
> >     2) | (2 << 0)), val);
> >      > +}
> >      > +
> >      > +// MDC clock is generated manually; as per 802.3, it must not
> >     exceed 2.5MHz
> >      > +static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_ra_data *d =
> >      > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
> >      > s_ifp = ifp;
> >      >
> >      > + // Init SMI clock timing. If user told us the clock value, use it.
> >      > + // TODO(): Otherwise, guess
> >      > + s_smispin = d->clock / 15000000;
> >      > +
> >      > // Init RX descriptors
> >      > for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > - s_rxdesc[i][0] = BIT(31); // Own
> >      > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
> >     chained
> >      > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
> >     data buffer
> >      > - s_rxdesc[i][3] =
> >      > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > - // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> >      > + s_rxdesc[i][0] = MG_BIT(31); // RACT
> >      > + s_rxdesc[i][1] = ETH_PKT_SIZE << 16; // RBL
> >      > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; // Point to data buffer
> >      > }
> >      > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
> >     descriptor
> >      >
> >      > // Init TX descriptors
> >      > for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> >      > - s_txdesc[i][3] =
> >      > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + // TACT = 0
> >      > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
> >      > }
> >      > -
> >      > - EMAC->EMACDMABUSMOD |= BIT(0); // Software reset
> >      > - while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1); //
> >     Wait until done
> >      > -
> >      > - // Set MDC clock divider. If user told us the value, use it.
> >     Otherwise, guess
> >      > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
> >     d->mdc_cr;
> >      > - EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> >      > -
> >      > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
> >     not use
> >      > - // hardware checksum. Therefore, descriptor size is 4, not 8
> >      > - // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23)
> >     | BIT(25);
> >      > - EMAC->EMACIM = BIT(3) | BIT(9); // Mask timestamp & PMT IT
> >      > - EMAC->EMACFLOWCTL = BIT(7); // Disable zero-quanta pause
> >      > - // EMAC->EMACFRAMEFLTR = BIT(31); // Receive all
> >      > - // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> >      > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15)); // Reset internal
> >     PHY (EPHY)
> >      > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12)); // Set
> >     autonegotiation
> >      > - EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
> >     descriptors
> >      > - EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
> >     descriptors
> >      > - EMAC->EMACDMAIM = BIT(6) | BIT(16); // RIE, NIE
> >      > - EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
> >     Duplex, Fast
> >      > - EMAC->EMACDMAOPMODE =
> >      > - BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
> >      > - EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> >      > - EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> >      > - ((uint32_t) ifp->mac[2] << 16) |
> >      > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> >      > - // NOTE(scaprile) There are 3 additional slots for filtering,
> >     disabled by
> >      > - // default. This also applies to the STM32 driver (at least for
> >     F7)
> >      > -
> >      > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
> >      > + s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
> >     descriptor
> >      > +
> >      > + EDMAC->EDMR = MG_BIT(0); // Software reset, wait 64 PCLKA
> >     clocks (27.2.1)
> >      > + uint32_t sckdivcr = RASYSC[8]; // get divisors from SCKDIVCR
> >     (8.2.2)
> >      > + uint32_t ick = 1 << ((sckdivcr >> 24) & 7); // sys_clock div
> >      > + uint32_t pcka = 1 << ((sckdivcr >> 12) & 7); // pclka div
> >      > + raspin((64U * pcka) / (3U * ick));
> >      > + EDMAC->EDMR = MG_BIT(6); // Initialize, little-endian (27.2.1)
> >      > +
> >      > + MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
> >      > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> >      > + mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
> >      > +
> >      > + // Select RMII mode,
> >      > + ETHERC->ECMR = MG_BIT(2) | MG_BIT(1); // 100M, Full-duplex, CRC
> >      > + // ETHERC->ECMR |= MG_BIT(0); // Receive all
> >      > + ETHERC->RFLR = 1518; // Set max rx length
> >      > +
> >      > + EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
> >      > + EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
> >      > + // MAC address filtering (bytes in reversed order)
> >      > + ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
> >      > + ((uint32_t) ifp->mac[1] << 16U) |
> >      > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
> >      > + ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
> >      > +
> >      > + EDMAC->TFTR = 0; // Store and forward (27.2.10)
> >      > + EDMAC->FDR = 0x070f; // (27.2.11)
> >      > + EDMAC->RMCR = MG_BIT(0); // (27.2.12)
> >      > + ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5); // TE RE
> >      > + EDMAC->EESIPR = MG_BIT(18); // Enable Rx IRQ
> >      > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
> >      > + EDMAC->EDTRR = MG_BIT(0); // Transmit Descriptors have changed
> >      > return true;
> >      > }
> >      >
> >      > -static uint32_t s_txno;
> >      > -static size_t mip_driver_tm4c_tx(const void *buf, size_t len,
> >     struct mip_if *ifp) {
> >      > - if (len > sizeof(s_txbuf[s_txno])) {
> >      > +// Transmit frame
> >      > +static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + static int s_txno; // Current descriptor index
> >      > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
> >      > MG_ERROR(("Frame too big, %ld", (long) len));
> >      > - len = 0; // fail
> >      > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
> >      > + len = (size_t) -1; // fail
> >      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> >      > + ifp->nerr++;
> >      > MG_ERROR(("No descriptors available"));
> >      > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> >      > - // EMAC->EMACDMARIS);
> >      > - len = 0; // fail
> >      > + len = 0; // retry later
> >      > } else {
> >      > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> >      > - s_txdesc[s_txno][0] =
> >      > - BIT(20) | BIT(28) | BIT(29) | BIT(30); // Chain,FS,LS,IC
> >      > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno][1] = len << 16; // Set data len
> >      > + s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28; // (27.3.1.1) mark
> >     valid
> >      > + EDMAC->EDTRR = MG_BIT(0); // Transmit request
> >      > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > }
> >      > - EMAC->EMACDMARIS = BIT(2) | BIT(5); // Clear any prior TU/UNF
> >      > - EMAC->EMACTXPOLLD = 0; // and resume
> >      > return len;
> >      > - (void) ifp;
> >      > }
> >      >
> >      > -static bool mip_driver_tm4c_up(struct mip_if *ifp) {
> >      > - uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> >      > - (void) ifp;
> >      > - return (bmsr & BIT(2)) ? 1 : 0;
> >      > +static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_ra_data *d =
> >      > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
> >      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + // tmp = reg with flags set to the most likely situation: 100M
> >     full-duplex
> >      > + // if(link is slow or half) set flags otherwise
> >      > + // reg = tmp
> >      > + uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1); // 100M
> >     Full-duplex
> >      > + if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2); // 10M
> >      > + if (full_duplex == false) ecmr &= ~MG_BIT(1); // Half-duplex
> >      > + ETHERC->ECMR = ecmr; // IRQ handler does not fiddle with these
> >     registers
> >      > + MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
> >      > + ecmr & MG_BIT(1) ? "full" : "half"));
> >      > + }
> >      > + return up;
> >      > }
> >      >
> >      > -void EMAC0_IRQHandler(void);
> >      > +void EDMAC_IRQHandler(void);
> >      > static uint32_t s_rxno;
> >      > -void EMAC0_IRQHandler(void) {
> >      > - qp_mark(QP_IRQTRIGGERED, 0);
> >      > - if (EMAC->EMACDMARIS & BIT(6)) { // Frame received, loop
> >      > - EMAC->EMACDMARIS = BIT(16) | BIT(6); // Clear flag
> >      > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
> >      > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
> >     BIT(9))) &&
> >      > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
> >      > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
> >      > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> >      > - // EMAC->EMACDMARIS);
> >      > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > - }
> >      > - s_rxdesc[s_rxno][0] = BIT(31);
> >      > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > +void EDMAC_IRQHandler(void) {
> >      > + struct mg_tcpip_driver_ra_data *d =
> >      > + (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
> >      > + EDMAC->EESR = MG_BIT(18); // Ack IRQ in EDMAC 1st
> >      > + ICU_IELSR[d->irqno] &= ~MG_BIT(16); // Ack IRQ in ICU last
> >      > + // Frame received, loop
> >      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > + uint32_t r = s_rxdesc[s_rxno][0];
> >      > + if (r & MG_BIT(31)) break; // exit when done
> >      > + // skip partial/errored frames (27.3.1.2)
> >      > + if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
> >      > + size_t len = s_rxdesc[s_rxno][1] & 0xffff;
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); // CRC already
> >     stripped
> >      > }
> >      > + s_rxdesc[s_rxno][0] |= MG_BIT(31);
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > }
> >      > - EMAC->EMACDMARIS = BIT(7); // Clear possible RU while processing
> >      > - EMAC->EMACRXPOLLD = 0; // and resume RX
> >      > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
> >      > + // If b0 == 0, descriptors were exhausted and probably frames
> >     were dropped,
> >      > + // (27.2.9 RMFCR counts them)
> >      > }
> >      >
> >      > -struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init,
> >     mip_driver_tm4c_tx,
> >      > - mip_driver_rx, mip_driver_tm4c_up};
> >      > +struct mg_tcpip_driver mg_tcpip_driver_ra =
> >     {mg_tcpip_driver_ra_init,
> >      > + mg_tcpip_driver_ra_tx, NULL,
> >      > + mg_tcpip_driver_ra_up};
> >      > +
> >      > #endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "mip/driver_w5500.c"
> >      > +#line 1 "src/drivers/same54.c"
> >      > #endif
> >      >
> >      >
> >      > -#if MG_ENABLE_MIP
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
> >     MG_ENABLE_DRIVER_SAME54
> >      >
> >      > -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> >      > +#include <sam.h>
> >      >
> >      > -static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t
> >     addr, bool wr,
> >      > - void *buf, size_t len) {
> >      > - uint8_t *p = (uint8_t *) buf;
> >      > - uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> >      > - (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> >      > - s->begin(s->spi);
> >      > - for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> >      > - for (size_t i = 0; i < len; i++) {
> >      > - uint8_t r = s->txn(s->spi, p[i]);
> >      > - if (!wr) p[i] = r;
> >      > - }
> >      > - s->end(s->spi);
> >      > +#define ETH_PKT_SIZE 1536 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 2 // Descriptor size (words)
> >      > +
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > +static uint8_t s_txno; // Current TX descriptor
> >      > +static uint8_t s_rxno; // Current RX descriptor
> >      > +
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> >      > +
> >      > +#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
> >      > +#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
> >      > +#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
> >      > +
> >      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
> >      > + GMAC_MAN_OP(2) | // Setting the read operation
> >      > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
> >      > + GMAC_MAN_REGA(reg); // Setting the register
> >      > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
> >      > + return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk; // Getting the
> >     read value
> >      > }
> >      >
> >      > -// clang-format off
> >      > -static void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t
> >     addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf,
> >     len); }
> >      > -static void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t
> >     addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> >      > -static void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t
> >     addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8),
> >     (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
> >      > -static void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t
> >     addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf,
> >     len); }
> >      > -static uint8_t w5500_r1(struct mip_spi *s, uint8_t block,
> >     uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
> >     return r; }
> >      > -static uint16_t w5500_r2(struct mip_spi *s, uint8_t block,
> >     uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
> >     buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> >      > -// clang-format on
> >      > +#if 0
> >      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) | //
> >     Setting the write operation
> >      > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
> >      > + GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val); // Setting the register
> >      > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting
> >     until the write op is complete
> >      > +}
> >      > +#endif
> >      >
> >      > -static size_t w5500_rx(void *buf, size_t buflen, struct mip_if
> >     *ifp) {
> >      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> >      > - uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
> >     recv len
> >      > - while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
> >     it is stable
> >      > - // printf("RSR: %d\n", (int) n);
> >      > - if (n > 0) {
> >      > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
> >      > - n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
> >      > - if (n <= len + 2 && n > 1) {
> >      > - r = (uint16_t) (n - 2);
> >      > - w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
> >      > +int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
> >      > + if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
> >      > + return d->mdc_cr;
> >      > + } else {
> >      > + // get MCLK from GCLK_GENERATOR 0
> >      > + uint32_t div = 512;
> >      > + uint32_t mclk;
> >      > + if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
> >      > + div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
> >      > + if (div == 0) div = 1;
> >      > }
> >      > - w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
> >     read pointer
> >      > - w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
> >      > - // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
> >      > + switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
> >      > + case GCLK_GENCTRL_SRC_XOSC0_Val:
> >      > + mclk = 32000000UL; /* 32MHz */
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_XOSC1_Val:
> >      > + mclk = 32000000UL; /* 32MHz */
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_OSCULP32K_Val:
> >      > + mclk = 32000UL;
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_XOSC32K_Val:
> >      > + mclk = 32000UL;
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_DFLL_Val:
> >      > + mclk = 48000000UL; /* 48MHz */
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_DPLL0_Val:
> >      > + mclk = 200000000UL; /* 200MHz */
> >      > + break;
> >      > + case GCLK_GENCTRL_SRC_DPLL1_Val:
> >      > + mclk = 200000000UL; /* 200MHz */
> >      > + break;
> >      > + default:
> >      > + mclk = 200000000UL; /* 200MHz */
> >      > + }
> >      > +
> >      > + mclk /= div;
> >      > + uint8_t crs[] = {0, 1, 2, 3, 4, 5}; // GMAC->NCFGR::CLK values
> >      > + uint8_t dividers[] = {8, 16, 32, 48, 64, 96}; // Respective CLK
> >     dividers
> >      > + for (int i = 0; i < 6; i++) {
> >      > + if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > + return crs[i];
> >      > + }
> >      > + }
> >      > +
> >      > + return 5;
> >      > }
> >      > - return r;
> >      > }
> >      >
> >      > -static size_t w5500_tx(const void *buf, size_t buflen, struct
> >     mip_if *ifp) {
> >      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> >      > - uint16_t n = 0, len = (uint16_t) buflen;
> >      > - while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
> >      > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
> >      > - w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
> >      > - w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
> >     write pointer
> >      > - w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
> >      > - for (int i = 0; i < 40; i++) {
> >      > - uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
> >      > - if (ir == 0) continue;
> >      > - // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
> >     (int) n, ptr);
> >      > - w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
> >      > - if (ir & 8) len = 0; // Timeout. Report error
> >      > - if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
> >      > +static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_same54_data *d =
> >      > + (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
> >      > + s_ifp = ifp;
> >      > +
> >      > + MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
> >      > + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
> >      > + GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d)); //
> >     Set MDC divider
> >      > + GMAC_REGS->GMAC_NCR = 0; // Disable RX & TX
> >      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk; // Enable MDC & MDIO
> >      > +
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init TX descriptors
> >      > + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; // Point to data buffer
> >      > + s_txdesc[i][1] = MG_BIT(31); // OWN bit
> >      > }
> >      > - return len;
> >      > + s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30); // Last tx
> >     descriptor - wrap
> >      > +
> >      > + GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18) // DMA recv buf 1536
> >      > + | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
> >      > + GMAC_DCFGR_TXPBMS(1); // See #2487
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init RX descriptors
> >      > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; // Address of the data
> >     buffer
> >      > + s_rxdesc[i][1] = 0; // Clear status
> >      > + }
> >      > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1); // Last rx
> >     descriptor - wrap
> >      > +
> >      > + GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc; // about the
> >     descriptor addresses
> >      > + GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc; // Let the
> >     controller know
> >      > +
> >      > + GMAC_REGS->SA[0].GMAC_SAB =
> >      > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> >      > + GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5],
> >     ifp->mac[4]);
> >      > +
> >      > + GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk; // Disable MII, use RMII
> >      > + GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk |
> >     GMAC_NCFGR_MTIHEN_Msk |
> >      > + GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
> >      > + GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
> >      > + GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
> >      > + GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> >      > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
> >      > + GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
> >      > + GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
> >      > + GMAC_REGS->GMAC_IDR = ~0U; // Disable interrupts, then enable
> >     required
> >      > + GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
> >      > + GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
> >      > + GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
> >      > + GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
> >      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
> >      > + NVIC_EnableIRQ(GMAC_IRQn);
> >      > +
> >      > + return true;
> >      > }
> >      >
> >      > -static bool w5500_init(struct mip_if *ifp) {
> >      > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
> >      > - s->end(s->spi);
> >      > - w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
> >      > - w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
> >      > - w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
> >      > - // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
> >      > - w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
> >      > - w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
> >      > - w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
> >      > - w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
> >      > - return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
> >      > +static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t
> >     len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // Frame is too big
> >      > + } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No free descriptors"));
> >      > + len = 0; // All descriptors are busy, fail
> >      > + } else {
> >      > + uint32_t status = len | MG_BIT(15); // Frame length, last chunk
> >      > + if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30); // wrap
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno][1] = status;
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > + }
> >      > + __DSB(); // Ensure descriptors have been written
> >      > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk; // Enable transmission
> >      > + return len;
> >      > }
> >      >
> >      > -static bool w5500_up(struct mip_if *ifp) {
> >      > - struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
> >      > - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> >      > - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> >      > +static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
> >      > + uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
> >      > + bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
> >      > +
> >      > + // If PHY is ready, update NCFGR accordingly
> >      > + if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
> >      > + uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
> >      > + bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
> >      > + bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
> >      > + GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
> >      > + ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
> >      > + GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
> >      > + }
> >      > +
> >      > + return up;
> >      > +}
> >      > +
> >      > +void GMAC_Handler(void);
> >      > +void GMAC_Handler(void) {
> >      > + uint32_t isr = GMAC_REGS->GMAC_ISR;
> >      > + uint32_t rsr = GMAC_REGS->GMAC_RSR;
> >      > + uint32_t tsr = GMAC_REGS->GMAC_TSR;
> >      > + if (isr & GMAC_ISR_RCOMP_Msk) {
> >      > + if (rsr & GMAC_ISR_RCOMP_Msk) {
> >      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> >      > + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
> >      > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> >      > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // Disown
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > + }
> >      > + }
> >      > + }
> >      > +
> >      > + if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
> >     GMAC_TSR_TXCOMP_Msk |
> >      > + GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
> >      > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
> >      > + // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
> >      > + if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |=
> >     MG_BIT(31);
> >      > + }
> >      > +
> >      > + GMAC_REGS->GMAC_RSR = rsr;
> >      > + GMAC_REGS->GMAC_TSR = tsr;
> >      > }
> >      >
> >      > -struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx,
> >     w5500_rx, w5500_up};
> >      > +struct mg_tcpip_driver mg_tcpip_driver_same54 = {
> >      > + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
> >      > + mg_tcpip_driver_same54_up};
> >      > #endif
> >      >
> >      > #ifdef MG_ENABLE_LINES
> >      > -#line 1 "mip/mip.c"
> >      > +#line 1 "src/drivers/stm32f.c"
> >      > #endif
> >      >
> >      >
> >      > -#if MG_ENABLE_MIP
> >      > -
> >      > -#define MIP_ETHEMERAL_PORT 49152
> >      > -#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
> >      > -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
> >      > -
> >      > -#ifndef MIP_QSIZE
> >      > -#define MIP_QSIZE (16 * 1024) // Queue size
> >      > -#endif
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> >      > + MG_ENABLE_DRIVER_STM32F
> >      > +struct stm32f_eth {
> >      > + volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
> >     MACMIIDR, MACFCR,
> >      > + MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
> >     MACDBGR, MACSR,
> >      > + MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
> >     MACA3HR,
> >      > + MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
> >      > + RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
> >      > + RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
> >      > + RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
> >     PTPTSLUR,
> >      > + PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
> >     RESERVED9[564],
> >      > + DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
> >     DMAIER,
> >      > + DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
> >      > + DMACHRBAR;
> >      > +};
> >      > +#undef ETH
> >      > +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
> >      >
> >      > -#ifndef MIP_TCP_KEEPALIVE_MS
> >      > -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
> >      > -#endif
> >      > +#define ETH_PKT_SIZE 1540 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 4 // Descriptor size (words)
> >      >
> >      > -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
> >      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
> >     ethernet buffers
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
> >     ethernet buffers
> >      > +static uint8_t s_txno; // Current TX descriptor
> >      > +static uint8_t s_rxno; // Current RX descriptor
> >      >
> >      > -struct connstate {
> >      > - uint32_t seq, ack; // TCP seq/ack counters
> >      > - uint64_t timer; // TCP keep-alive / ACK timer
> >      > - uint8_t mac[6]; // Peer MAC address
> >      > - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
> >      > -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
> >     send keepalive
> >      > -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
> >     soon
> >      > - uint8_t tmiss; // Number of keep-alive misses
> >      > - struct mg_iobuf raw; // For TLS only. Incoming raw data
> >      > -};
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      >
> >      > -#pragma pack(push, 1)
> >      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + ETH->MACMIIAR &= (7 << 2);
> >      > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
> >      > + ETH->MACMIIAR |= MG_BIT(0);
> >      > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> >      > + return ETH->MACMIIDR & 0xffff;
> >      > +}
> >      >
> >      > -struct lcp {
> >      > - uint8_t addr, ctrl, proto[2], code, id, len[2];
> >      > -};
> >      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + ETH->MACMIIDR = val;
> >      > + ETH->MACMIIAR &= (7 << 2);
> >      > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
> >     6) | MG_BIT(1);
> >      > + ETH->MACMIIAR |= MG_BIT(0);
> >      > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
> >      > +}
> >      >
> >      > -struct eth {
> >      > - uint8_t dst[6]; // Destination MAC address
> >      > - uint8_t src[6]; // Source MAC address
> >      > - uint16_t type; // Ethernet type
> >      > -};
> >      > +static uint32_t get_hclk(void) {
> >      > + struct rcc {
> >      > + volatile uint32_t CR, PLLCFGR, CFGR;
> >      > + } *rcc = (struct rcc *) 0x40023800;
> >      > + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
> >     8MHz */;
> >      >
> >      > -struct ip {
> >      > - uint8_t ver; // Version
> >      > - uint8_t tos; // Unused
> >      > - uint16_t len; // Length
> >      > - uint16_t id; // Unused
> >      > - uint16_t frag; // Fragmentation
> >      > - uint8_t ttl; // Time to live
> >      > - uint8_t proto; // Upper level protocol
> >      > - uint16_t csum; // Checksum
> >      > - uint32_t src; // Source IP
> >      > - uint32_t dst; // Destination IP
> >      > -};
> >      > + if (rcc->CFGR & (1 << 2)) {
> >      > + clk = hse;
> >      > + } else if (rcc->CFGR & (1 << 3)) {
> >      > + uint32_t vco, m, n, p;
> >      > + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
> >      > + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
> >      > + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
> >      > + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
> >      > + vco = (uint32_t) ((uint64_t) clk * n / m);
> >      > + clk = vco / p;
> >      > + } else {
> >      > + clk = hsi;
> >      > + }
> >      > + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
> >      > + if (hpre < 8) return clk;
> >      >
> >      > -struct ip6 {
> >      > - uint8_t ver; // Version
> >      > - uint8_t opts[3]; // Options
> >      > - uint16_t len; // Length
> >      > - uint8_t proto; // Upper level protocol
> >      > - uint8_t ttl; // Time to live
> >      > - uint8_t src[16]; // Source IP
> >      > - uint8_t dst[16]; // Destination IP
> >      > -};
> >      > + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> >      > + return ((uint32_t) clk) >> ahbptab[hpre - 8];
> >      > +}
> >      >
> >      > -struct icmp {
> >      > - uint8_t type;
> >      > - uint8_t code;
> >      > - uint16_t csum;
> >      > -};
> >      > +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
> >     as per 802.3,
> >      > +// it must not exceed 2.5MHz As the AHB clock can be (and
> >     usually is) derived
> >      > +// from the HSI (internal RC), and it can go above specs, the
> >     datasheets
> >      > +// specify a range of frequencies and activate one of a series
> >     of dividers to
> >      > +// keep the MDC clock safely below 2.5MHz. We guess a divider
> >     setting based on
> >      > +// HCLK with a +5% drift. If the user uses a different clock
> >     from our
> >      > +// defaults, needs to set the macros on top Valid for
> >     STM32F74xxx/75xxx
> >      > +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
> >     drift)
> >      > +static int guess_mdc_cr(void) {
> >      > + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
> >      > + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
> >     dividers
> >      > + uint32_t hclk = get_hclk(); // Guess system HCLK
> >      > + int result = -1; // Invalid CR value
> >      > + if (hclk < 25000000) {
> >      > + MG_ERROR(("HCLK too low"));
> >      > + } else {
> >      > + for (int i = 0; i < 6; i++) {
> >      > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > + result = crs[i];
> >      > + break;
> >      > + }
> >      > + }
> >      > + if (result < 0) MG_ERROR(("HCLK too high"));
> >      > + }
> >      > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> >      > + return result;
> >      > +}
> >      >
> >      > -struct arp {
> >      > - uint16_t fmt; // Format of hardware address
> >      > - uint16_t pro; // Format of protocol address
> >      > - uint8_t hlen; // Length of hardware address
> >      > - uint8_t plen; // Length of protocol address
> >      > - uint16_t op; // Operation
> >      > - uint8_t sha[6]; // Sender hardware address
> >      > - uint32_t spa; // Sender protocol address
> >      > - uint8_t tha[6]; // Target hardware address
> >      > - uint32_t tpa; // Target protocol address
> >      > -};
> >      > +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_stm32f_data *d =
> >      > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> >      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> >      > + s_ifp = ifp;
> >      >
> >      > -struct tcp {
> >      > - uint16_t sport; // Source port
> >      > - uint16_t dport; // Destination port
> >      > - uint32_t seq; // Sequence number
> >      > - uint32_t ack; // Acknowledgement number
> >      > - uint8_t off; // Data offset
> >      > - uint8_t flags; // TCP flags
> >      > -#define TH_FIN 0x01
> >      > -#define TH_SYN 0x02
> >      > -#define TH_RST 0x04
> >      > -#define TH_PUSH 0x08
> >      > -#define TH_ACK 0x10
> >      > -#define TH_URG 0x20
> >      > -#define TH_ECE 0x40
> >      > -#define TH_CWR 0x80
> >      > - uint16_t win; // Window
> >      > - uint16_t csum; // Checksum
> >      > - uint16_t urp; // Urgent pointer
> >      > -};
> >      > + // Init RX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_rxdesc[i][0] = MG_BIT(31); // Own
> >      > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
> >     address chained
> >      > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
> >     data buffer
> >      > + s_rxdesc[i][3] =
> >      > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + }
> >      >
> >      > -struct udp {
> >      > - uint16_t sport; // Source port
> >      > - uint16_t dport; // Destination port
> >      > - uint16_t len; // UDP length
> >      > - uint16_t csum; // UDP checksum
> >      > -};
> >      > + // Init TX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> >      > + s_txdesc[i][3] =
> >      > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + }
> >      >
> >      > -struct dhcp {
> >      > - uint8_t op, htype, hlen, hops;
> >      > - uint32_t xid;
> >      > - uint16_t secs, flags;
> >      > - uint32_t ciaddr, yiaddr, siaddr, giaddr;
> >      > - uint8_t hwaddr[208];
> >      > - uint32_t magic;
> >      > - uint8_t options[32];
> >      > -};
> >      > + ETH->DMABMR |= MG_BIT(0); // Software reset
> >      > + while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until
> >     done
> >      >
> >      > -#pragma pack(pop)
> >      > + // Set MDC clock divider. If user told us the value, use it.
> >     Otherwise, guess
> >      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
> >     d->mdc_cr;
> >      > + ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
> >      >
> >      > -struct pkt {
> >      > - struct mg_str raw; // Raw packet data
> >      > - struct mg_str pay; // Payload data
> >      > - struct eth *eth;
> >      > - struct llc *llc;
> >      > - struct arp *arp;
> >      > - struct ip *ip;
> >      > - struct ip6 *ip6;
> >      > - struct icmp *icmp;
> >      > - struct tcp *tcp;
> >      > - struct udp *udp;
> >      > - struct dhcp *dhcp;
> >      > -};
> >      > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
> >     not use
> >      > + // hardware checksum. Therefore, descriptor size is 4, not 8
> >      > + // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
> >     MG_BIT(23) |
> >      > + // MG_BIT(25);
> >      > + ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
> >      > + ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause
> >      > + // ETH->MACFFR = MG_BIT(31); // Receive all
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
> >      > + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
> >      > + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
> >      > + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE
> >      > + ETH->MACCR =
> >      > + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE,
> >     Duplex, Fast
> >      > + ETH->DMAOMR =
> >      > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
> >     TSF, RSF
> >      >
> >      > -static void q_copyin(struct queue *q, const uint8_t *buf, size_t
> >     len,
> >      > - size_t head) {
> >      > - size_t left = q->len - head;
> >      > - memcpy(&q->buf[head], buf, left < len ? left : len);
> >      > - if (left < len) memcpy(q->buf, &buf[left], len - left);
> >      > + // MAC address filtering
> >      > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> >      > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> >      > + ((uint32_t) ifp->mac[2] << 16) |
> >      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> >      > + return true;
> >      > }
> >      >
> >      > -static void q_copyout(struct queue *q, uint8_t *buf, size_t len,
> >     size_t tail) {
> >      > - size_t left = q->len - tail;
> >      > - memcpy(buf, &q->buf[tail], left < len ? left : len);
> >      > - if (left < len) memcpy(&buf[left], q->buf, len - left);
> >      > +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t
> >     len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // Frame is too big
> >      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No free descriptors"));
> >      > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> >     ETH->DMASR);
> >      > + len = 0; // All descriptors are busy, fail
> >      > + } else {
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> >      > + s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); //
> >     Chain,FS,LS
> >      > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
> >     take over
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > + }
> >      > + MG_DSB(); // ensure descriptors have been written
> >      > + ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS
> >      > + ETH->DMATPDR = 0; // and resume
> >      > + return len;
> >      > }
> >      >
> >      > -static bool q_write(struct queue *q, const void *buf, size_t len) {
> >      > - bool success = false;
> >      > - size_t left = (q->len - q->head + q->tail - 1) % q->len;
> >      > - if (len + sizeof(size_t) <= left) {
> >      > - q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
> >      > - q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) %
> >     q->len);
> >      > - q->head = (q->head + sizeof(len) + len) % q->len;
> >      > - success = true;
> >      > +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_stm32f_data *d =
> >      > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
> >      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + // tmp = reg with flags set to the most likely situation: 100M
> >     full-duplex
> >      > + // if(link is slow or half) set flags otherwise
> >      > + // reg = tmp
> >      > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M,
> >     Full-duplex
> >      > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
> >      > + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex
> >      > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
> >     register
> >      > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> >      > + maccr & MG_BIT(11) ? "full" : "half"));
> >      > }
> >      > - return success;
> >      > + return up;
> >      > }
> >      >
> >      > -#ifdef MIP_QPROFILE
> >      > -static inline size_t q_space(struct queue *q) {
> >      > - return q->tail > q->head ? q->tail - q->head : q->tail +
> >     (q->len - q->head);
> >      > +#ifdef __riscv
> >      > +__attribute__((interrupt())) // For RISCV CH32V307, which share
> >     the same MAC
> >      > +#endif
> >      > +void ETH_IRQHandler(void);
> >      > +void ETH_IRQHandler(void) {
> >      > + if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop
> >      > + ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag
> >      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
> >      > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
> >      > + (MG_BIT(8) | MG_BIT(9))) &&
> >      > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
> >     frames
> >      > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> >      > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> >      > + // ETH->DMASR);
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > + }
> >      > + s_rxdesc[s_rxno][0] = MG_BIT(31);
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > + }
> >      > + }
> >      > + // Cleanup flags
> >      > + ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary
> >      > + | MG_BIT(7); // Clear possible RBUS while processing
> >      > + ETH->DMARPDR = 0; // and resume RX
> >      > }
> >      > +
> >      > +struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
> >      > + mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
> >      > + mg_tcpip_driver_stm32f_up};
> >      > #endif
> >      >
> >      > -static inline size_t q_avail(struct queue *q) {
> >      > - size_t n = 0;
> >      > - if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n),
> >     q->tail);
> >      > - return n;
> >      > -}
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/stm32h.c"
> >      > +#endif
> >      >
> >      > -static size_t q_read(struct queue *q, void *buf) {
> >      > - size_t n = q_avail(q);
> >      > - if (n > 0) {
> >      > - q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
> >      > - q->tail = (q->tail + sizeof(n) + n) % q->len;
> >      > - }
> >      > - return n;
> >      > -}
> >      >
> >      > -static struct mg_str mkstr(void *buf, size_t len) {
> >      > - struct mg_str str = {(char *) buf, len};
> >      > - return str;
> >      > -}
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> >      > + MG_ENABLE_DRIVER_STM32H
> >      > +struct stm32h_eth {
> >      > + volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
> >      > + RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR,
> >     MACIVIR,
> >      > + RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7],
> >     MACISR,
> >      > + MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2],
> >     MACLCSR,
> >      > + MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR,
> >     RESERVED10,
> >      > + MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR,
> >     MACMDIODR,
> >      > + RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR,
> >     MACA1HR,
> >      > + MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248],
> >     MMCCR,
> >      > + MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR,
> >     MMCTMCGPR,
> >      > + RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
> >      > + RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
> >      > + MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
> >      > + RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
> >      > + RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
> >      > + MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR,
> >     MACSSIR,
> >      > + MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25,
> >     MACTSSR,
> >      > + RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR,
> >     RESERVED28,
> >      > + MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
> >      > + RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR,
> >     MACPPSIR,
> >      > + MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R,
> >     MACSPI2R, MACLMIR,
> >      > + RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55],
> >     MTLTQOMR,
> >      > + MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR,
> >     MTLRQMPOCR, MTLRQDR,
> >      > + RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR,
> >     RESERVED37[60], DMACCR,
> >      > + DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
> >      > + DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
> >      > + DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42,
> >     DMACCARDR,
> >      > + RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR,
> >     RESERVED45[2],
> >      > + DMACMFCR;
> >      > +};
> >      > +#undef ETH
> >      > +#define ETH \
> >      > + ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL
> >     + 0x8000UL))
> >      >
> >      > -static void mkpay(struct pkt *pkt, void *p) {
> >      > - pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] -
> >     (char *) p));
> >      > +#define ETH_PKT_SIZE 1540 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 4 // Descriptor size (words)
> >      > +
> >      > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX
> >     descriptors
> >      > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX
> >     descriptors
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
> >     ethernet buffers
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
> >     ethernet buffers
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +
> >      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + ETH->MACMDIOAR &= (0xF << 8);
> >      > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
> >     16) | 3 << 2;
> >      > + ETH->MACMDIOAR |= MG_BIT(0);
> >      > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
> >      > + return (uint16_t) ETH->MACMDIODR;
> >      > }
> >      >
> >      > -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
> >      > - const uint8_t *p = (const uint8_t *) buf;
> >      > - for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] :
> >     (uint32_t) (p[i] << 8);
> >      > - return sum;
> >      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + ETH->MACMDIODR = val;
> >      > + ETH->MACMDIOAR &= (0xF << 8);
> >      > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
> >     16) | 1 << 2;
> >      > + ETH->MACMDIOAR |= MG_BIT(0);
> >      > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
> >      > }
> >      >
> >      > -static uint16_t csumfin(uint32_t sum) {
> >      > - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
> >      > - return mg_htons(~sum & 0xffff);
> >      > +static uint32_t get_hclk(void) {
> >      > + struct rcc {
> >      > + volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1,
> >     D1CFGR,
> >      > + D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR,
> >     PLL1FRACR,
> >      > + PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR,
> >     D2CCIP1R,
> >      > + D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5,
> >     BDCR, CSR,
> >      > + RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR,
> >     APB1LRSTR,
> >      > + APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR,
> >     RESERVED11[9],
> >      > + RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR,
> >     APB1HENR,
> >      > + APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
> >      > + AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR,
> >     APB4LPENR,
> >      > + RESERVED13[4];
> >      > + } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
> >      > + uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /*
> >     8MHz */,
> >      > + csi = 4000000 /* 4MHz */;
> >      > + unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
> >      > +
> >      > + if (sel == 1) {
> >      > + clk = csi;
> >      > + } else if (sel == 2) {
> >      > + clk = hse;
> >      > + } else if (sel == 3) {
> >      > + uint32_t vco, m, n, p;
> >      > + unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
> >      > + m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
> >      > + n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
> >      > + ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional
> >     mode
> >      > + p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
> >      > + if (src == 1) {
> >      > + clk = csi;
> >      > + } else if (src == 2) {
> >      > + clk = hse;
> >      > + } else {
> >      > + clk = hsi;
> >      > + clk >>= ((rcc->CR & 3) >> 3);
> >      > + }
> >      > + vco = (uint32_t) ((uint64_t) clk * n / m);
> >      > + clk = vco / p;
> >      > + } else {
> >      > + clk = hsi;
> >      > + clk >>= ((rcc->CR & 3) >> 3);
> >      > + }
> >      > + const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
> >      > + uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
> >      > + if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
> >      > + MG_DEBUG(("D1 CLK: %u", clk));
> >      > + uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
> >      > + if (hpre < 8) return clk;
> >      > + return ((uint32_t) clk) >> cptab[hpre - 8];
> >      > +}
> >      > +
> >      > +// Guess CR from AHB1 clock. MDC clock is generated from the ETH
> >     peripheral
> >      > +// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB
> >     clock can
> >      > +// be derived from HSI or CSI (internal RC) clocks, and those
> >     can go above
> >      > +// specs, the datasheets specify a range of frequencies and
> >     activate one of a
> >      > +// series of dividers to keep the MDC clock safely below 2.5MHz.
> >     We guess a
> >      > +// divider setting based on HCLK with some drift. If the user
> >     uses a different
> >      > +// clock from our defaults, needs to set the macros on top.
> >     Valid for
> >      > +// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock
> >     has a 7.5 %
> >      > +// worst case drift @ max temp)
> >      > +static int guess_mdc_cr(void) {
> >      > + const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR
> >     values
> >      > + const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective
> >     HCLK dividers
> >      > + uint32_t hclk = get_hclk(); // Guess system HCLK
> >      > + int result = -1; // Invalid CR value
> >      > + for (int i = 0; i < 6; i++) {
> >      > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > + result = crs[i];
> >      > + break;
> >      > + }
> >      > + }
> >      > + if (result < 0) MG_ERROR(("HCLK too high"));
> >      > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
> >      > + return result;
> >      > }
> >      >
> >      > -static uint16_t ipcsum(const void *buf, size_t len) {
> >      > - uint32_t sum = csumup(0, buf, len);
> >      > - return csumfin(sum);
> >      > -}
> >      > +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_stm32h_data *d =
> >      > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> >      > + s_ifp = ifp;
> >      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> >      > + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
> >      >
> >      > -// ARP cache is organised as a doubly linked list. A successful
> >     cache lookup
> >      > -// moves an entry to the head of the list. New entries are added
> >     by replacing
> >      > -// the last entry in the list with a new IP/MAC.
> >      > -// ARP cache format: | prev | next | Entry0 | Entry1 | .... |
> >     EntryN |
> >      > -// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
> >      > -// prev and next are 1-byte offsets in the cache, so cache size
> >     is max 256 bytes
> >      > -// ARP entry size is 12 bytes
> >      > -static void arp_cache_init(uint8_t *p, int n, int size) {
> >      > - for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i
> >     - 1) * size);
> >      > - for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i
> >     + 1) * size);
> >      > - p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
> >      > - p[1] = p[3 + (n - 1) * size] = 2;
> >      > -}
> >      > + // Init RX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
> >     data buffer
> >      > + s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN,
> >     IOC, BUF1V
> >      > + }
> >      >
> >      > -#if 0
> >      > -static inline void arp_cache_dump(const uint8_t *p) {
> >      > - MG_INFO(("ARP cache:"));
> >      > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
> >     + 1]) {
> >      > - MG_INFO((" %I -> %A", 4, &p[j + 2], &p[j + 6]));
> >      > + // Init TX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> >      > }
> >      > -}
> >      > -#endif
> >      >
> >      > -static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF,
> >     0xFF, 0xFF};
> >      > + ETH->DMAMR |= MG_BIT(0); // Software reset
> >      > + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done
> >      >
> >      > -static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
> >      > - uint8_t *p = ifp->arp_cache;
> >      > - if (ip == 0) return NULL;
> >      > - // use broadcast MAC for local and global broadcast IP
> >      > - if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
> >      > - return (uint8_t *) bcastmac;
> >      > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
> >     + 1]) {
> >      > - if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
> >      > - p[1] = j, p[0] = p[j]; // Found entry! Point list head to us
> >      > - // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
> >      > - return p + j + 6; // And return MAC address
> >      > - }
> >      > - }
> >      > - return NULL;
> >      > -}
> >      > + // Set MDC clock divider. If user told us the value, use it.
> >     Otherwise, guess
> >      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
> >     d->mdc_cr;
> >      > + ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
> >      > +
> >      > + // NOTE(scaprile): We do not use timing facilities so the DMA
> >     engine does not
> >      > + // re-write buffer address
> >      > + ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset
> >     value)
> >      > + ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this
> >     actually needed
> >      > + ETH->MACIER = 0; // Do not enable additional irq sources (reset
> >     value)
> >      > + ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause
> >      > + // ETH->MACPFR = MG_BIT(31); // Receive all
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + mg_phy_init(&phy, phy_addr, phy_conf);
> >      > + ETH->DMACRDLAR =
> >      > + (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address
> >      > + ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length
> >      > + ETH->DMACRDTPR =
> >      > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
> >      > + 1]; // last valid descriptor address
> >      > + ETH->DMACTDLAR =
> >      > + (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address
> >      > + ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length
> >      > + ETH->DMACTDTPR =
> >      > + (uint32_t) (uintptr_t) s_txdesc; // first available descriptor
> >     address
> >      > + ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table)
> >     (reset value)
> >      > + ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE
> >      > + ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
> >      > + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved
> >      > + ETH->MTLTQOMR |= MG_BIT(1); // TSF
> >      > + ETH->MTLRQOMR |= MG_BIT(5); // RSF
> >      > + ETH->DMACTCR |= MG_BIT(0); // ST
> >      > + ETH->DMACRCR |= MG_BIT(0); // SR
> >      >
> >      > -static void arp_cache_add(struct mip_if *ifp, uint32_t ip,
> >     uint8_t mac[6]) {
> >      > - uint8_t *p = ifp->arp_cache;
> >      > - if (ip == 0 || ip == ~0U) return; // Bad IP
> >      > - if (arp_cache_find(ifp, ip) != NULL) return; // Already exists,
> >     do nothing
> >      > - memcpy(p + p[0] + 2, &ip, sizeof(ip)); // Replace last entry:
> >     IP address
> >      > - memcpy(p + p[0] + 6, mac, 6); // And MAC address
> >      > - p[1] = p[0], p[0] = p[p[1]]; // Point list head to us
> >      > - MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
> >      > + // MAC address filtering
> >      > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> >      > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
> >      > + ((uint32_t) ifp->mac[2] << 16) |
> >      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> >      > + return true;
> >      > }
> >      >
> >      > -static size_t ether_output(struct mip_if *ifp, size_t len) {
> >      > - // size_t min = 64; // Pad short frames to 64 bytes (minimum
> >     Ethernet size)
> >      > - // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len
> >     = min;
> >      > - // mg_hexdump(ifp->tx.ptr, len);
> >      > - return ifp->driver->tx(ifp->tx.ptr, len, ifp);
> >      > +static uint32_t s_txno;
> >      > +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t
> >     len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // Frame is too big
> >      > + } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
> >      > + s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X",
> >     s_txdesc[i][3]));
> >      > + len = 0; // All descriptors are busy, fail
> >      > + } else {
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno][2] = (uint32_t) len; // Set data len
> >      > + s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD
> >      > + s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA
> >     take over
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > + }
> >      > + ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS
> >      > + ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; //
> >     and resume
> >      > + return len;
> >      > + (void) ifp;
> >      > }
> >      >
> >      > -static void arp_ask(struct mip_if *ifp, uint32_t ip) {
> >      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> >      > - struct arp *arp = (struct arp *) (eth + 1);
> >      > - memset(eth->dst, 255, sizeof(eth->dst));
> >      > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
> >      > - eth->type = mg_htons(0x806);
> >      > - memset(arp, 0, sizeof(*arp));
> >      > - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
> >      > - arp->plen = 4;
> >      > - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
> >      > - memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
> >      > - ether_output(ifp, PDIFF(eth, arp + 1));
> >      > +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_stm32h_data *d =
> >      > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
> >      > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + // tmp = reg with flags set to the most likely situation: 100M
> >     full-duplex
> >      > + // if(link is slow or half) set flags otherwise
> >      > + // reg = tmp
> >      > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M,
> >     Full-duplex
> >      > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
> >      > + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex
> >      > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
> >     register
> >      > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
> >      > + maccr & MG_BIT(13) ? "full" : "half"));
> >      > + }
> >      > + return up;
> >      > }
> >      >
> >      > -static void onstatechange(struct mip_if *ifp) {
> >      > - if (ifp->state == MIP_STATE_READY) {
> >      > - MG_INFO(("READY, IP: %I", 4, &ifp->ip));
> >      > - MG_INFO((" GW: %I", 4, &ifp->gw));
> >      > - if (ifp->lease_expire > ifp->now) {
> >      > - MG_INFO(
> >      > - (" Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
> >      > +void ETH_IRQHandler(void);
> >      > +static uint32_t s_rxno;
> >      > +void ETH_IRQHandler(void) {
> >      > + if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop
> >      > + ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag
> >      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > + if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done
> >      > + if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
> >      > + (MG_BIT(28) | MG_BIT(29))) &&
> >      > + !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored
> >     frames
> >      > + uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
> >      > + // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len,
> >     s_rxdesc[s_rxno][3],
> >      > + // ETH->DMACSR));
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > + }
> >      > + s_rxdesc[s_rxno][3] =
> >      > + MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > }
> >      > - arp_ask(ifp, ifp->gw);
> >      > - } else if (ifp->state == MIP_STATE_UP) {
> >      > - MG_ERROR(("Link up"));
> >      > - } else if (ifp->state == MIP_STATE_DOWN) {
> >      > - MG_ERROR(("Link down"));
> >      > }
> >      > + ETH->DMACSR =
> >      > + MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
> >      > + ETH->DMACRDTPR =
> >      > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and
> >     resume RX
> >      > }
> >      >
> >      > -static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto,
> >     uint32_t ip_src,
> >      > - uint32_t ip_dst, size_t plen) {
> >      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> >      > - struct ip *ip = (struct ip *) (eth + 1);
> >      > - uint8_t *mac = arp_cache_find(ifp, ip_dst); // Dst IP in ARP
> >     cache ?
> >      > - if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
> >      > - arp_ask(ifp, ip_dst); // Same net, lookup
> >      > - if (!mac) mac = arp_cache_find(ifp, ifp->gw); // Use gateway MAC
> >      > - if (!mac) arp_ask(ifp, ifp->gw); // Not found? lookup
> >      > - if (mac) memcpy(eth->dst, mac, sizeof(eth->dst)); // Found? Use it
> >      > - if (!mac) memset(eth->dst, 255, sizeof(eth->dst)); // No? Use
> >     broadcast
> >      > - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // TODO(cpq): ARP
> >     lookup
> >      > - eth->type = mg_htons(0x800);
> >      > - memset(ip, 0, sizeof(*ip));
> >      > - ip->ver = 0x45; // Version 4, header length 5 words
> >      > - ip->frag = 0x40; // Don't fragment
> >      > - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
> >      > - ip->ttl = 64;
> >      > - ip->proto = proto;
> >      > - ip->src = ip_src;
> >      > - ip->dst = ip_dst;
> >      > - ip->csum = ipcsum(ip, sizeof(*ip));
> >      > - return ip;
> >      > -}
> >      > +struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
> >      > + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
> >      > + mg_tcpip_driver_stm32h_up};
> >      > +#endif
> >      >
> >      > -static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t
> >     sport,
> >      > - uint32_t ip_dst, uint16_t dport, const void *buf,
> >      > - size_t len) {
> >      > - struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len +
> >     sizeof(struct udp));
> >      > - struct udp *udp = (struct udp *) (ip + 1);
> >      > - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
> >      > - udp->sport = sport;
> >      > - udp->dport = dport;
> >      > - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
> >      > - udp->csum = 0;
> >      > - uint32_t cs = csumup(0, udp, sizeof(*udp));
> >      > - cs = csumup(cs, buf, len);
> >      > - cs = csumup(cs, &ip->src, sizeof(ip->src));
> >      > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> >      > - cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
> >      > - udp->csum = csumfin(cs);
> >      > - memmove(udp + 1, buf, len);
> >      > - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
> >      > - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
> >     sizeof(*udp) + len);
> >      > -}
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/tm4c.c"
> >      > +#endif
> >      >
> >      > -static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
> >      > - uint8_t *opts, size_t optslen) {
> >      > - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> >      > - dhcp.magic = mg_htonl(0x63825363);
> >      > - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
> >      > - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
> >      > - memcpy(&dhcp.options, opts, optslen);
> >      > - tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp,
> >     sizeof(dhcp));
> >      > -}
> >      >
> >      > -static void tx_dhcp_request(struct mip_if *ifp, uint32_t src,
> >     uint32_t dst) {
> >      > - uint8_t opts[] = {
> >      > - 53, 1, 3, // Type: DHCP request
> >      > - 55, 2, 1, 3, // GW and mask
> >      > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
> >      > - 54, 4, 0, 0, 0, 0, // DHCP server ID
> >      > - 50, 4, 0, 0, 0, 0, // Requested IP
> >      > - 255 // End of options
> >      > - };
> >      > - memcpy(opts + 14, &dst, sizeof(dst));
> >      > - memcpy(opts + 20, &src, sizeof(src));
> >      > - tx_dhcp(ifp, src, dst, opts, sizeof(opts));
> >      > -}
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
> >     MG_ENABLE_DRIVER_TM4C
> >      > +struct tm4c_emac {
> >      > + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
> >     EMACHASHTBLL,
> >      > + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
> >     EMACSTATUS,
> >      > + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
> >     EMACADDR0H,
> >      > + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
> >     EMACADDR3H,
> >      > + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
> >      > + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
> >      > + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
> >     RESERVED6[4],
> >      > + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
> >     EMACRXCNTCRCERR,
> >      > + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
> >      > + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
> >     EMACSUBSECINC,
> >      > + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
> >      > + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
> >      > + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
> >      > + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
> >     EMACTXDLADDR,
> >      > + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
> >      > + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
> >     EMACHOSRXBA,
> >      > + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
> >      > + EMACEPHYIM, EMACEPHYIMSC;
> >      > +};
> >      > +#undef EMAC
> >      > +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
> >      >
> >      > -static void tx_dhcp_discover(struct mip_if *ifp) {
> >      > - uint8_t opts[] = {
> >      > - 53, 1, 1, // Type: DHCP discover
> >      > - 55, 2, 1, 3, // Parameters: ip, mask
> >      > - 255 // End of options
> >      > - };
> >      > - tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
> >      > - MG_DEBUG(("DHCP discover sent"));
> >      > -}
> >      > +#define ETH_PKT_SIZE 1540 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 4 // Descriptor size (words)
> >      >
> >      > -static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
> >      > - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
> >      > - // ARP request. Make a response, then send
> >      > - MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4,
> >     &pkt->arp->spa, 4,
> >      > - &pkt->arp->tpa));
> >      > - struct eth *eth = (struct eth *) ifp->tx.ptr;
> >      > - struct arp *arp = (struct arp *) (eth + 1);
> >      > - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
> >      > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
> >      > - eth->type = mg_htons(0x806);
> >      > - *arp = *pkt->arp;
> >      > - arp->op = mg_htons(2);
> >      > - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
> >      > - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
> >      > - arp->tpa = pkt->arp->spa;
> >      > - arp->spa = ifp->ip;
> >      > - MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
> >      > - ether_output(ifp, PDIFF(eth, arp + 1));
> >      > - } else if (pkt->arp->op == mg_htons(2)) {
> >      > - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
> >     0) return;
> >      > - // MG_INFO(("ARP RESPONSE"));
> >      > - arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
> >      > - }
> >      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
> >     ethernet buffers
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
> >     ethernet buffers
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +enum {
> >      > + EPHY_ADDR = 0,
> >      > + EPHYBMCR = 0,
> >      > + EPHYBMSR = 1,
> >      > + EPHYSTS = 16
> >      > +}; // PHY constants
> >      > +
> >      > +static inline void tm4cspin(volatile uint32_t count) {
> >      > + while (count--) (void) 0;
> >      > }
> >      >
> >      > -static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
> >      > - // MG_DEBUG(("ICMP %d", (int) len));
> >      > - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
> >     ifp->ip) {
> >      > - size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
> >     sizeof(struct icmp);
> >      > - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
> >      > - if (plen > space) plen = space;
> >      > - struct ip *ip =
> >      > - tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
> >      > - struct icmp *icmp = (struct icmp *) (ip + 1);
> >      > - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
> >      > - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX
> >      > - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
> >      > - ether_output(ifp, hlen + plen);
> >      > - }
> >      > +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
> >      > + EMAC->EMACMIIADDR &= (0xf << 2);
> >      > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
> >     << 6);
> >      > + EMAC->EMACMIIADDR |= MG_BIT(0);
> >      > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
> >      > + return EMAC->EMACMIIDATA;
> >      > }
> >      >
> >      > -static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
> >      > - uint32_t ip = 0, gw = 0, mask = 0;
> >      > - uint8_t *p = pkt->dhcp->options,
> >      > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> >      > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> >      > - while (p + 1 < end && p[0] != 255) { // Parse options
> >      > - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
> >     Mask
> >      > - memcpy(&mask, p + 2, sizeof(mask));
> >      > - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
> >     { // GW
> >      > - memcpy(&gw, p + 2, sizeof(gw));
> >      > - ip = pkt->dhcp->yiaddr;
> >      > - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
> >      > - uint32_t lease = 0;
> >      > - memcpy(&lease, p + 2, sizeof(lease));
> >      > - ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
> >      > - }
> >      > - p += p[1] + 2;
> >      > - }
> >      > - if (ip && mask && gw && ifp->ip == 0) {
> >      > - arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *)
> >     pkt->raw.ptr)->src);
> >      > - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
> >      > - ifp->state = MIP_STATE_READY;
> >      > - onstatechange(ifp);
> >      > - tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
> >      > - }
> >      > +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
> >     val) {
> >      > + EMAC->EMACMIIDATA = val;
> >      > + EMAC->EMACMIIADDR &= (0xf << 2);
> >      > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
> >     << 6) | MG_BIT(1);
> >      > + EMAC->EMACMIIADDR |= MG_BIT(0);
> >      > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
> >      > }
> >      >
> >      > -// Simple DHCP server that assigns a next IP address: ifp->ip + 1
> >      > -static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
> >      > - uint8_t op = 0, *p = pkt->dhcp->options,
> >      > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
> >      > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
> >      > - // struct dhcp *req = pkt->dhcp;
> >      > - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
> >      > - res.yiaddr = ifp->ip;
> >      > - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
> >      > - while (p + 1 < end && p[0] != 255) { // Parse options
> >      > - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
> >      > - op = p[2];
> >      > +static uint32_t get_sysclk(void) {
> >      > + struct sysctl {
> >      > + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
> >     PLLFREQ0,
> >      > + PLLFREQ1;
> >      > + } *sysctl = (struct sysctl *) 0x400FE000;
> >      > + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
> >     25000000 /* 25MHz */;
> >      > + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
> >      > + uint32_t fin, vco, mdiv, n, q, psysdiv;
> >      > + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
> >      > + if (pllsrc == 0) {
> >      > + clk = piosc;
> >      > + } else if (pllsrc == 3) {
> >      > + clk = mosc;
> >      > + } else {
> >      > + MG_ERROR(("Unsupported clock source"));
> >      > }
> >      > - p += p[1] + 2;
> >      > - }
> >      > - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
> >      > - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
> >     DHCP ACK
> >      > - uint8_t opts[] = {
> >      > - 53, 1, msg, // Message type
> >      > - 1, 4, 0, 0, 0, 0, // Subnet mask
> >      > - 54, 4, 0, 0, 0, 0, // Server ID
> >      > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
> >      > - 51, 4, 255, 255, 255, 255, // Lease time
> >      > - 255 // End of options
> >      > - };
> >      > - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
> >      > - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
> >      > - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
> >      > - memcpy(&res.options, opts, sizeof(opts));
> >      > - res.magic = pkt->dhcp->magic;
> >      > - res.xid = pkt->dhcp->xid;
> >      > - arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
> >      > - tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
> >     mg_htons(68),
> >      > - &res, sizeof(res));
> >      > + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
> >      > + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
> >      > + fin = clk / ((q + 1) * (n + 1));
> >      > + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
> >      > + 0; // mint + (mfrac / 1024); MFRAC not supported
> >      > + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
> >      > + vco = (uint32_t) ((uint64_t) fin * mdiv);
> >      > + return vco / (psysdiv + 1);
> >      > }
> >      > -}
> >      > -
> >      > -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
> >     pkt *pkt,
> >      > - bool lsn) {
> >      > - struct mg_connection *c = NULL;
> >      > - for (c = mgr->conns; c != NULL; c = c->next) {
> >      > - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
> >     break;
> >      > - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
> >      > - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
> >      > - break;
> >      > + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
> >      > + if (oscsrc == 0) {
> >      > + clk = piosc;
> >      > + } else if (oscsrc == 3) {
> >      > + clk = mosc;
> >      > + } else {
> >      > + MG_ERROR(("Unsupported clock source"));
> >      > }
> >      > - return c;
> >      > + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
> >      > + return clk / (osysdiv + 1);
> >      > }
> >      >
> >      > -static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
> >      > - struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
> >      > - if (c == NULL) {
> >      > - // No UDP listener on this port. Should send ICMP, but keep
> >     silent.
> >      > - } else if (c != NULL) {
> >      > - c->rem.port = pkt->udp->sport;
> >      > - c->rem.ip = pkt->ip->src;
> >      > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
> >      > - mg_error(c, "max_recv_buf_size reached");
> >      > - } else if (c->recv.size - c->recv.len < pkt->pay.len &&
> >      > - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
> >      > - mg_error(c, "oom");
> >      > - } else {
> >      > - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
> >      > - c->recv.len += pkt->pay.len;
> >      > - mg_call(c, MG_EV_READ, &pkt->pay.len);
> >      > +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
> >     (AHB); as per
> >      > +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
> >     clock can be
> >      > +// derived from the PIOSC (internal RC), and it can go above
> >     specs, the
> >      > +// datasheets specify a range of frequencies and activate one of
> >     a series of
> >      > +// dividers to keep the MDC clock safely below 2.5MHz. We guess
> >     a divider
> >      > +// setting based on SYSCLK with a +5% drift. If the user uses a
> >     different clock
> >      > +// from our defaults, needs to set the macros on top Valid for
> >     TM4C129x (20.7)
> >      > +// (4.5% worst case drift)
> >      > +// The PHY receives the main oscillator (MOSC) (20.3.1)
> >      > +static int guess_mdc_cr(void) {
> >      > + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
> >      > + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
> >      > + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
> >      > + int result = -1; // Invalid CR value
> >      > + if (sysclk < 25000000) {
> >      > + MG_ERROR(("SYSCLK too low"));
> >      > + } else {
> >      > + for (int i = 0; i < 4; i++) {
> >      > + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
> >      > + result = crs[i];
> >      > + break;
> >      > + }
> >      > }
> >      > + if (result < 0) MG_ERROR(("SYSCLK too high"));
> >      > }
> >      > + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
> >      > + return result;
> >      > }
> >      >
> >      > -static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip,
> >     uint8_t flags,
> >      > - uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
> >      > - const void *buf, size_t len) {
> >      > - struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct
> >     tcp) + len);
> >      > - struct tcp *tcp = (struct tcp *) (ip + 1);
> >      > - memset(tcp, 0, sizeof(*tcp));
> >      > - if (buf != NULL && len) memmove(tcp + 1, buf, len);
> >      > - tcp->sport = sport;
> >      > - tcp->dport = dport;
> >      > - tcp->seq = seq;
> >      > - tcp->ack = ack;
> >      > - tcp->flags = flags;
> >      > - tcp->win = mg_htons(8192);
> >      > - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
> >      > - uint32_t cs = 0;
> >      > - uint16_t n = (uint16_t) (sizeof(*tcp) + len);
> >      > - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
> >     (n & 255)};
> >      > - cs = csumup(cs, tcp, n);
> >      > - cs = csumup(cs, &ip->src, sizeof(ip->src));
> >      > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
> >      > - cs = csumup(cs, pseudo, sizeof(pseudo));
> >      > - tcp->csum = csumfin(cs);
> >      > - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
> >      > -}
> >      > +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_tm4c_data *d =
> >      > + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
> >      > + s_ifp = ifp;
> >      >
> >      > -static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt,
> >     uint8_t flags,
> >      > - uint32_t seq, const void *buf, size_t len) {
> >      > - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
> >      > - return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport,
> >     pkt->tcp->sport, seq,
> >      > - mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
> >      > -}
> >      > + // Init RX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_rxdesc[i][0] = MG_BIT(31); // Own
> >      > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
> >     address chained
> >      > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
> >     data buffer
> >      > + s_rxdesc[i][3] =
> >      > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
> >      > + }
> >      >
> >      > -static void settmout(struct mg_connection *c, uint8_t type) {
> >      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS :
> >     MIP_TCP_KEEPALIVE_MS;
> >      > - s->timer = ifp->now + n;
> >      > - s->ttype = type;
> >      > - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
> >      > -}
> >      > + // Init TX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
> >      > + s_txdesc[i][3] =
> >      > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
> >      > + }
> >      >
> >      > -static struct mg_connection *accept_conn(struct mg_connection *lsn,
> >      > - struct pkt *pkt) {
> >      > - struct mg_connection *c = mg_alloc_conn(lsn->mgr);
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
> >     mg_ntohl(pkt->tcp->seq);
> >      > - settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > - c->rem.ip = pkt->ip->src;
> >      > - c->rem.port = pkt->tcp->sport;
> >      > - MG_DEBUG(
> >      > - ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip,
> >     mg_ntohs(c->rem.port)));
> >      > - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
> >      > - c->is_accepted = 1;
> >      > - c->is_hexdumping = lsn->is_hexdumping;
> >      > - c->pfn = lsn->pfn;
> >      > - c->loc = lsn->loc;
> >      > - c->pfn_data = lsn->pfn_data;
> >      > - c->fn = lsn->fn;
> >      > - c->fn_data = lsn->fn_data;
> >      > - mg_call(c, MG_EV_OPEN, NULL);
> >      > - mg_call(c, MG_EV_ACCEPT, NULL);
> >      > - return c;
> >      > + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset
> >      > + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); //
> >     Wait until done
> >      > +
> >      > + // Set MDC clock divider. If user told us the value, use it.
> >     Otherwise, guess
> >      > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
> >     d->mdc_cr;
> >      > + EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
> >      > +
> >      > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
> >     not use
> >      > + // hardware checksum. Therefore, descriptor size is 4, not 8
> >      > + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
> >     MG_BIT(23) | MG_BIT(25);
> >      > + EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
> >      > + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause
> >      > + // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all
> >      > + // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
> >      > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset
> >     internal PHY (EPHY)
> >      > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set
> >     autonegotiation
> >      > + EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
> >     descriptors
> >      > + EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
> >     descriptors
> >      > + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE
> >      > + EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) |
> >     MG_BIT(14); // RE, TE, Duplex, Fast
> >      > + EMAC->EMACDMAOPMODE =
> >      > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
> >     TSF, RSF
> >      > + EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
> >      > + EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
> >      > + ((uint32_t) ifp->mac[2] << 16) |
> >      > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
> >      > + // NOTE(scaprile) There are 3 additional slots for filtering,
> >     disabled by
> >      > + // default. This also applies to the STM32 driver (at least for
> >     F7)
> >      > + return true;
> >      > }
> >      >
> >      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
> >      > - if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len -
> >     max_headers_len;
> >      > - if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port,
> >     c->rem.port,
> >      > - mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
> >      > - s->seq += (uint32_t) len;
> >      > - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > +static uint32_t s_txno;
> >      > +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // fail
> >      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No descriptors available"));
> >      > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
> >      > + // EMAC->EMACDMARIS);
> >      > + len = 0; // fail
> >      > } else {
> >      > - return MG_IO_ERR;
> >      > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
> >      > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
> >      > + s_txdesc[s_txno][0] =
> >      > + MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); //
> >     Chain,FS,LS,IC
> >      > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
> >     take over
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > }
> >      > - return (long) len;
> >      > + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior
> >     TU/UNF
> >      > + EMAC->EMACTXPOLLD = 0; // and resume
> >      > + return len;
> >      > + (void) ifp;
> >      > }
> >      >
> >      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - if (s->raw.len == 0) return MG_IO_WAIT;
> >      > - if (len > s->raw.len) len = s->raw.len;
> >      > - memcpy(buf, s->raw.buf, len);
> >      > - mg_iobuf_del(&s->raw, 0, len);
> >      > - MG_DEBUG(("%lu", len));
> >      > - return (long) len;
> >      > +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
> >      > + uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
> >      > + bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
> >      > + // tmp = reg with flags set to the most likely situation: 100M
> >     full-duplex
> >      > + // if(link is slow or half) set flags otherwise
> >      > + // reg = tmp
> >      > + uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); //
> >     100M, Full-duplex
> >      > + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M
> >      > + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex
> >      > + EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with
> >     this register
> >      > + MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 :
> >     10,
> >      > + emaccfg & MG_BIT(11) ? "full" : "half"));
> >      > + }
> >      > + return up;
> >      > }
> >      >
> >      > -static void read_conn(struct mg_connection *c, struct pkt *pkt) {
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
> >      > - uint32_t seq = mg_ntohl(pkt->tcp->seq);
> >      > - s->raw.align = c->recv.align;
> >      > - if (pkt->tcp->flags & TH_FIN) {
> >      > - s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq =
> >     mg_htonl(pkt->tcp->ack);
> >      > - c->is_closing = 1;
> >      > - } else if (pkt->pay.len == 0) {
> >      > - // TODO(cpq): handle this peer's ACK
> >      > - } else if (seq != s->ack) {
> >      > - // TODO(cpq): peer sent us SEQ which we don't expect.
> >     Retransmit rather
> >      > - // than close this connection
> >      > - mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
> >      > - } else if (io->size - io->len < pkt->pay.len &&
> >      > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> >      > - mg_error(c, "oom");
> >      > - } else {
> >      > - // Copy TCP payload into the IO buffer. If the connection is
> >     plain text, we
> >      > - // copy to c->recv. If the connection is TLS, this data is
> >     encrypted,
> >      > - // therefore we copy that encrypted data to the s->raw iobuffer
> >     instead,
> >      > - // and then call mg_tls_recv() to decrypt it. NOTE:
> >     mg_tls_recv() will
> >      > - // call back mg_io_recv() which grabs raw data from s->raw
> >      > - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
> >      > - io->len += pkt->pay.len;
> >      > -
> >      > - MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
> >     s->ack));
> >      > - // Advance ACK counter
> >      > - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
> >      > -#if 0
> >      > - // Send ACK immediately
> >      > - MG_DEBUG((" imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
> >      > - tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK,
> >     c->loc.port,
> >      > - c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> >      > -#else
> >      > - // if not already running, setup a timer to send an ACK later
> >      > - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
> >      > -#endif
> >      > -
> >      > - if (c->is_tls) {
> >      > - // TLS connection. Make room for decrypted data in c->recv
> >      > - io = &c->recv;
> >      > - if (io->size - io->len < pkt->pay.len &&
> >      > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
> >      > - mg_error(c, "oom");
> >      > - } else {
> >      > - // Decrypt data directly into c->recv
> >      > - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
> >      > - if (n == MG_IO_ERR) {
> >      > - mg_error(c, "TLS recv error");
> >      > - } else if (n > 0) {
> >      > - // Decrypted successfully - trigger MG_EV_READ
> >      > - io->len += (size_t) n;
> >      > - mg_call(c, MG_EV_READ, &n);
> >      > - }
> >      > +void EMAC0_IRQHandler(void);
> >      > +static uint32_t s_rxno;
> >      > +void EMAC0_IRQHandler(void) {
> >      > + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop
> >      > + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag
> >      > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
> >     not forever
> >      > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
> >      > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
> >     (MG_BIT(8) | MG_BIT(9))) &&
> >      > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
> >     frames
> >      > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
> >      > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
> >      > + // EMAC->EMACDMARIS);
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
> >      > }
> >      > - } else {
> >      > - // Plain text connection, data is already in c->recv, trigger
> >     MG_EV_READ
> >      > - mg_call(c, MG_EV_READ, &pkt->pay.len);
> >      > + s_rxdesc[s_rxno][0] = MG_BIT(31);
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > }
> >      > }
> >      > + EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while
> >     processing
> >      > + EMAC->EMACRXPOLLD = 0; // and resume RX
> >      > }
> >      >
> >      > -static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
> >      > - struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
> >      > - struct connstate *s = c == NULL ? NULL : (struct connstate *)
> >     (c + 1);
> >      > -#if 0
> >      > - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
> >     pkt->pay.len));
> >      > +struct mg_tcpip_driver mg_tcpip_driver_tm4c =
> >     {mg_tcpip_driver_tm4c_init,
> >      > + mg_tcpip_driver_tm4c_tx, NULL,
> >      > + mg_tcpip_driver_tm4c_up};
> >      > #endif
> >      > - if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN
> >     | TH_ACK)) {
> >      > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
> >     mg_ntohl(pkt->tcp->seq) + 1;
> >      > - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > - c->is_connecting = 0; // Client connected
> >      > - settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > - mg_call(c, MG_EV_CONNECT, NULL); // Let user know
> >      > - } else if (c != NULL && c->is_connecting) {
> >      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > - } else if (c != NULL && pkt->tcp->flags & TH_RST) {
> >      > - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> >      > - } else if (c != NULL) {
> >      > -#if 0
> >      > - MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
> >      > - 4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
> >      > - 4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
> >      > - mg_hexdump(pkt->pay.buf, pkt->pay.len);
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/w5500.c"
> >      > #endif
> >      > - s->tmiss = 0; // Reset missed keep-alive counter
> >      > - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
> >      > - settmout(c, MIP_TTYPE_KEEPALIVE); // unless a former ACK
> >     timeout is pending
> >      > - read_conn(c, pkt); // Override timer with ACK timeout if needed
> >      > - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
> >      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > - } else if (pkt->tcp->flags & TH_RST) {
> >      > - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
> >      > - // ignore RST if not connected
> >      > - } else if (pkt->tcp->flags & TH_SYN) {
> >      > - // Use peer's source port as ISN, in order to recognise the
> >     handshake
> >      > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
> >      > - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
> >      > - } else if (pkt->tcp->flags & TH_FIN) {
> >      > - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
> >     + 1U) {
> >      > - accept_conn(c, pkt);
> >      > - } else if (!c->is_accepted ) { // no peer
> >      > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
> >      > - } else {
> >      > - // MG_DEBUG(("dropped silently.."));
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
> >     MG_ENABLE_DRIVER_W5500
> >      > +
> >      > +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
> >      > +
> >      > +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr,
> >      > + bool wr, void *buf, size_t len) {
> >      > + size_t i;
> >      > + uint8_t *p = (uint8_t *) buf;
> >      > + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
> >      > + (uint8_t) ((block << 3) | (wr ? 4 : 0))};
> >      > + s->begin(s->spi);
> >      > + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
> >      > + for (i = 0; i < len; i++) {
> >      > + uint8_t r = s->txn(s->spi, p[i]);
> >      > + if (!wr) p[i] = r;
> >      > }
> >      > + s->end(s->spi);
> >      > }
> >      >
> >      > -static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
> >      > - // MG_DEBUG(("IP %d", (int) pkt->pay.len));
> >      > - if (pkt->ip->proto == 1) {
> >      > - pkt->icmp = (struct icmp *) (pkt->ip + 1);
> >      > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> >      > - mkpay(pkt, pkt->icmp + 1);
> >      > - rx_icmp(ifp, pkt);
> >      > - } else if (pkt->ip->proto == 17) {
> >      > - pkt->udp = (struct udp *) (pkt->ip + 1);
> >      > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
> >      > - mkpay(pkt, pkt->udp + 1);
> >      > - if (pkt->udp->dport == mg_htons(68)) {
> >      > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> >      > - mkpay(pkt, pkt->dhcp + 1);
> >      > - rx_dhcp_client(ifp, pkt);
> >      > - } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
> >     mg_htons(67)) {
> >      > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
> >      > - mkpay(pkt, pkt->dhcp + 1);
> >      > - rx_dhcp_server(ifp, pkt);
> >      > - } else {
> >      > - rx_udp(ifp, pkt);
> >      > +// clang-format off
> >      > +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
> >     true, buf, len); }
> >      > +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
> >      > +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >>
> >     8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf,
> >     sizeof(buf)); }
> >      > +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
> >     false, buf, len); }
> >      > +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
> >     return r; }
> >      > +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block,
> >     uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
> >     buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
> >      > +// clang-format on
> >      > +
> >      > +static size_t w5500_rx(void *buf, size_t buflen, struct
> >     mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> >      > + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
> >     recv len
> >      > + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
> >     it is stable
> >      > + // printf("RSR: %d\n", (int) n);
> >      > + if (n > 0) {
> >      > + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
> >      > + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
> >      > + if (n <= len + 2 && n > 1) {
> >      > + r = (uint16_t) (n - 2);
> >      > + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
> >      > }
> >      > - } else if (pkt->ip->proto == 6) {
> >      > - pkt->tcp = (struct tcp *) (pkt->ip + 1);
> >      > - if (pkt->pay.len < sizeof(*pkt->tcp)) return;
> >      > - mkpay(pkt, pkt->tcp + 1);
> >      > - uint16_t iplen = mg_ntohs(pkt->ip->len);
> >      > - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
> >      >> 4) * 4U));
> >      > - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
> >      > - rx_tcp(ifp, pkt);
> >      > + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
> >     read pointer
> >      > + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
> >      > + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
> >      > }
> >      > + return r;
> >      > }
> >      >
> >      > -static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
> >      > - // MG_DEBUG(("IP %d", (int) len));
> >      > - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
> >      > - pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
> >      > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
> >      > - mkpay(pkt, pkt->icmp + 1);
> >      > - rx_icmp(ifp, pkt);
> >      > - } else if (pkt->ip6->proto == 17) {
> >      > - pkt->udp = (struct udp *) (pkt->ip6 + 1);
> >      > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
> >      > - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
> >      > - // mg_htons(udp->dport)));
> >      > - mkpay(pkt, pkt->udp + 1);
> >      > +static size_t w5500_tx(const void *buf, size_t buflen,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> >      > + uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
> >      > + while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
> >      > + ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
> >      > + w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
> >      > + w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
> >     write pointer
> >      > + w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
> >      > + for (i = 0; i < 40; i++) {
> >      > + uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
> >      > + if (ir == 0) continue;
> >      > + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
> >     (int) n, ptr);
> >      > + w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
> >      > + if (ir & 8) len = 0; // Timeout. Report error
> >      > + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
> >      > }
> >      > + return len;
> >      > }
> >      >
> >      > -static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
> >      > - const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
> >      > - struct pkt pkt;
> >      > - memset(&pkt, 0, sizeof(pkt));
> >      > - pkt.raw.ptr = (char *) buf;
> >      > - pkt.raw.len = len;
> >      > - pkt.eth = (struct eth *) buf;
> >      > - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
> >      > - if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
> >      > - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
> >      > - // Not for us. Drop silently
> >      > - } else if (pkt.eth->type == mg_htons(0x806)) {
> >      > - pkt.arp = (struct arp *) (pkt.eth + 1);
> >      > - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
> >     // Truncated
> >      > - rx_arp(ifp, &pkt);
> >      > - } else if (pkt.eth->type == mg_htons(0x86dd)) {
> >      > - pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
> >      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
> >     // Truncated
> >      > - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
> >      > - mkpay(&pkt, pkt.ip6 + 1);
> >      > - rx_ip6(ifp, &pkt);
> >      > - } else if (pkt.eth->type == mg_htons(0x800)) {
> >      > - pkt.ip = (struct ip *) (pkt.eth + 1);
> >      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
> >     // Truncated
> >      > - // Truncate frame to what IP header tells us
> >      > - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
> >     pkt.raw.len) {
> >      > - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
> >      > - }
> >      > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
> >     // Truncated
> >      > - if ((pkt.ip->ver >> 4) != 4) return; // Not IP
> >      > - mkpay(&pkt, pkt.ip + 1);
> >      > - rx_ip(ifp, &pkt);
> >      > - } else {
> >      > - MG_DEBUG((" Unknown eth type %x", mg_htons(pkt.eth->type)));
> >      > +static bool w5500_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
> >      > + s->end(s->spi);
> >      > + w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
> >      > + w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
> >      > + w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
> >      > + // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
> >      > + w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
> >      > + w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
> >      > + w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
> >      > + w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
> >      > + return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
> >      > +}
> >      > +
> >      > +static bool w5500_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *)
> >     ifp->driver_data;
> >      > + uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
> >      > + return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
> >      > +}
> >      > +
> >      > +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init,
> >     w5500_tx, w5500_rx,
> >      > + w5500_up};
> >      > +#endif
> >      > +
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/xmc.c"
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
> >     MG_ENABLE_DRIVER_XMC
> >      > +
> >      > +struct ETH_GLOBAL_TypeDef {
> >      > + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER,
> >     HASH_TABLE_HIGH,
> >      > + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL,
> >     VLAN_TAG, VERSION,
> >      > + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS,
> >     RESERVED[2],
> >      > + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH,
> >     MAC_ADDRESS0_LOW,
> >      > + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH,
> >     MAC_ADDRESS2_LOW,
> >      > + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
> >      > + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT,
> >     MMC_RECEIVE_INTERRUPT_MASK,
> >      > + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
> >      > + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
> >      > + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5,
> >     RX_STATISTICS_2[30],
> >      > + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
> >      > + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
> >      > + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
> >      > + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
> >      > + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
> >      > + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
> >      > + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
> >      > + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
> >      > + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
> >      > + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
> >      > + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
> >      > + CURRENT_HOST_RECEIVE_DESCRIPTOR,
> >     CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
> >      > + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
> >      > +};
> >      > +
> >      > +#undef ETH0
> >      > +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
> >      > +
> >      > +#define ETH_PKT_SIZE 1536 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 4 // Descriptor size (words)
> >      > +
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > +static uint8_t s_txno; // Current TX descriptor
> >      > +static uint8_t s_rxno; // Current RX descriptor
> >      > +
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> >      > +
> >      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> >      > + ((uint32_t)addr << 11) |
> >      > + ((uint32_t)reg << 6) | 1;
> >      > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> >      > + return (uint16_t)(ETH0->GMII_DATA & 0xffff);
> >      > +}
> >      > +
> >      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + ETH0->GMII_DATA = val;
> >      > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
> >      > + ((uint32_t)addr << 11) |
> >      > + ((uint32_t)reg << 6) | 3;
> >      > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
> >      > +}
> >      > +
> >      > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data
> >     *d) {
> >      > + if (d->mdc_cr == -1) {
> >      > + // assume ETH clock is 60MHz by default
> >      > + // then according to 13.2.8.1, we need to set value 3
> >      > + return 3;
> >      > }
> >      > +
> >      > + return d->mdc_cr;
> >      > }
> >      >
> >      > -static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
> >      > - if (ifp == NULL || ifp->driver == NULL) return;
> >      > - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
> >     1000, uptime_ms);
> >      > - ifp->now = uptime_ms;
> >      > +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_xmc_data *d =
> >      > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> >      > + s_ifp = ifp;
> >      >
> >      > - // Handle physical interface up/down status
> >      > - if (expired_1000ms && ifp->driver->up) {
> >      > - bool up = ifp->driver->up(ifp);
> >      > - bool current = ifp->state != MIP_STATE_DOWN;
> >      > - if (up != current) {
> >      > - ifp->state = up == false ? MIP_STATE_DOWN
> >      > - : ifp->enable_dhcp_client ? MIP_STATE_UP
> >      > - : MIP_STATE_READY;
> >      > - if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
> >      > - onstatechange(ifp);
> >      > + // reset MAC
> >      > + ETH0->BUS_MODE |= 1;
> >      > + while (ETH0->BUS_MODE & 1) (void) 0;
> >      > +
> >      > + // set clock rate
> >      > + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
> >      > +
> >      > + // init phy
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> >      > +
> >      > + // configure MAC: DO, DM, FES, TC
> >      > + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14)
> >     | MG_BIT(24);
> >      > +
> >      > + // set the MAC address
> >      > + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
> >      > + ETH0->MAC_ADDRESS0_LOW =
> >      > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
> >      > +
> >      > + // Configure the receive filter
> >      > + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
> >      > + // Disable flow control
> >      > + ETH0->FLOW_CONTROL = 0;
> >      > + // Enable store and forward mode
> >      > + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
> >      > +
> >      > + // Configure DMA bus mode (AAL, USP, RPBL, PBL)
> >      > + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8);
> >      > +
> >      > + // init RX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
> >      > + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
> >      > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
> >      > + if (i == ETH_DESC_CNT - 1) {
> >      > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
> >      > + } else {
> >      > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
> >      > }
> >      > }
> >      > - if (ifp->state == MIP_STATE_DOWN) return;
> >      > - // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
> >      > + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
> >     &s_rxdesc[0][0];
> >      >
> >      > - if (ifp->ip == 0 && expired_1000ms) {
> >      > - tx_dhcp_discover(ifp); // If IP not configured, send DHCP
> >      > - } else if (ifp->enable_dhcp_client == false && expired_1000ms
> >     && ifp->gw &&
> >      > - arp_cache_find(ifp, ifp->gw) == NULL) {
> >      > - arp_ask(ifp, ifp->gw); // If GW's MAC address in not in ARP cache
> >      > + // init TX descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
> >      > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
> >      > + if (i == ETH_DESC_CNT - 1) {
> >      > + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
> >      > + } else {
> >      > + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
> >      > + }
> >      > }
> >      > + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
> >     &s_txdesc[0][0];
> >      >
> >      > - // Read data from the network
> >      > - size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len,
> >     ifp);
> >      > - mip_rx(ifp, (void *) ifp->rx.ptr, len);
> >      > - qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
> >      > + // Clear interrupts
> >      > + ETH0->STATUS = 0xFFFFFFFF;
> >      >
> >      > - // Process timeouts
> >      > - for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c =
> >     c->next) {
> >      > - if (c->is_udp || c->is_listening) continue;
> >      > - if (c->is_connecting || c->is_resolving) continue;
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - if (uptime_ms > s->timer) {
> >      > - if (s->ttype == MIP_TTYPE_ACK) {
> >      > - MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
> >      > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> >      > - mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
> >      > - } else {
> >      > - if (s->tmiss++ > 2) {
> >      > - mg_error(c, "keepalive");
> >      > - } else {
> >      > - MG_DEBUG(("%lu keepalive", c->id));
> >      > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
> >      > - mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
> >      > - }
> >      > - }
> >      > - settmout(c, MIP_TTYPE_KEEPALIVE);
> >      > - }
> >      > - }
> >      > -#ifdef MIP_QPROFILE
> >      > - qp_log();
> >      > -#endif
> >      > + // Disable MAC interrupts
> >      > + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
> >      > + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> >      > + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
> >      > + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
> >      > +
> >      > + //Enable interrupts (NIE, RIE, TIE)
> >      > + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
> >      > +
> >      > + // Enable MAC transmission and reception (TE, RE)
> >      > + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
> >      > + // Enable DMA transmission and reception (ST, SR)
> >      > + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
> >      > + return true;
> >      > }
> >      >
> >      > -// This function executes in interrupt context, thus it should
> >     copy data
> >      > -// somewhere fast. Note that newlib's malloc is not thread safe,
> >     thus use
> >      > -// our lock-free queue with preallocated buffer to copy data and
> >     return asap
> >      > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
> >      > - if (q_write(&ifp->queue, buf, len)) {
> >      > - qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
> >      > +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // Frame is too big
> >      > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No free descriptors"));
> >      > + len = 0; // All descriptors are busy, fail
> >      > } else {
> >      > - ifp->dropped++;
> >      > - qp_mark(QP_FRAMEDROPPED, ifp->dropped);
> >      > - MG_ERROR(("dropped %d", (int) len));
> >      > + memcpy(s_txbuf[s_txno], buf, len);
> >      > + s_txdesc[s_txno][1] = len;
> >      > + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
> >      > + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) |
> >     MG_BIT(20);
> >      > + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control
> >     to DMA
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > }
> >      > -}
> >      >
> >      > -size_t mip_qread(void *buf, struct mip_if *ifp) {
> >      > - size_t len = q_read(&ifp->queue, buf);
> >      > - qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
> >      > + // Resume processing
> >      > + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
> >      > + ETH0->TRANSMIT_POLL_DEMAND = 0;
> >      > return len;
> >      > }
> >      >
> >      > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
> >      > - return mip_qread((void *) ifp->rx.ptr, ifp);
> >      > - (void) len, (void) buf;
> >      > -}
> >      > +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_xmc_data *d =
> >      > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ?
> >     10 : 100,
> >      > + full_duplex ? "full" : "half"));
> >      > + }
> >      > + return up;
> >      > +}
> >      > +
> >      > +void ETH0_IRQHandler(void);
> >      > +void ETH0_IRQHandler(void) {
> >      > + uint32_t irq_status = ETH0->STATUS;
> >      > +
> >      > + // check if a frame was received
> >      > + if (irq_status & MG_BIT(6)) {
> >      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> >      > + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
> >      > + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> >      > + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to
> >     DMA
> >      > + // Resume processing
> >      > + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
> >      > + ETH0->RECEIVE_POLL_DEMAND = 0;
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > + }
> >      > + }
> >      > + ETH0->STATUS = MG_BIT(6);
> >      > + }
> >      >
> >      > -void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
> >      > - if (ifp->driver->init && !ifp->driver->init(ifp)) {
> >      > - MG_ERROR(("driver init failed"));
> >      > - } else {
> >      > - size_t maxpktsize = 1540;
> >      > - ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len =
> >     maxpktsize;
> >      > - ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len =
> >     maxpktsize;
> >      > - if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1,
> >     ifp->queue.len);
> >      > - ifp->timer_1000ms = mg_millis();
> >      > - arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
> >      > - mgr->priv = ifp;
> >      > - ifp->mgr = mgr;
> >      > - mgr->extraconnsize = sizeof(struct connstate);
> >      > - if (ifp->ip == 0) ifp->enable_dhcp_client = true;
> >      > -#ifdef MIP_QPROFILE
> >      > - qp_init();
> >      > -#endif
> >      > + // clear Successful transmission interrupt
> >      > + if (irq_status & 1) {
> >      > + ETH0->STATUS = 1;
> >      > }
> >      > -}
> >      >
> >      > -void mip_free(struct mip_if *ifp) {
> >      > - free((char *) ifp->rx.ptr);
> >      > - free((char *) ifp->tx.ptr);
> >      > + // clear normal interrupt
> >      > + if (irq_status & MG_BIT(16)) {
> >      > + ETH0->STATUS = MG_BIT(16);
> >      > + }
> >      > }
> >      >
> >      > -int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d,
> >     bool udp) {
> >      > - (void) m, (void) fn, (void) d, (void) udp;
> >      > - MG_ERROR(("Not implemented"));
> >      > - return -1;
> >      > -}
> >      > +struct mg_tcpip_driver mg_tcpip_driver_xmc = {
> >      > + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
> >      > + mg_tcpip_driver_xmc_up};
> >      > +#endif
> >      >
> >      > -#if 0
> >      > -static uint16_t mkeport(void) {
> >      > - uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
> >      > - mg_random(&a, sizeof(a));
> >      > - c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
> >      > - return c;
> >      > -}
> >      > +#ifdef MG_ENABLE_LINES
> >      > +#line 1 "src/drivers/xmc7.c"
> >      > #endif
> >      >
> >      > -void mg_connect_resolved(struct mg_connection *c) {
> >      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> >      > - c->is_resolving = 0;
> >      > - if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport =
> >     MIP_ETHEMERAL_PORT;
> >      > - c->loc.ip = ifp->ip;
> >      > - c->loc.port = mg_htons(ifp->eport++);
> >      > - MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip,
> >     mg_ntohs(c->loc.port),
> >      > - 4, &c->rem.ip, mg_ntohs(c->rem.port)));
> >      > - mg_call(c, MG_EV_RESOLVE, NULL);
> >      > - if (c->is_udp) {
> >      > - mg_call(c, MG_EV_CONNECT, NULL);
> >      > - } else {
> >      > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
> >      > - tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn,
> >     0, NULL, 0);
> >      > - c->is_connecting = 1;
> >      > - }
> >      > -}
> >      >
> >      > -bool mg_open_listener(struct mg_connection *c, const char *url) {
> >      > - c->loc.port = mg_htons(mg_url_port(url));
> >      > - return true;
> >      > -}
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
> >     MG_ENABLE_DRIVER_XMC7
> >      > +
> >      > +struct ETH_Type {
> >      > + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
> >      > + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
> >      > + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
> >      > + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT,
> >     PAUSE_TIME,
> >      > + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU,
> >     JUMBO_MAX_LENGTH,
> >      > + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
> >      > + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM,
> >     HASH_TOP,
> >      > + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
> >      > + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
> >      > + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
> >      > + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
> >      > + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
> >      > + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
> >      > + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
> >      > + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
> >      > + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
> >      > + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
> >      > + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
> >      > + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
> >      > + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
> >      > + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
> >      > + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65,
> >     FRAMES_RXED_128,
> >      > + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024,
> >     FRAMES_RXED_1519,
> >      > + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
> >      > + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS,
> >     RX_RESOURCE_ERRORS,
> >      > + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
> >      > + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC,
> >     TSU_TIMER_MSB_SEC,
> >      > + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC,
> >     TSU_TIMER_SEC,
> >      > + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
> >      > + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
> >      > + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
> >      > + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
> >      > + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS,
> >     RESERVED6[8],
> >      > + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3,
> >     RESERVED7,
> >      > + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
> >      > + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4,
> >     DESIGNCFG_DEBUG5,
> >      > + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8,
> >     DESIGNCFG_DEBUG9,
> >      > + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
> >      > + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
> >      > + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS,
> >     RESERVED11,
> >      > + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
> >      > + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
> >      > + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
> >      > + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3,
> >     RESERVED16[3],
> >      > + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A,
> >     CBS_IDLESLOPE_Q_B,
> >      > + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL,
> >     UPPER_RX_Q_BASE_ADDR,
> >      > + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
> >      > + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
> >      > +};
> >      >
> >      > -static void write_conn(struct mg_connection *c) {
> >      > - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
> >      > - : mg_io_send(c, c->send.buf, c->send.len);
> >      > - if (len > 0) {
> >      > - mg_iobuf_del(&c->send, 0, (size_t) len);
> >      > - mg_call(c, MG_EV_WRITE, &len);
> >      > - }
> >      > +#define ETH0 ((struct ETH_Type *) 0x40490000)
> >      > +
> >      > +#define ETH_PKT_SIZE 1536 // Max frame size
> >      > +#define ETH_DESC_CNT 4 // Descriptors count
> >      > +#define ETH_DS 2 // Descriptor size (words)
> >      > +
> >      > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
> >      > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
> >      > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
> >      > +static uint8_t s_txno; // Current TX descriptor
> >      > +static uint8_t s_rxno; // Current RX descriptor
> >      > +
> >      > +static struct mg_tcpip_if *s_ifp; // MIP interface
> >      > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
> >      > +
> >      > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
> >      > + // WRITE1, READ OPERATION, PHY, REG, WRITE10
> >      > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf)
> >     << 24) |
> >      > + ((reg & 0x1f) << 18) | MG_BIT(17);
> >      > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
> >      > + return ETH0->PHY_MANAGEMENT & 0xffff;
> >      > }
> >      >
> >      > -static void close_conn(struct mg_connection *c) {
> >      > - struct connstate *s = (struct connstate *) (c + 1);
> >      > - mg_iobuf_free(&s->raw); // For TLS connections, release raw data
> >      > - if (c->is_udp == false && c->is_listening == false) { // For
> >     TCP conns,
> >      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv; // send
> >     TCP FIN
> >      > - tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
> >      > - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
> >      > - }
> >      > - mg_close_conn(c);
> >      > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
> >     val) {
> >      > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf)
> >     << 24) |
> >      > + ((reg & 0x1f) << 18) | MG_BIT(17) | val;
> >      > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
> >      > }
> >      >
> >      > -static bool can_write(struct mg_connection *c) {
> >      > - return c->is_connecting == 0 && c->is_resolving == 0 &&
> >     c->send.len > 0 &&
> >      > - c->is_tls_hs == 0;
> >      > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data
> >     *d) {
> >      > + // see ETH0 -> NETWORK_CONFIG register
> >      > + (void) d;
> >      > + return 3;
> >      > }
> >      >
> >      > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
> >      > - struct mg_connection *c, *tmp;
> >      > - uint64_t now = mg_millis();
> >      > - mip_poll((struct mip_if *) mgr->priv, now);
> >      > - mg_timer_poll(&mgr->timers, now);
> >      > - for (c = mgr->conns; c != NULL; c = tmp) {
> >      > - tmp = c->next;
> >      > - mg_call(c, MG_EV_POLL, &now);
> >      > - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
> >      > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
> >      > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
> >      > - if (c->is_tls_hs) mg_tls_handshake(c);
> >      > - if (can_write(c)) write_conn(c);
> >      > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
> >      > - if (c->is_closing) close_conn(c);
> >      > +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_xmc7_data *d =
> >      > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> >      > + s_ifp = ifp;
> >      > +
> >      > + // enable controller, set RGMII mode
> >      > + ETH0->CTL = MG_BIT(31) | 2;
> >      > +
> >      > + uint32_t cr = get_clock_rate(d);
> >      > + // set NSP change, ignore RX FCS, data bus width, clock rate
> >      > + // frame length 1536, full duplex, speed
> >      > + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
> >      > + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
> >      > + MG_BIT(1) | MG_BIT(0);
> >      > +
> >      > + // config DMA settings: Force TX burst, Discard on Error, set
> >     RX buffer size
> >      > + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
> >      > + ETH0->DMA_CONFIG =
> >      > + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8)
> >     | 4;
> >      > +
> >      > + // initialize descriptors
> >      > + for (int i = 0; i < ETH_DESC_CNT; i++) {
> >      > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
> >      > + if (i == ETH_DESC_CNT - 1) {
> >      > + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor
> >      > + }
> >      > +
> >      > + s_txdesc[i][0] = (uint32_t) s_txbuf[i];
> >      > + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor
> >      > + if (i == ETH_DESC_CNT - 1) {
> >      > + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor
> >      > + }
> >      > }
> >      > - (void) ms;
> >      > + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
> >      > + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
> >      > +
> >      > + // disable other queues
> >      > + ETH0->TRANSMIT_Q2_PTR = 1;
> >      > + ETH0->TRANSMIT_Q1_PTR = 1;
> >      > + ETH0->RECEIVE_Q2_PTR = 1;
> >      > + ETH0->RECEIVE_Q1_PTR = 1;
> >      > +
> >      > + // enable interrupts (TX and RX complete)
> >      > + ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
> >      > +
> >      > + // set MAC address
> >      > + ETH0->SPEC_ADD1_BOTTOM =
> >      > + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 |
> >     ifp->mac[0];
> >      > + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
> >      > +
> >      > + // enable MDIO, TX, RX
> >      > + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
> >      > +
> >      > + // start transmission
> >      > + ETH0->NETWORK_CONTROL |= MG_BIT(9);
> >      > +
> >      > + // init phy
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
> >      > +
> >      > + (void) d;
> >      > + return true;
> >      > }
> >      >
> >      > -bool mg_send(struct mg_connection *c, const void *buf, size_t
> >     len) {
> >      > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
> >      > - bool res = false;
> >      > - if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
> >      > - mg_error(c, "net down");
> >      > - } else if (c->is_udp) {
> >      > - tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf,
> >     len);
> >      > - res = true;
> >      > +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
> >      > + struct mg_tcpip_if *ifp) {
> >      > + if (len > sizeof(s_txbuf[s_txno])) {
> >      > + MG_ERROR(("Frame too big, %ld", (long) len));
> >      > + len = 0; // Frame is too big
> >      > + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
> >      > + ifp->nerr++;
> >      > + MG_ERROR(("No free descriptors"));
> >      > + len = 0; // All descriptors are busy, fail
> >      > } else {
> >      > - res = mg_iobuf_add(&c->send, c->send.len, buf, len);
> >      > - }
> >      > - return res;
> >      > -}
> >      > + memcpy(s_txbuf[s_txno], buf, len);
> >      > + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30)
> >     : 0) |
> >      > + MG_BIT(15) | len; // Last buffer and length
> >      >
> >      > -#ifdef MIP_QPROFILE
> >      > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
> >      > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
> >      > + }
> >      >
> >      > -#pragma pack(push, 1)
> >      > -struct qpentry {
> >      > - uint32_t timestamp;
> >      > - uint16_t type;
> >      > - uint16_t len;
> >      > -};
> >      > -#pragma pack(pop)
> >      > + MG_DSB();
> >      > + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
> >      > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
> >      >
> >      > -static struct queue qp;
> >      > + return len;
> >      > +}
> >      >
> >      > -// This is called from IRQ and main contexts; two producers,
> >     single consumer
> >      > -// TODO(scaprile): avoid concurrency issues (2 queues ?)
> >      > -void qp_mark(unsigned int type, int len) {
> >      > - static bool ovf = false;
> >      > - static uint16_t irq_ctr = 0, drop_ctr = 0;
> >      > - struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
> >      > - .type = (uint16_t) type,
> >      > - .len = (uint16_t) len};
> >      > - if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
> >      > - if (ovf) {
> >      > - e.type = (uint16_t) QP_QUEUEOVF;
> >      > - e.len = drop_ctr;
> >      > +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
> >      > + struct mg_tcpip_driver_xmc7_data *d =
> >      > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
> >      > + uint8_t speed = MG_PHY_SPEED_10M;
> >      > + bool up = false, full_duplex = false;
> >      > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
> >      > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
> >      > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
> >     just went up
> >      > + if (speed == MG_PHY_SPEED_1000M) {
> >      > + ETH0->NETWORK_CONFIG |= MG_BIT(10);
> >      > + }
> >      > + MG_DEBUG(("Link is %uM %s-duplex",
> >      > + speed == MG_PHY_SPEED_10M ? 10 :
> >      > + (speed == MG_PHY_SPEED_100M ? 100 : 1000),
> >      > + full_duplex ? "full" : "half"));
> >      > }
> >      > - ovf = !q_write(&qp, &e, sizeof(e));
> >      > + (void) d;
> >      > + return up;
> >      > }
> >      >
> >      > -void qp_log(void) {
> >      > - struct qpentry e;
> >      > - const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP",
> >     "OVFL"};
> >      > - for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
> >      > - MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
> >      > +void ETH_IRQHandler(void) {
> >      > + uint32_t irq_status = ETH0->INT_STATUS;
> >      > + if (irq_status & MG_BIT(1)) {
> >      > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
> >      > + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
> >      > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
> >      > + //MG_INFO(("Receive complete: %ld bytes", len));
> >      > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
> >      > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control
> >     to DMA
> >      > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
> >      > + }
> >      > + }
> >      > }
> >      > -}
> >      >
> >      > -void qp_init(void) {
> >      > - qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
> >      > - qp.buf = calloc(1, qp.len); // THERE IS NO FREE
> >      > + ETH0->INT_STATUS = irq_status;
> >      > }
> >      > -#endif // MIP_QPROFILE
> >      >
> >      > -#endif // MG_ENABLE_MIP
> >      > +struct mg_tcpip_driver mg_tcpip_driver_xmc7 =
> >     {mg_tcpip_driver_xmc7_init,
> >      > + mg_tcpip_driver_xmc7_tx, NULL,
> >      > + mg_tcpip_driver_xmc7_up};
> >      > +#endif
> >      > diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
> >      > index 350aad9c..3a439a6e 100644
> >      > --- a/mongoose/mongoose.h
> >      > +++ b/mongoose/mongoose.h
> >      > @@ -1,5 +1,5 @@
> >      > // Copyright (c) 2004-2013 Sergey Lyubka
> >      > -// Copyright (c) 2013-2022 Cesanta Software Limited
> >      > +// Copyright (c) 2013-2024 Cesanta Software Limited
> >      > // All rights reserved
> >      > //
> >      > // This software is dual-licensed: you can redistribute it and/or
> >     modify
> >      > @@ -15,58 +15,48 @@
> >      > // Alternatively, you can license this software under a commercial
> >      > // license, as set out in https://www.mongoose.ws/licensing/
> >     <https://www.mongoose.ws/licensing/>
> >      > //
> >      > -// SPDX-License-Identifier: GPL-2.0-only
> >      > +// SPDX-License-Identifier: GPL-2.0-only or commercial
> >      >
> >      > #ifndef MONGOOSE_H
> >      > #define MONGOOSE_H
> >      >
> >      > -#define MG_VERSION "7.8"
> >      > +#define MG_VERSION "7.14"
> >      >
> >      > #ifdef __cplusplus
> >      > extern "C" {
> >      > #endif
> >      >
> >      >
> >      > -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
> >      > -#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
> >      > -#define MG_ARCH_WIN32 2 // Windows
> >      > -#define MG_ARCH_ESP32 3 // ESP32
> >      > -#define MG_ARCH_ESP8266 4 // ESP8266
> >      > -#define MG_ARCH_FREERTOS 5 // FreeRTOS
> >      > -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
> >      > -#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
> >      > -#define MG_ARCH_NEWLIB 8 // Bare metal ARM
> >      > -#define MG_ARCH_RTX 9 // Keil MDK RTX
> >      > -#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
> >      > -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
> >      > +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h
> >      > +#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
> >      > +#define MG_ARCH_WIN32 2 // Windows
> >      > +#define MG_ARCH_ESP32 3 // ESP32
> >      > +#define MG_ARCH_ESP8266 4 // ESP8266
> >      > +#define MG_ARCH_FREERTOS 5 // FreeRTOS
> >      > +#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
> >      > +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
> >      > +#define MG_ARCH_NEWLIB 8 // Bare metal ARM
> >      > +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
> >      > +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
> >      > +#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
> >      > +#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
> >      > +#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5,
> >     FreeRTOS)
> >      > +#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
> >      >
> >      > #if !defined(MG_ARCH)
> >      > #if defined(__unix__) || defined(__APPLE__)
> >      > #define MG_ARCH MG_ARCH_UNIX
> >      > #elif defined(_WIN32)
> >      > #define MG_ARCH MG_ARCH_WIN32
> >      > -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
> >      > -#define MG_ARCH MG_ARCH_ESP8266
> >      > -#elif defined(__ZEPHYR__)
> >      > -#define MG_ARCH MG_ARCH_ZEPHYR
> >      > -#elif defined(ESP_PLATFORM)
> >      > -#define MG_ARCH MG_ARCH_ESP32
> >      > -#elif defined(FREERTOS_IP_H)
> >      > -#define MG_ARCH MG_ARCH_FREERTOS
> >      > -#define MG_ENABLE_FREERTOS_TCP 1
> >      > -#elif defined(AZURE_RTOS_THREADX)
> >      > -#define MG_ARCH MG_ARCH_AZURERTOS
> >      > -#elif defined(PICO_TARGET_NAME)
> >      > -#define MG_ARCH MG_ARCH_RP2040
> >      > #endif
> >      > #endif // !defined(MG_ARCH)
> >      >
> >      > #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
> >      > -#include "mongoose_custom.h" // keep this include
> >      > +#include "mongoose_config.h" // keep this include
> >      > #endif
> >      >
> >      > #if !defined(MG_ARCH)
> >      > -#error "MG_ARCH is not specified and we couldn't guess it. Set
> >     -D MG_ARCH=..."
> >      > +#error "MG_ARCH is not specified and we couldn't guess it.
> >     Define MG_ARCH=... in your compiler"
> >      > #endif
> >      >
> >      > // http://esr.ibiblio.org/?p=5095 <http://esr.ibiblio.org/?p=5095>
> >      > @@ -130,7 +120,8 @@ extern "C" {
> >      > #include <sys/types.h>
> >      > #include <time.h>
> >      >
> >      > -#include <esp_timer.h>
> >      > +#include <esp_ota_ops.h> // Use angle brackets to avoid
> >      > +#include <esp_timer.h> // amalgamation ditching them
> >      >
> >      > #define MG_PATH_MAX 128
> >      >
> >      > @@ -166,15 +157,24 @@ extern "C" {
> >      > #if MG_ARCH == MG_ARCH_FREERTOS
> >      >
> >      > #include <ctype.h>
> >      > -// #include <errno.h> // Cannot include errno - might conflict
> >     with lwip!
> >      > +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
> >      > +#include <errno.h>
> >      > +#endif
> >      > #include <stdarg.h>
> >      > #include <stdbool.h>
> >      > #include <stddef.h>
> >      > #include <stdint.h>
> >      > #include <stdio.h>
> >      > -#include <stdlib.h> // rand(), strtol(), atoi()
> >      > +#include <stdlib.h> // rand(), strtol(), atoi()
> >      > #include <string.h>
> >      > +#if defined(__ARMCC_VERSION)
> >      > +#define mode_t size_t
> >      > +#include <alloca.h>
> >      > +#include <time.h>
> >      > +#elif defined(__CCRH__)
> >      > +#else
> >      > #include <sys/stat.h>
> >      > +#endif
> >      >
> >      > #include <FreeRTOS.h>
> >      > #include <task.h>
> >      > @@ -186,7 +186,7 @@ extern "C" {
> >      > #define calloc(a, b) mg_calloc(a, b)
> >      > #define free(a) vPortFree(a)
> >      > #define malloc(a) pvPortMalloc(a)
> >      > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> >      > +#define strdup(s) mg_mprintf("%s", s)
> >      >
> >      > // Re-route calloc/free to the FreeRTOS's functions, don't use
> >     stdlib
> >      > static inline void *mg_calloc(size_t cnt, size_t size) {
> >      > @@ -240,9 +240,34 @@ static inline int mg_mkdir(const char *path,
> >     mode_t mode) {
> >      > #include <pico/stdlib.h>
> >      > int mkdir(const char *, mode_t);
> >      > #endif
> >      > -
> >      > -
> >      > -#if MG_ARCH == MG_ARCH_RTX
> >      > +
> >      >
> >      > +
> >      >
> >      > +#if MG_ARCH == MG_ARCH_RTTHREAD
> >      >
> >      > +
> >      >
> >      > +#include <rtthread.h>
> >      >
> >      > +#include <ctype.h>
> >      >
> >      > +#include <errno.h>
> >      >
> >      > +#include <fcntl.h>
> >      >
> >      > +#include <sys/socket.h>
> >      >
> >      > +#include <sys/select.h>
> >      >
> >      > +#include <stdarg.h>
> >      >
> >      > +#include <stdbool.h>
> >      >
> >      > +#include <stdint.h>
> >      >
> >      > +#include <stdio.h>
> >      >
> >      > +#include <stdlib.h>
> >      >
> >      > +#include <string.h>
> >      >
> >      > +#include <sys/types.h>
> >      >
> >      > +#include <time.h>
> >      >
> >      > +
> >      >
> >      > +#ifndef MG_IO_SIZE
> >      >
> >      > +#define MG_IO_SIZE 1460
> >      >
> >      > +#endif
> >      >
> >      > +
> >      >
> >      > +#endif // MG_ARCH == MG_ARCH_RTTHREAD
> >      >
> >      > +
> >      > +
> >      > +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
> >      > + MG_ARCH == MG_ARCH_CMSIS_RTOS2
> >      >
> >      > #include <ctype.h>
> >      > #include <errno.h>
> >      > @@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
> >      > #include <stdint.h>
> >      > #include <stdio.h>
> >      > #include <stdlib.h>
> >      > +#include <alloca.h>
> >      > #include <string.h>
> >      > #include <time.h>
> >      > +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
> >      > +#include "cmsis_os.h" // keep this include
> >      > +// https://developer.arm.com/documentation/ka003821/latest
> >     <https://developer.arm.com/documentation/ka003821/latest>
> >      > +extern uint32_t rt_time_get(void);
> >      > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
> >      > +#include "cmsis_os2.h" // keep this include
> >      > +#endif
> >      > +
> >      > +#define strdup(s) mg_mprintf("%s", s)
> >      > +
> >      > +#if defined(__ARMCC_VERSION)
> >      > +#define mode_t size_t
> >      > +#define mkdir(a, b) mg_mkdir(a, b)
> >      > +static inline int mg_mkdir(const char *path, mode_t mode) {
> >      > + (void) path, (void) mode;
> >      > + return -1;
> >      > +}
> >      > +#endif
> >      >
> >      > -#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
> >     !MG_ENABLE_LWIP)
> >      > +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH ==
> >     MG_ARCH_CMSIS_RTOS2) && \
> >      > + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
> >     !MG_ENABLE_LWIP) && \
> >      > + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
> >      > #define MG_ENABLE_RL 1
> >      > +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> >      > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> >      > +#endif
> >      > #endif
> >      >
> >      > #endif
> >      > @@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
> >      > #define MG_INVALID_SOCKET INVALID_SOCKET
> >      > #define MG_SOCKET_TYPE SOCKET
> >      > typedef unsigned long nfds_t;
> >      > -#define MG_SOCKET_ERRNO WSAGetLastError()
> >      > #if defined(_MSC_VER)
> >      > #pragma comment(lib, "ws2_32.lib")
> >      > #ifndef alloca
> >      > @@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
> >      > #endif
> >      > #endif
> >      > #define poll(a, b, c) WSAPoll((a), (b), (c))
> >      > -#ifndef SO_EXCLUSIVEADDRUSE
> >      > -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
> >      > -#endif
> >      > #define closesocket(x) closesocket(x)
> >      >
> >      > typedef int socklen_t;
> >      > @@ -423,16 +468,24 @@ typedef int socklen_t;
> >      > #define MG_PATH_MAX FILENAME_MAX
> >      > #endif
> >      >
> >      > -#ifndef EINPROGRESS
> >      > -#define EINPROGRESS WSAEINPROGRESS
> >      > -#endif
> >      > -#ifndef EWOULDBLOCK
> >      > -#define EWOULDBLOCK WSAEWOULDBLOCK
> >      > +#ifndef SO_EXCLUSIVEADDRUSE
> >      > +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
> >      > #endif
> >      >
> >      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError()
> >     : 0)
> >      > +
> >      > +#define MG_SOCK_PENDING(errcode) \
> >      > + (((errcode) < 0) && \
> >      > + (WSAGetLastError() == WSAEINTR || WSAGetLastError() ==
> >     WSAEINPROGRESS || \
> >      > + WSAGetLastError() == WSAEWOULDBLOCK))
> >      > +
> >      > +#define MG_SOCK_RESET(errcode) \
> >      > + (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
> >      > +
> >      > #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
> >      > -#define sleep(x) Sleep(x)
> >      > +#define sleep(x) Sleep((x) *1000)
> >      > #define mkdir(a, b) _mkdir(a)
> >      > +#define timegm(x) _mkgmtime(x)
> >      >
> >      > #ifndef S_ISDIR
> >      > #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
> >      > @@ -442,6 +495,10 @@ typedef int socklen_t;
> >      > #define MG_ENABLE_DIRLIST 1
> >      > #endif
> >      >
> >      > +#ifndef SIGPIPE
> >      > +#define SIGPIPE 0
> >      > +#endif
> >      > +
> >      > #endif
> >      >
> >      >
> >      > @@ -451,8 +508,9 @@ typedef int socklen_t;
> >      >
> >      > #include <ctype.h>
> >      > #include <errno.h>
> >      > -#include <fcntl.h>
> >      > #include <zephyr/net/socket.h>
> >      > +#include <zephyr/posix/fcntl.h>
> >      > +#include <zephyr/posix/sys/select.h>
> >      > #include <stdarg.h>
> >      > #include <stdbool.h>
> >      > #include <stdint.h>
> >      > @@ -464,11 +522,18 @@ typedef int socklen_t;
> >      >
> >      > #define MG_PUTCHAR(x) printk("%c", x)
> >      > #ifndef strdup
> >      > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
> >      > +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
> >      > #endif
> >      > #define strerror(x) zsock_gai_strerror(x)
> >      > +
> >      > +#ifndef FD_CLOEXEC
> >      > #define FD_CLOEXEC 0
> >      > +#endif
> >      > +
> >      > +#ifndef F_SETFD
> >      > #define F_SETFD 0
> >      > +#endif
> >      > +
> >      > #define MG_ENABLE_SSI 0
> >      >
> >      > int rand(void);
> >      > @@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
> >      >
> >      > #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
> >      >
> >      > -#include <ctype.h>
> >      > -#include <errno.h>
> >      > #include <limits.h>
> >      > -#include <stdarg.h>
> >      > -#include <stdbool.h>
> >      > -#include <stddef.h>
> >      > -#include <stdio.h>
> >      > -#include <stdlib.h>
> >      > -#include <string.h>
> >      > -#include <sys/stat.h>
> >      > -#include <time.h>
> >      > -
> >      > -#include <FreeRTOS.h>
> >      > #include <list.h>
> >      > -#include <task.h>
> >      >
> >      > #include <FreeRTOS_IP.h>
> >      > #include <FreeRTOS_Sockets.h>
> >      > +#include <FreeRTOS_errno_TCP.h> // contents to be moved and file
> >     removed, some day
> >      >
> >      > #define MG_SOCKET_TYPE Socket_t
> >      > #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
> >      > @@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
> >      > #define SO_ERROR 0
> >      > #define SOL_SOCKET 0
> >      > #define SO_REUSEADDR 0
> >      > +
> >      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> >      > +
> >      > +#define MG_SOCK_PENDING(errcode) \
> >      > + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
> >      > + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \
> >      > + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
> >      > + (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
> >      > +
> >      > +#define MG_SOCK_RESET(errcode) ((errcode) ==
> >     -pdFREERTOS_ERRNO_ENOTCONN)
> >      > +
> >      > +// actually only if optional timeout is enabled
> >      > +#define MG_SOCK_INTR(fd) (fd == NULL)
> >      > +
> >      > #define sockaddr_in freertos_sockaddr
> >      > #define sockaddr freertos_sockaddr
> >      > #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
> >      > @@ -543,8 +610,17 @@ static inline int
> >     mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
> >      >
> >      >
> >      > #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
> >      > -#if defined(__GNUC__)
> >      > +
> >      > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
> >      > #include <sys/stat.h>
> >      > +#endif
> >      > +
> >      > +struct timeval;
> >      > +
> >      > +#include <lwip/sockets.h>
> >      > +
> >      > +#if !LWIP_TIMEVAL_PRIVATE
> >      > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang
> >     sets both
> >      > #include <sys/time.h>
> >      > #else
> >      > struct timeval {
> >      > @@ -552,8 +628,7 @@ struct timeval {
> >      > long tv_usec;
> >      > };
> >      > #endif
> >      > -
> >      > -#include <lwip/sockets.h>
> >      > +#endif
> >      >
> >      > #if LWIP_SOCKET != 1
> >      > // Sockets support disabled in LWIP by default
> >      > @@ -565,16 +640,25 @@ struct timeval {
> >      > #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
> >      > #include <rl_net.h>
> >      >
> >      > -#define MG_ENABLE_CUSTOM_MILLIS 1
> >      > #define closesocket(x) closesocket(x)
> >      > -#define mkdir(a, b) (-1)
> >      > -#define EWOULDBLOCK BSD_EWOULDBLOCK
> >      > -#define EAGAIN BSD_EWOULDBLOCK
> >      > -#define EINPROGRESS BSD_EWOULDBLOCK
> >      > -#define EINTR BSD_EWOULDBLOCK
> >      > -#define ECONNRESET BSD_ECONNRESET
> >      > -#define EPIPE BSD_ECONNRESET
> >      > +
> >      > #define TCP_NODELAY SO_KEEPALIVE
> >      > +
> >      > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
> >      > +
> >      > +#define MG_SOCK_PENDING(errcode) \
> >      > + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
> >      > + (errcode) == BSD_EINPROGRESS)
> >      > +
> >      > +#define MG_SOCK_RESET(errcode) \
> >      > + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
> >      > +
> >      > +// In blocking mode, which is enabled by default, accept() waits
> >     for a
> >      > +// connection request. In non blocking mode, you must call accept()
> >      > +// again if the error code BSD_EWOULDBLOCK is returned.
> >      > +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
> >      > +
> >      > +#define socklen_t int
> >      > #endif
> >      >
> >      >
> >      > @@ -582,8 +666,12 @@ struct timeval {
> >      > #define MG_ENABLE_LOG 1
> >      > #endif
> >      >
> >      > -#ifndef MG_ENABLE_MIP
> >      > -#define MG_ENABLE_MIP 0 // Mongoose built-in network stack
> >      > +#ifndef MG_ENABLE_CUSTOM_LOG
> >      > +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
> >      > +#endif
> >      > +
> >      > +#ifndef MG_ENABLE_TCPIP
> >      > +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack
> >      > #endif
> >      >
> >      > #ifndef MG_ENABLE_LWIP
> >      > @@ -599,7 +687,7 @@ struct timeval {
> >      > #endif
> >      >
> >      > #ifndef MG_ENABLE_SOCKET
> >      > -#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
> >      > +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
> >      > #endif
> >      >
> >      > #ifndef MG_ENABLE_POLL
> >      > @@ -614,18 +702,6 @@ struct timeval {
> >      > #define MG_ENABLE_FATFS 0
> >      > #endif
> >      >
> >      > -#ifndef MG_ENABLE_MBEDTLS
> >      > -#define MG_ENABLE_MBEDTLS 0
> >      > -#endif
> >      > -
> >      > -#ifndef MG_ENABLE_OPENSSL
> >      > -#define MG_ENABLE_OPENSSL 0
> >      > -#endif
> >      > -
> >      > -#ifndef MG_ENABLE_CUSTOM_TLS
> >      > -#define MG_ENABLE_CUSTOM_TLS 0
> >      > -#endif
> >      > -
> >      > #ifndef MG_ENABLE_SSI
> >      > #define MG_ENABLE_SSI 0
> >      > #endif
> >      > @@ -634,6 +710,10 @@ struct timeval {
> >      > #define MG_ENABLE_IPV6 0
> >      > #endif
> >      >
> >      > +#ifndef MG_IPV6_V6ONLY
> >      > +#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4
> >     address
> >      > +#endif
> >      > +
> >      > #ifndef MG_ENABLE_MD5
> >      > #define MG_ENABLE_MD5 1
> >      > #endif
> >      > @@ -659,12 +739,16 @@ struct timeval {
> >      > #define MG_ENABLE_PACKED_FS 0
> >      > #endif
> >      >
> >      > +#ifndef MG_ENABLE_ASSERT
> >      > +#define MG_ENABLE_ASSERT 0
> >      > +#endif
> >      > +
> >      > #ifndef MG_IO_SIZE
> >      > #define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer
> >     growth
> >      > #endif
> >      >
> >      > #ifndef MG_MAX_RECV_SIZE
> >      > -#define MG_MAX_RECV_SIZE (3 * 1024 * 1024) // Maximum recv IO
> >     buffer size
> >      > +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv
> >     IO buffer size
> >      > #endif
> >      >
> >      > #ifndef MG_DATA_SIZE
> >      > @@ -688,18 +772,18 @@ struct timeval {
> >      > #endif
> >      >
> >      > #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
> >      > -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
> >      > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
> >      > #endif
> >      >
> >      > #ifndef MG_DIRSEP
> >      > #define MG_DIRSEP '/'
> >      > #endif
> >      >
> >      > -#ifndef MG_ENABLE_FILE
> >      > +#ifndef MG_ENABLE_POSIX_FS
> >      > #if defined(FOPEN_MAX)
> >      > -#define MG_ENABLE_FILE 1
> >      > +#define MG_ENABLE_POSIX_FS 1
> >      > #else
> >      > -#define MG_ENABLE_FILE 0
> >      > +#define MG_ENABLE_POSIX_FS 0
> >      > #endif
> >      > #endif
> >      >
> >      > @@ -732,60 +816,112 @@ struct timeval {
> >      > #define MG_EPOLL_MOD(c, wr)
> >      > #endif
> >      >
> >      > +#ifndef MG_ENABLE_PROFILE
> >      > +#define MG_ENABLE_PROFILE 0
> >      > +#endif
> >      >
> >      > +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also
> >     initialize
> >      > +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver
> >     for
> >      > +#endif // Mongoose built-in network stack
> >      >
> >      > +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
> >      > +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
> >     (DHCP)
> >      > +#endif
> >      >
> >      > -struct mg_str {
> >      > - const char *ptr; // Pointer to string data
> >      > - size_t len; // String len
> >      > -};
> >      > +#ifndef MG_TCPIP_MASK
> >      > +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
> >     (DHCP)
> >      > +#endif
> >      > +
> >      > +#ifndef MG_TCPIP_GW
> >      > +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
> >     (DHCP)
> >      > +#endif
> >      > +
> >      > +#ifndef MG_SET_MAC_ADDRESS
> >      > +#define MG_SET_MAC_ADDRESS(mac)
> >      > +#endif
> >      >
> >      > -#define MG_NULL_STR \
> >      > - { NULL, 0 }
> >      > +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
> >      > +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
> >      > +#endif
> >      >
> >      > -#define MG_C_STR(a) \
> >      > - { (a), sizeof(a) - 1 }
> >      > +
> >      > +
> >      > +
> >      > +// Describes an arbitrary chunk of memory
> >      > +struct mg_str {
> >      > + char *buf; // String data
> >      > + size_t len; // String length
> >      > +};
> >      >
> >      > // Using macro to avoid shadowing C++ struct constructor, see #1298
> >      > #define mg_str(s) mg_str_s(s)
> >      >
> >      > struct mg_str mg_str(const char *s);
> >      > struct mg_str mg_str_n(const char *s, size_t n);
> >      > -int mg_lower(const char *s);
> >      > -int mg_ncasecmp(const char *s1, const char *s2, size_t len);
> >      > int mg_casecmp(const char *s1, const char *s2);
> >      > -int mg_vcmp(const struct mg_str *s1, const char *s2);
> >      > -int mg_vcasecmp(const struct mg_str *str1, const char *str2);
> >      > int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
> >      > -struct mg_str mg_strstrip(struct mg_str s);
> >      > -struct mg_str mg_strdup(const struct mg_str s);
> >      > -const char *mg_strstr(const struct mg_str haystack, const struct
> >     mg_str needle);
> >      > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
> >     str2);
> >      > bool mg_match(struct mg_str str, struct mg_str pattern, struct
> >     mg_str *caps);
> >      > -bool mg_globmatch(const char *pattern, size_t plen, const char
> >     *s, size_t n);
> >      > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
> >     mg_str *v);
> >      > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
> >     *v, char delim);
> >      > -char *mg_hex(const void *buf, size_t len, char *dst);
> >      > -void mg_unhex(const char *buf, size_t len, unsigned char *to);
> >      > -unsigned long mg_unhexn(const char *s, size_t len);
> >      > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
> >      > -int64_t mg_to64(struct mg_str str);
> >      > -uint64_t mg_tou64(struct mg_str str);
> >      > -char *mg_remove_double_dots(char *s);
> >      > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
> >     *b, char delim);
> >      > +
> >      > +bool mg_str_to_num(struct mg_str, int base, void *val, size_t
> >     val_len);
> >      > +
> >      >
> >      >
> >      >
> >      > +// Single producer, single consumer non-blocking queue
> >      > +
> >      > +struct mg_queue {
> >      > + char *buf;
> >      > + size_t size;
> >      > + volatile size_t tail;
> >      > + volatile size_t head;
> >      > +};
> >      > +
> >      > +void mg_queue_init(struct mg_queue *, char *, size_t); // Init
> >     queue
> >      > +size_t mg_queue_book(struct mg_queue *, char **buf, size_t); //
> >     Reserve space
> >      > +void mg_queue_add(struct mg_queue *, size_t); // Add new message
> >      > +size_t mg_queue_next(struct mg_queue *, char **); // Get oldest
> >     message
> >      > +void mg_queue_del(struct mg_queue *, size_t); // Delete oldest
> >     message
> >      >
> >      >
> >      > -typedef void (*mg_pfn_t)(char, void *); // Custom putchar
> >      > +
> >      > +
> >      > +typedef void (*mg_pfn_t)(char, void *); // Output function
> >      > typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M
> >     printer
> >      > -void mg_pfn_iobuf(char ch, void *param); // iobuf printer
> >      >
> >      > size_t mg_vxprintf(void (*)(char, void *), void *, const char
> >     *fmt, va_list *);
> >      > size_t mg_xprintf(void (*fn)(char, void *), void *, const char
> >     *fmt, ...);
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +// Convenience wrappers around mg_xprintf
> >      > size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
> >     va_list *ap);
> >      > size_t mg_snprintf(char *, size_t, const char *fmt, ...);
> >      > char *mg_vmprintf(const char *fmt, va_list *ap);
> >      > char *mg_mprintf(const char *fmt, ...);
> >      > +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt,
> >     va_list *);
> >      > +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
> >      > +
> >      > +// %M print helper functions
> >      > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
> >     *ap);
> >      > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
> >     va_list *ap);
> >      > +
> >      > +// Various output functions
> >      > +void mg_pfn_iobuf(char ch, void *param); // param: struct
> >     mg_iobuf *
> >      > +void mg_pfn_stdout(char c, void *param); // param: ignored
> >      > +
> >      > +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m",
> >     MG_ESC("hi"))
> >      > +#define MG_ESC(str) mg_print_esc, 0, (str)
> >      >
> >      >
> >      >
> >      > @@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
> >      >
> >      >
> >      > enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG,
> >     MG_LL_VERBOSE };
> >      > +extern int mg_log_level; // Current log level, one of MG_LL_*
> >      > +
> >      > void mg_log(const char *fmt, ...);
> >      > -bool mg_log_prefix(int ll, const char *file, int line, const
> >     char *fname);
> >      > -void mg_log_set(int log_level);
> >      > +void mg_log_prefix(int ll, const char *file, int line, const
> >     char *fname);
> >      > +// bool mg_log2(int ll, const char *file, int line, const char
> >     *fmt, ...);
> >      > void mg_hexdump(const void *buf, size_t len);
> >      > void mg_log_set_fn(mg_pfn_t fn, void *param);
> >      >
> >      > +#define mg_log_set(level_) mg_log_level = (level_)
> >      > +
> >      > #if MG_ENABLE_LOG
> >      > -#define MG_LOG(level, args) \
> >      > - do { \
> >      > - if (mg_log_prefix((level), __FILE__, __LINE__, __func__))
> >     mg_log args; \
> >      > +#define MG_LOG(level, args) \
> >      > + do { \
> >      > + if ((level) <= mg_log_level) { \
> >      > + mg_log_prefix((level), __FILE__, __LINE__, __func__); \
> >      > + mg_log args; \
> >      > + } \
> >      > } while (0)
> >      > #else
> >      > #define MG_LOG(level, args) \
> >      > @@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2,
> >     MG_FS_DIR = 4 };
> >      > // stat(), write(), read() calls.
> >      > struct mg_fs {
> >      > int (*st)(const char *path, size_t *size, time_t *mtime); // stat
> >     file
> >      > - void (*ls)(const char *path, void (*fn)(const char *, void *),
> >     void *);
> >      > + void (*ls)(const char *path, void (*fn)(const char *, void *),
> >      > + void *); // List directory entries: call fn(file_name, fn_data)
> >      > + // for each directory entry
> >      > void *(*op)(const char *path, int flags); // Open file
> >      > void (*cl)(void *fd); // Close file
> >      > size_t (*rd)(void *fd, void *buf, size_t len); // Read file
> >      > @@ -877,28 +1022,84 @@ struct mg_fd {
> >      >
> >      > struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
> >     flags);
> >      > void mg_fs_close(struct mg_fd *fd);
> >      > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
> >     *size);
> >      > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
> >     size_t len);
> >      > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
> >      > bool mg_file_write(struct mg_fs *fs, const char *path, const void
> >     *, size_t);
> >      > bool mg_file_printf(struct mg_fs *fs, const char *path, const
> >     char *fmt, ...);
> >      >
> >      > +// Packed API
> >      > +const char *mg_unpack(const char *path, size_t *size, time_t
> >     *mtime);
> >      > +const char *mg_unlist(size_t no); // Get no'th packed filename
> >      > +struct mg_str mg_unpacked(const char *path); // Packed file as
> >     mg_str
> >      > +
> >      >
> >      >
> >      >
> >      >
> >      >
> >      > +
> >      > +#if MG_ENABLE_ASSERT
> >      > +#include <assert.h>
> >      > +#elif !defined(assert)
> >      > +#define assert(x)
> >      > +#endif
> >      > +
> >      > +void mg_bzero(volatile unsigned char *buf, size_t len);
> >      > void mg_random(void *buf, size_t len);
> >      > char *mg_random_str(char *buf, size_t len);
> >      > uint16_t mg_ntohs(uint16_t net);
> >      > uint32_t mg_ntohl(uint32_t net);
> >      > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
> >      > -uint64_t mg_millis(void);
> >      > +uint64_t mg_millis(void); // Return milliseconds since boot
> >      > +uint64_t mg_now(void); // Return milliseconds since Epoch
> >      > +bool mg_path_is_sane(const struct mg_str path);
> >      >
> >      > #define mg_htons(x) mg_ntohs(x)
> >      > #define mg_htonl(x) mg_ntohl(x)
> >      >
> >      > -#define MG_U32(a, b, c, d) \
> >      > - (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
> >      > - ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
> >      > +#define MG_U32(a, b, c, d) \
> >      > + (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) <<
> >     16) | \
> >      > + ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
> >      > +
> >      > +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
> >      > +
> >      > +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n",
> >     MG_IPADDR_PARTS(&ip))
> >      > +#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
> >      > +#define MG_IPADDR_PARTS(ADDR) \
> >      > + MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
> >      > +
> >      > +#define MG_REG(x) ((volatile uint32_t *) (x))[0]
> >      > +#define MG_BIT(x) (((uint32_t) 1U) << (x))
> >      > +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) &
> >     ~(CLRMASK)) | (SETMASK)
> >      > +
> >      > +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) /
> >     (a)) * (a)))
> >      > +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
> >      > +
> >      > +#if defined(__GNUC__)
> >      > +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
> >      > +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
> >      > +#elif defined(__CCRH__)
> >      > +#define MG_RH850_DISABLE_IRQ() __DI()
> >      > +#define MG_RH850_ENABLE_IRQ() __EI()
> >      > +#else
> >      > +#define MG_ARM_DISABLE_IRQ()
> >      > +#define MG_ARM_ENABLE_IRQ()
> >      > +#endif
> >      > +
> >      > +#if defined(__CC_ARM)
> >      > +#define MG_DSB() __dsb(0xf)
> >      > +#elif defined(__ARMCC_VERSION)
> >      > +#define MG_DSB() __builtin_arm_dsb(0xf)
> >      > +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
> >      > +#define MG_DSB() asm("DSB 0xf")
> >      > +#elif defined(__ICCARM__)
> >      > +#define MG_DSB() __iar_builtin_DSB()
> >      > +#else
> >      > +#define MG_DSB()
> >      > +#endif
> >      > +
> >      > +struct mg_addr;
> >      > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
> >      >
> >      > // Linked list management macros
> >      > #define LIST_ADD_HEAD(type_, head_, elem_) \
> >      > @@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
> >      > size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *,
> >     size_t);
> >      > size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
> >      >
> >      > -int mg_base64_update(unsigned char p, char *to, int len);
> >      > -int mg_base64_final(char *to, int len);
> >      > -int mg_base64_encode(const unsigned char *p, int n, char *to);
> >      > -int mg_base64_decode(const char *src, int n, char *dst);
> >      > +
> >      > +size_t mg_base64_update(unsigned char input_byte, char *buf,
> >     size_t len);
> >      > +size_t mg_base64_final(char *buf, size_t len);
> >      > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
> >     *buf, size_t);
> >      > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
> >     size_t);
> >      >
> >      >
> >      >
> >      > @@ -976,35 +1178,793 @@ typedef struct {
> >      > void mg_sha1_init(mg_sha1_ctx *);
> >      > void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data,
> >     size_t len);
> >      > void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
> >      > +// https://github.com/B-Con/crypto-algorithms
> >     <https://github.com/B-Con/crypto-algorithms>
> >      > +// Author: Brad Conte (brad AT bradconte.com
> >     <http://bradconte.com>)
> >      > +// Disclaimer: This code is presented "as is" without any
> >     guarantees.
> >      > +// Details: Defines the API for the corresponding SHA1
> >     implementation.
> >      > +// Copyright: public domain
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +typedef struct {
> >      > + uint32_t state[8];
> >      > + uint64_t bits;
> >      > + uint32_t len;
> >      > + unsigned char buffer[64];
> >      > +} mg_sha256_ctx;
> >      > +
> >      > +void mg_sha256_init(mg_sha256_ctx *);
> >      > +void mg_sha256_update(mg_sha256_ctx *, const unsigned char
> >     *data, size_t len);
> >      > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
> >      > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
> >     uint8_t *data,
> >      > + size_t datasz);
> >      > +#ifndef TLS_X15519_H
> >      > +#define TLS_X15519_H
> >      > +
> >      > +
> >      > +
> >      > +#define X25519_BYTES 32
> >      > +extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
> >      > +
> >      > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
> >     scalar[X25519_BYTES],
> >      > + const uint8_t x1[X25519_BYTES], int clamp);
> >      > +
> >      > +
> >      > +#endif /* TLS_X15519_H */
> >      >
> >     +/******************************************************************************
> >      > + *
> >      > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
> >     THE GOOD OF ALL
> >      > + *
> >      > + * This is a simple and straightforward implementation of
> >     AES-GCM authenticated
> >      > + * encryption. The focus of this work was correctness &
> >     accuracy. It is written
> >      > + * in straight 'C' without any particular focus upon
> >     optimization or speed. It
> >      > + * should be endian (memory byte order) neutral since the few
> >     places that care
> >      > + * are handled explicitly.
> >      > + *
> >      > + * This implementation of AES-GCM was created by Steven M.
> >     Gibson of GRC.com.
> >      > + *
> >      > + * It is intended for general purpose use, but was written in
> >     support of GRC's
> >      > + * reference implementation of the SQRL (Secure Quick Reliable
> >     Login) client.
> >      > + *
> >      > + * See:
> >     http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
> >     <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
> >      > + *
> >     http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
> >     <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/> \
> >      > + * gcm/gcm-revised-spec.pdf
> >      > + *
> >      > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
> >     WARRANTY MADE
> >      > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
> >     YOUR OWN RISK.
> >      > + *
> >      > +
> >     *******************************************************************************/
> >      > +#ifndef TLS_AES128_H
> >      > +#define TLS_AES128_H
> >      > +
> >      > +typedef unsigned char uchar; // add some convienent shorter types
> >      > +typedef unsigned int uint;
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * AES_CONTEXT : cipher context / holds inter-call data
> >      > +
> >     ******************************************************************************/
> >      > +typedef struct {
> >      > + int mode; // 1 for Encryption, 0 for Decryption
> >      > + int rounds; // keysize-based rounds count
> >      > + uint32_t *rk; // pointer to current round key
> >      > + uint32_t buf[68]; // key expansion buffer
> >      > +} aes_context;
> >      > +
> >      > +
> >      > +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure
> >      > +
> >      >
> >     +/******************************************************************************
> >      > + * GCM_CONTEXT : MUST be called once before ANY use of this library
> >      > +
> >     ******************************************************************************/
> >      > +int mg_gcm_initialize(void);
> >      > +
> >      > +//
> >      > +// aes-gcm.h
> >      > +// MKo
> >      > +//
> >      > +// Created by Markus Kosmal on 20/11/14.
> >      > +//
> >      > +//
> >      > +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned
> >     char *input,
> >      > + size_t input_length, const unsigned char *key,
> >      > + const size_t key_len, const unsigned char *iv,
> >      > + const size_t iv_len, unsigned char *aead,
> >      > + size_t aead_len, unsigned char *tag,
> >      > + const size_t tag_len);
> >      > +
> >      > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
> >     char *input,
> >      > + size_t input_length, const unsigned char *key,
> >      > + const size_t key_len, const unsigned char *iv,
> >      > + const size_t iv_len);
> >      > +
> >      > +#endif /* TLS_AES128_H */
> >      > +
> >      > +// End of aes128 PD
> >      > +
> >      > +
> >      > +
> >      > +#define MG_UECC_SUPPORTS_secp256r1 1
> >      > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
> >     2-clause license. */
> >      > +
> >      > +#ifndef _UECC_H_
> >      > +#define _UECC_H_
> >      > +
> >      > +/* Platform selection options.
> >      > +If MG_UECC_PLATFORM is not defined, the code will try to guess
> >     it based on
> >      > +compiler macros. Possible values for MG_UECC_PLATFORM are
> >     defined below: */
> >      > +#define mg_uecc_arch_other 0
> >      > +#define mg_uecc_x86 1
> >      > +#define mg_uecc_x86_64 2
> >      > +#define mg_uecc_arm 3
> >      > +#define mg_uecc_arm_thumb 4
> >      > +#define mg_uecc_arm_thumb2 5
> >      > +#define mg_uecc_arm64 6
> >      > +#define mg_uecc_avr 7
> >      > +
> >      > +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate
> >     for your platform
> >      > +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly
> >     defined then it will
> >      > +be automatically set based on your platform. */
> >      > +
> >      > +/* Optimization level; trade speed for code size.
> >      > + Larger values produce code that is faster but larger.
> >      > + Currently supported values are 0 - 4; 0 is unusably slow for most
> >      > + applications. Optimization level 4 currently only has an effect
> >     ARM platforms
> >      > + where more than one curve is enabled. */
> >      > +#ifndef MG_UECC_OPTIMIZATION_LEVEL
> >      > +#define MG_UECC_OPTIMIZATION_LEVEL 2
> >      > +#endif
> >      > +
> >      > +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this
> >     will cause a
> >      > +specific function to be used for (scalar) squaring instead of
> >     the generic
> >      > +multiplication function. This can make things faster somewhat
> >     faster, but
> >      > +increases the code size. */
> >      > +#ifndef MG_UECC_SQUARE_FUNC
> >      > +#define MG_UECC_SQUARE_FUNC 0
> >      > +#endif
> >      > +
> >      > +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as
> >     nonzero), this will
> >      > +switch to native little-endian format for *all* arrays passed in
> >     and out of the
> >      > +public API. This includes public and private keys, shared
> >     secrets, signatures
> >      > +and message hashes. Using this switch reduces the amount of call
> >     stack memory
> >      > +used by uECC, since less intermediate translations are required.
> >     Note that this
> >      > +will *only* work on native little-endian processors and it will
> >     treat the
> >      > +uint8_t arrays passed into the public API as word arrays,
> >     therefore requiring
> >      > +the provided byte arrays to be word aligned on architectures
> >     that do not support
> >      > +unaligned accesses. IMPORTANT: Keys and signatures generated with
> >      > +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys
> >     and signatures
> >      > +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties
> >     must use the same
> >      > +endianness. */
> >      > +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
> >      > +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
> >      > +#endif
> >      > +
> >      > +/* Curve support selection. Set to 0 to remove that curve. */
> >      > +#ifndef MG_UECC_SUPPORTS_secp160r1
> >      > +#define MG_UECC_SUPPORTS_secp160r1 0
> >      > +#endif
> >      > +#ifndef MG_UECC_SUPPORTS_secp192r1
> >      > +#define MG_UECC_SUPPORTS_secp192r1 0
> >      > +#endif
> >      > +#ifndef MG_UECC_SUPPORTS_secp224r1
> >      > +#define MG_UECC_SUPPORTS_secp224r1 0
> >      > +#endif
> >      > +#ifndef MG_UECC_SUPPORTS_secp256r1
> >      > +#define MG_UECC_SUPPORTS_secp256r1 1
> >      > +#endif
> >      > +#ifndef MG_UECC_SUPPORTS_secp256k1
> >      > +#define MG_UECC_SUPPORTS_secp256k1 0
> >      > +#endif
> >      > +
> >      > +/* Specifies whether compressed point format is supported.
> >      > + Set to 0 to disable point compression/decompression functions. */
> >      > +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
> >      > +#endif
> >      > +
> >      > +struct MG_UECC_Curve_t;
> >      > +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
> >      > +
> >      > +#ifdef __cplusplus
> >      > +extern "C" {
> >      > +#endif
> >      > +
> >      > +#if MG_UECC_SUPPORTS_secp160r1
> >      > +MG_UECC_Curve mg_uecc_secp160r1(void);
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp192r1
> >      > +MG_UECC_Curve mg_uecc_secp192r1(void);
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp224r1
> >      > +MG_UECC_Curve mg_uecc_secp224r1(void);
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp256r1
> >      > +MG_UECC_Curve mg_uecc_secp256r1(void);
> >      > +#endif
> >      > +#if MG_UECC_SUPPORTS_secp256k1
> >      > +MG_UECC_Curve mg_uecc_secp256k1(void);
> >      > +#endif
> >      > +
> >      > +/* MG_UECC_RNG_Function type
> >      > +The RNG function should fill 'size' random bytes into 'dest'. It
> >     should return 1
> >      > +if 'dest' was filled with random data, or 0 if the random data
> >     could not be
> >      > +generated. The filled-in values should be either truly random,
> >     or from a
> >      > +cryptographically-secure PRNG.
> >      > +
> >      > +A correctly functioning RNG function must be set (using
> >     mg_uecc_set_rng())
> >      > +before calling mg_uecc_make_key() or mg_uecc_sign().
> >      > +
> >      > +Setting a correctly functioning RNG function improves the
> >     resistance to
> >      > +side-channel attacks for mg_uecc_shared_secret() and
> >      > +mg_uecc_sign_deterministic().
> >      > +
> >      > +A correct RNG function is set by default when building for
> >     Windows, Linux, or OS
> >      > +X. If you are building on another POSIX-compliant system that
> >     supports
> >      > +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use
> >     the predefined
> >      > +RNG. For embedded platforms there is no predefined RNG function;
> >     you must
> >      > +provide your own.
> >      > +*/
> >      > +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
> >      > +
> >      > +/* mg_uecc_set_rng() function.
> >      > +Set the function that will be used to generate random bytes. The
> >     RNG function
> >      > +should return 1 if the random data was generated, or 0 if the
> >     random data could
> >      > +not be generated.
> >      > +
> >      > +On platforms where there is no predefined RNG function (eg
> >     embedded platforms),
> >      > +this must be called before mg_uecc_make_key() or mg_uecc_sign()
> >     are used.
> >      > +
> >      > +Inputs:
> >      > + rng_function - The function that will be used to generate
> >     random bytes.
> >      > +*/
> >      > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
> >      > +
> >      > +/* mg_uecc_get_rng() function.
> >      > +
> >      > +Returns the function that will be used to generate random bytes.
> >      > +*/
> >      > +MG_UECC_RNG_Function mg_uecc_get_rng(void);
> >      > +
> >      > +/* mg_uecc_curve_private_key_size() function.
> >      > +
> >      > +Returns the size of a private key for the curve in bytes.
> >      > +*/
> >      > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_curve_public_key_size() function.
> >      > +
> >      > +Returns the size of a public key for the curve in bytes.
> >      > +*/
> >      > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_make_key() function.
> >      > +Create a public/private key pair.
> >      > +
> >      > +Outputs:
> >      > + public_key - Will be filled in with the public key. Must be at
> >     least 2 *
> >      > +the curve size (in bytes) long. For example, if the curve is
> >     secp256r1,
> >      > +public_key must be 64 bytes long. private_key - Will be filled
> >     in with the
> >      > +private key. Must be as long as the curve order; this is
> >     typically the same as
> >      > +the curve size, except for secp160r1. For example, if the curve
> >     is secp256r1,
> >      > +private_key must be 32 bytes long.
> >      > +
> >      > + For secp160r1, private_key must be 21 bytes long! Note that
> >      > +the first byte will almost always be 0 (there is about a 1 in
> >     2^80 chance of it
> >      > +being non-zero).
> >      > +
> >      > +Returns 1 if the key pair was generated successfully, 0 if an
> >     error occurred.
> >      > +*/
> >      > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_shared_secret() function.
> >      > +Compute a shared secret given your secret key and someone else's
> >     public key. If
> >      > +the public key is not from a trusted source and has not been
> >     previously
> >      > +verified, you should verify it first using
> >     mg_uecc_valid_public_key(). Note: It
> >      > +is recommended that you hash the result of
> >     mg_uecc_shared_secret() before using
> >      > +it for symmetric encryption or HMAC.
> >      > +
> >      > +Inputs:
> >      > + public_key - The public key of the remote party.
> >      > + private_key - Your private key.
> >      > +
> >      > +Outputs:
> >      > + secret - Will be filled in with the shared secret value. Must
> >     be the same
> >      > +size as the curve size; for example, if the curve is secp256r1,
> >     secret must be
> >      > +32 bytes long.
> >      > +
> >      > +Returns 1 if the shared secret was generated successfully, 0 if
> >     an error
> >      > +occurred.
> >      > +*/
> >      > +int mg_uecc_shared_secret(const uint8_t *public_key, const
> >     uint8_t *private_key,
> >      > + uint8_t *secret, MG_UECC_Curve curve);
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +/* mg_uecc_compress() function.
> >      > +Compress a public key.
> >      > +
> >      > +Inputs:
> >      > + public_key - The public key to compress.
> >      > +
> >      > +Outputs:
> >      > + compressed - Will be filled in with the compressed public key.
> >     Must be at
> >      > +least (curve size + 1) bytes long; for example, if the curve is
> >     secp256r1,
> >      > + compressed must be 33 bytes long.
> >      > +*/
> >      > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
> >     *compressed,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_decompress() function.
> >      > +Decompress a compressed public key.
> >      > +
> >      > +Inputs:
> >      > + compressed - The compressed public key.
> >      > +
> >      > +Outputs:
> >      > + public_key - Will be filled in with the decompressed public key.
> >      > +*/
> >      > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
> >     *public_key,
> >      > + MG_UECC_Curve curve);
> >      > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
> >      > +
> >      > +/* mg_uecc_valid_public_key() function.
> >      > +Check to see if a public key is valid.
> >      > +
> >      > +Note that you are not required to check for a valid public key
> >     before using any
> >      > +other uECC functions. However, you may wish to avoid spending
> >     CPU time computing
> >      > +a shared secret or verifying a signature using an invalid public
> >     key.
> >      > +
> >      > +Inputs:
> >      > + public_key - The public key to check.
> >      > +
> >      > +Returns 1 if the public key is valid, 0 if it is invalid.
> >      > +*/
> >      > +int mg_uecc_valid_public_key(const uint8_t *public_key,
> >     MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_compute_public_key() function.
> >      > +Compute the corresponding public key for a private key.
> >      > +
> >      > +Inputs:
> >      > + private_key - The private key to compute the public key for
> >      > +
> >      > +Outputs:
> >      > + public_key - Will be filled in with the corresponding public key
> >      > +
> >      > +Returns 1 if the key was computed successfully, 0 if an error
> >     occurred.
> >      > +*/
> >      > +int mg_uecc_compute_public_key(const uint8_t *private_key,
> >     uint8_t *public_key,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_sign() function.
> >      > +Generate an ECDSA signature for a given hash value.
> >      > +
> >      > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
> >     recommended) and
> >      > +pass it in to this function along with your private key.
> >      > +
> >      > +Inputs:
> >      > + private_key - Your private key.
> >      > + message_hash - The hash of the message to sign.
> >      > + hash_size - The size of message_hash in bytes.
> >      > +
> >      > +Outputs:
> >      > + signature - Will be filled in with the signature value. Must be
> >     at least 2 *
> >      > +curve size long. For example, if the curve is secp256r1,
> >     signature must be 64
> >      > +bytes long.
> >      > +
> >      > +Returns 1 if the signature generated successfully, 0 if an error
> >     occurred.
> >      > +*/
> >      > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
> >     *message_hash,
> >      > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
> >      > +
> >      > +/* MG_UECC_HashContext structure.
> >      > +This is used to pass in an arbitrary hash function to
> >      > +mg_uecc_sign_deterministic(). The structure will be used for
> >     multiple hash
> >      > +computations; each time a new hash is computed, init_hash() will
> >     be called,
> >      > +followed by one or more calls to update_hash(), and finally a
> >     call to
> >      > +finish_hash() to produce the resulting hash.
> >      > +
> >      > +The intention is that you will create a structure that includes
> >      > +MG_UECC_HashContext followed by any hash-specific data. For
> >     example:
> >      > +
> >      > +typedef struct SHA256_HashContext {
> >      > + MG_UECC_HashContext uECC;
> >      > + SHA256_CTX ctx;
> >      > +} SHA256_HashContext;
> >      > +
> >      > +void init_SHA256(MG_UECC_HashContext *base) {
> >      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> >      > + SHA256_Init(&context->ctx);
> >      > +}
> >      > +
> >      > +void update_SHA256(MG_UECC_HashContext *base,
> >      > + const uint8_t *message,
> >      > + unsigned message_size) {
> >      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> >      > + SHA256_Update(&context->ctx, message, message_size);
> >      > +}
> >      > +
> >      > +void finish_SHA256(MG_UECC_HashContext *base, uint8_t
> >     *hash_result) {
> >      > + SHA256_HashContext *context = (SHA256_HashContext *)base;
> >      > + SHA256_Final(hash_result, &context->ctx);
> >      > +}
> >      > +
> >      > +... when signing ...
> >      > +{
> >      > + uint8_t tmp[32 + 32 + 64];
> >      > + SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256,
> >     &finish_SHA256, 64,
> >      > +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash,
> >     &ctx.uECC, signature);
> >      > +}
> >      > +*/
> >      > +typedef struct MG_UECC_HashContext {
> >      > + void (*init_hash)(const struct MG_UECC_HashContext *context);
> >      > + void (*update_hash)(const struct MG_UECC_HashContext *context,
> >      > + const uint8_t *message, unsigned message_size);
> >      > + void (*finish_hash)(const struct MG_UECC_HashContext *context,
> >      > + uint8_t *hash_result);
> >      > + unsigned
> >      > + block_size; /* Hash function block size in bytes, eg 64 for
> >     SHA-256. */
> >      > + unsigned
> >      > + result_size; /* Hash function result size in bytes, eg 32 for
> >     SHA-256. */
> >      > + uint8_t *tmp; /* Must point to a buffer of at least (2 *
> >     result_size +
> >      > + block_size) bytes. */
> >      > +} MG_UECC_HashContext;
> >      > +
> >      > +/* mg_uecc_sign_deterministic() function.
> >      > +Generate an ECDSA signature for a given hash value, using a
> >     deterministic
> >      > +algorithm (see RFC 6979). You do not need to set the RNG using
> >     mg_uecc_set_rng()
> >      > +before calling this function; however, if the RNG is defined it
> >     will improve
> >      > +resistance to side-channel attacks.
> >      > +
> >      > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
> >     recommended) and
> >      > +pass it to this function along with your private key and a hash
> >     context. Note
> >      > +that the message_hash does not need to be computed with the same
> >     hash function
> >      > +used by hash_context.
> >      > +
> >      > +Inputs:
> >      > + private_key - Your private key.
> >      > + message_hash - The hash of the message to sign.
> >      > + hash_size - The size of message_hash in bytes.
> >      > + hash_context - A hash context to use.
> >      > +
> >      > +Outputs:
> >      > + signature - Will be filled in with the signature value.
> >      > +
> >      > +Returns 1 if the signature generated successfully, 0 if an error
> >     occurred.
> >      > +*/
> >      > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
> >      > + const uint8_t *message_hash, unsigned hash_size,
> >      > + const MG_UECC_HashContext *hash_context,
> >      > + uint8_t *signature, MG_UECC_Curve curve);
> >      > +
> >      > +/* mg_uecc_verify() function.
> >      > +Verify an ECDSA signature.
> >      > +
> >      > +Usage: Compute the hash of the signed data using the same hash
> >     as the signer and
> >      > +pass it to this function along with the signer's public key and
> >     the signature
> >      > +values (r and s).
> >      > +
> >      > +Inputs:
> >      > + public_key - The signer's public key.
> >      > + message_hash - The hash of the signed data.
> >      > + hash_size - The size of message_hash in bytes.
> >      > + signature - The signature value.
> >      > +
> >      > +Returns 1 if the signature is valid, 0 if it is invalid.
> >      > +*/
> >      > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
> >     *message_hash,
> >      > + unsigned hash_size, const uint8_t *signature,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +#ifdef __cplusplus
> >      > +} /* end of extern "C" */
> >      > +#endif
> >      > +
> >      > +#endif /* _UECC_H_ */
> >      > +
> >      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
> >     2-clause license. */
> >      > +
> >      > +#ifndef _UECC_VLI_H_
> >      > +#define _UECC_VLI_H_
> >      > +
> >      > +//
> >      > +//
> >      > +
> >      > +/* Functions for raw large-integer manipulation. These are only
> >     available
> >      > + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
> >      > +#ifndef MG_UECC_ENABLE_VLI_API
> >      > +#define MG_UECC_ENABLE_VLI_API 0
> >      > +#endif
> >      > +
> >      > +#ifdef __cplusplus
> >      > +extern "C" {
> >      > +#endif
> >      > +
> >      > +#if MG_UECC_ENABLE_VLI_API
> >      > +
> >      > +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
> >      > +
> >      > +/* Constant-time comparison to zero - secure way to compare long
> >     integers */
> >      > +/* Returns 1 if vli == 0, 0 otherwise. */
> >      > +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Returns nonzero if bit 'bit' of vli is set. */
> >      > +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli,
> >     bitcount_t bit);
> >      > +
> >      > +/* Counts the number of bits required to represent vli. */
> >      > +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
> >      > + const wordcount_t max_words);
> >      > +
> >      > +/* Sets dest = src. */
> >      > +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t
> >     *src,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Constant-time comparison function - secure way to compare
> >     long integers */
> >      > +/* Returns one if left == right, zero otherwise */
> >      > +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Constant-time comparison function - secure way to compare
> >     long integers */
> >      > +/* Returns sign of left - right, in constant time. */
> >      > +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, wordcount_t num_words);
> >      > +
> >      > +/* Computes vli = vli >> 1. */
> >      > +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t
> >     num_words);
> >      > +
> >      > +/* Computes result = left + right, returning carry. Can modify
> >     in place. */
> >      > +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = left - right, returning borrow. Can modify
> >     in place. */
> >      > +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = left * right. Result must be 2 * num_words
> >     long. */
> >      > +void mg_uecc_vli_mult(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, wordcount_t num_words);
> >      > +
> >      > +/* Computes result = left^2. Result must be 2 * num_words long. */
> >      > +void mg_uecc_vli_square(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = (left + right) % mod.
> >      > + Assumes that left < mod and right < mod, and that result does
> >     not overlap
> >      > + mod. */
> >      > +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = (left - right) % mod.
> >      > + Assumes that left < mod and right < mod, and that result does
> >     not overlap
> >      > + mod. */
> >      > +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = product % mod, where product is 2N words long.
> >      > + Currently only designed to work for mod == curve->p or curve_n. */
> >      > +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t
> >     *product,
> >      > + const mg_uecc_word_t *mod, wordcount_t num_words);
> >      > +
> >      > +/* Calculates result = product (mod curve->p), where product is
> >     up to
> >      > + 2 * curve->num_words long. */
> >      > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
> >     mg_uecc_word_t *product,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +/* Computes result = (left * right) % mod.
> >      > + Currently only designed to work for mod == curve->p or curve_n. */
> >      > +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
> >      > + wordcount_t num_words);
> >      > +
> >      > +/* Computes result = (left * right) % curve->p. */
> >      > +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *right, MG_UECC_Curve curve);
> >      > +
> >      > +/* Computes result = left^2 % mod.
> >      > + Currently only designed to work for mod == curve->p or curve_n. */
> >      > +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *left,
> >      > + const mg_uecc_word_t *mod, wordcount_t num_words);
> >      > +
> >      > +/* Computes result = left^2 % curve->p. */
> >      > +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
> >      > + const mg_uecc_word_t *left,
> >      > + MG_UECC_Curve curve);
> >      > +
> >      > +/* Computes result = (1 / input) % mod.*/
> >      > +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *input,
> >      > + const mg_uecc_word_t *mod, wordcount_t num_words);
> >      > +
> >      > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
> >      > +/* Calculates a = sqrt(a) (mod curve->p) */
> >      > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
> >      > +#endif
> >      > +
> >      > +/* Converts an integer in uECC native format to big-endian
> >     bytes. */
> >      > +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
> >      > + const mg_uecc_word_t *native);
> >      > +/* Converts big-endian bytes to an integer in uECC native
> >     format. */
> >      > +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const
> >     uint8_t *bytes,
> >      > + int num_bytes);
> >      > +
> >      > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
> >      > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
> >      > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
> >      > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
> >      > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
> >      > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
> >      > +
> >      > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
> >      > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
> >      > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
> >      > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
> >      > +
> >      > +int mg_uecc_valid_point(const mg_uecc_word_t *point,
> >     MG_UECC_Curve curve);
> >      > +
> >      > +/* Multiplies a point by a scalar. Points are represented by the
> >     X coordinate
> >      > + followed by the Y coordinate in the same array, both
> >     coordinates are
> >      > + curve->num_words long. Note that scalar must be
> >     curve->num_n_words long (NOT
> >      > + curve->num_words). */
> >      > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
> >     mg_uecc_word_t *point,
> >      > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
> >      > +
> >      > +/* Generates a random integer in the range 0 < random < top.
> >      > + Both random and top have num_words words. */
> >      > +int mg_uecc_generate_random_int(mg_uecc_word_t *random,
> >      > + const mg_uecc_word_t *top,
> >      > + wordcount_t num_words);
> >      > +
> >      > +#endif /* MG_UECC_ENABLE_VLI_API */
> >      > +
> >      > +#ifdef __cplusplus
> >      > +} /* end of extern "C" */
> >      > +#endif
> >      > +
> >      > +#endif /* _UECC_VLI_H_ */
> >      > +
> >      > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
> >     2-clause license. */
> >      > +
> >      > +#ifndef _UECC_TYPES_H_
> >      > +#define _UECC_TYPES_H_
> >      > +
> >      > +#ifndef MG_UECC_PLATFORM
> >      > +#if defined(__AVR__) && __AVR__
> >      > +#define MG_UECC_PLATFORM mg_uecc_avr
> >      > +#elif defined(__thumb2__) || \
> >      > + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
> >      > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
> >      > +#elif defined(__thumb__)
> >      > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb
> >      > +#elif defined(__arm__) || defined(_M_ARM)
> >      > +#define MG_UECC_PLATFORM mg_uecc_arm
> >      > +#elif defined(__aarch64__)
> >      > +#define MG_UECC_PLATFORM mg_uecc_arm64
> >      > +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
> >      > + defined(__I86__)
> >      > +#define MG_UECC_PLATFORM mg_uecc_x86
> >      > +#elif defined(__amd64__) || defined(_M_X64)
> >      > +#define MG_UECC_PLATFORM mg_uecc_x86_64
> >      > +#else
> >      > +#define MG_UECC_PLATFORM mg_uecc_arch_other
> >      > +#endif
> >      > +#endif
> >      > +
> >      > +#ifndef MG_UECC_ARM_USE_UMAAL
> >      > +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
> >      > +#define MG_UECC_ARM_USE_UMAAL 1
> >      > +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >=
> >     6) && \
> >      > + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
> >      > +#define MG_UECC_ARM_USE_UMAAL 1
> >      > +#else
> >      > +#define MG_UECC_ARM_USE_UMAAL 0
> >      > +#endif
> >      > +#endif
> >      > +
> >      > +#ifndef MG_UECC_WORD_SIZE
> >      > +#if MG_UECC_PLATFORM == mg_uecc_avr
> >      > +#define MG_UECC_WORD_SIZE 1
> >      > +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM ==
> >     mg_uecc_arm64)
> >      > +#define MG_UECC_WORD_SIZE 8
> >      > +#else
> >      > +#define MG_UECC_WORD_SIZE 4
> >      > +#endif
> >      > +#endif
> >      > +
> >      > +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
> >      > + (MG_UECC_WORD_SIZE != 8)
> >      > +#error "Unsupported value for MG_UECC_WORD_SIZE"
> >      > +#endif
> >      > +
> >      > +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
> >      > +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
> >      > +#undef MG_UECC_WORD_SIZE
> >      > +#define MG_UECC_WORD_SIZE 1
> >      > +#endif
> >      > +
> >      > +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \
> >      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
> >      > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
> >      > + (MG_UECC_WORD_SIZE != 4))
> >      > +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
> >      > +#undef MG_UECC_WORD_SIZE
> >      > +#define MG_UECC_WORD_SIZE 4
> >      > +#endif
> >      > +
> >      > +typedef int8_t wordcount_t;
> >      > +typedef int16_t bitcount_t;
> >      > +typedef int8_t cmpresult_t;
> >      > +
> >      > +#if (MG_UECC_WORD_SIZE == 1)
> >      > +
> >      > +typedef uint8_t mg_uecc_word_t;
> >      > +typedef uint16_t mg_uecc_dword_t;
> >      > +
> >      > +#define HIGH_BIT_SET 0x80
> >      > +#define MG_UECC_WORD_BITS 8
> >      > +#define MG_UECC_WORD_BITS_SHIFT 3
> >      > +#define MG_UECC_WORD_BITS_MASK 0x07
> >      > +
> >      > +#elif (MG_UECC_WORD_SIZE == 4)
> >      > +
> >      > +typedef uint32_t mg_uecc_word_t;
> >      > +typedef uint64_t mg_uecc_dword_t;
> >      > +
> >      > +#define HIGH_BIT_SET 0x80000000
> >      > +#define MG_UECC_WORD_BITS 32
> >      > +#define MG_UECC_WORD_BITS_SHIFT 5
> >      > +#define MG_UECC_WORD_BITS_MASK 0x01F
> >      > +
> >      > +#elif (MG_UECC_WORD_SIZE == 8)
> >      > +
> >      > +typedef uint64_t mg_uecc_word_t;
> >      > +
> >      > +#define HIGH_BIT_SET 0x8000000000000000U
> >      > +#define MG_UECC_WORD_BITS 64
> >      > +#define MG_UECC_WORD_BITS_SHIFT 6
> >      > +#define MG_UECC_WORD_BITS_MASK 0x03F
> >      > +
> >      > +#endif /* MG_UECC_WORD_SIZE */
> >      > +
> >      > +#endif /* _UECC_TYPES_H_ */
> >      > +// End of uecc BSD-2
> >      >
> >      >
> >      > struct mg_connection;
> >      > typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
> >      > - void *ev_data, void *fn_data);
> >      > + void *ev_data);
> >      > void mg_call(struct mg_connection *c, int ev, void *ev_data);
> >      > void mg_error(struct mg_connection *c, const char *fmt, ...);
> >      >
> >      > enum {
> >      > - MG_EV_ERROR, // Error char *error_message
> >      > - MG_EV_OPEN, // Connection created NULL
> >      > - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
> >      > - MG_EV_RESOLVE, // Host name is resolved NULL
> >      > - MG_EV_CONNECT, // Connection established NULL
> >      > - MG_EV_ACCEPT, // Connection accepted NULL
> >      > - MG_EV_TLS_HS, // TLS handshake succeeded NULL
> >      > - MG_EV_READ, // Data received from socket long *bytes_read
> >      > - MG_EV_WRITE, // Data written to socket long *bytes_written
> >      > - MG_EV_CLOSE, // Connection closed NULL
> >      > - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message *
> >      > - MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct
> >     mg_http_message *
> >      > - MG_EV_WS_OPEN, // Websocket handshake done struct
> >     mg_http_message *
> >      > - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
> >      > - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
> >      > - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
> >      > - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
> >      > - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
> >      > - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
> >      > - MG_EV_USER // Starting ID for user events
> >      > + MG_EV_ERROR, // Error char *error_message
> >      > + MG_EV_OPEN, // Connection created NULL
> >      > + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
> >      > + MG_EV_RESOLVE, // Host name is resolved NULL
> >      > + MG_EV_CONNECT, // Connection established NULL
> >      > + MG_EV_ACCEPT, // Connection accepted NULL
> >      > + MG_EV_TLS_HS, // TLS handshake succeeded NULL
> >      > + MG_EV_READ, // Data received from socket long *bytes_read
> >      > + MG_EV_WRITE, // Data written to socket long *bytes_written
> >      > + MG_EV_CLOSE, // Connection closed NULL
> >      > + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message *
> >      > + MG_EV_HTTP_MSG, // Full HTTP request/response struct
> >     mg_http_message *
> >      > + MG_EV_WS_OPEN, // Websocket handshake done struct
> >     mg_http_message *
> >      > + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
> >      > + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
> >      > + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
> >      > + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
> >      > + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
> >      > + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
> >      > + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
> >      > + MG_EV_USER // Starting ID for user events
> >      > };
> >      >
> >      >
> >      > @@ -1021,10 +1981,10 @@ struct mg_dns {
> >      > };
> >      >
> >      > struct mg_addr {
> >      > - uint16_t port; // TCP or UDP port in network byte order
> >      > - uint32_t ip; // IP address in network byte order
> >      > - uint8_t ip6[16]; // IPv6 address
> >      > - bool is_ip6; // True when address is IPv6 address
> >      > + uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte
> >     order
> >      > + uint16_t port; // TCP or UDP port in network byte order
> >      > + uint8_t scope_id; // IPv6 scope ID
> >      > + bool is_ip6; // True when address is IPv6 address
> >      > };
> >      >
> >      > struct mg_mgr {
> >      > @@ -1036,12 +1996,14 @@ struct mg_mgr {
> >      > unsigned long nextid; // Next connection ID
> >      > unsigned long timerid; // Next timer ID
> >      > void *userdata; // Arbitrary user data pointer
> >      > + void *tls_ctx; // TLS context shared by all TLS sessions
> >      > uint16_t mqtt_id; // MQTT IDs for pub/sub
> >      > void *active_dns_requests; // DNS requests in progress
> >      > struct mg_timer *timers; // Active timers
> >      > int epoll_fd; // Used when MG_EPOLL_ENABLE=1
> >      > void *priv; // Used by the MIP stack
> >      > size_t extraconnsize; // Used by the MIP stack
> >      > + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup()
> >      > #if MG_ENABLE_FREERTOS_TCP
> >      > SocketSet_t ss; // NOTE(lsm): referenced from socket struct
> >      > #endif
> >      > @@ -1056,6 +2018,8 @@ struct mg_connection {
> >      > unsigned long id; // Auto-incrementing unique connection ID
> >      > struct mg_iobuf recv; // Incoming data
> >      > struct mg_iobuf send; // Outgoing data
> >      > + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE
> >      > + struct mg_iobuf rtls; // TLS only. Incoming encrypted data
> >      > mg_event_handler_t fn; // User-specified event handler function
> >      > void *fn_data; // User-specified function parameter
> >      > mg_event_handler_t pfn; // Protocol-specific handler function
> >      > @@ -1066,6 +2030,7 @@ struct mg_connection {
> >      > unsigned is_client : 1; // Outbound (client) connection
> >      > unsigned is_accepted : 1; // Accepted (server) connection
> >      > unsigned is_resolving : 1; // Non-blocking DNS resolution is in
> >     progress
> >      > + unsigned is_arplooking : 1; // Non-blocking ARP resolution is
> >     in progress
> >      > unsigned is_connecting : 1; // Non-blocking connect is in progress
> >      > unsigned is_tls : 1; // TLS-enabled connection
> >      > unsigned is_tls_hs : 1; // TLS handshake is in progress
> >      > @@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *,
> >     const void *, size_t);
> >      > size_t mg_printf(struct mg_connection *, const char *fmt, ...);
> >      > size_t mg_vprintf(struct mg_connection *, const char *fmt,
> >     va_list *ap);
> >      > bool mg_aton(struct mg_str str, struct mg_addr *addr);
> >      > -int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool
> >     udp);
> >      >
> >      > // These functions are used to integrate with custom network stacks
> >      > struct mg_connection *mg_alloc_conn(struct mg_mgr *);
> >      > void mg_close_conn(struct mg_connection *c);
> >      > bool mg_open_listener(struct mg_connection *c, const char *url);
> >      > +
> >      > +// Utility functions
> >      > +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void
> >     *buf, size_t len);
> >      > +bool mg_wakeup_init(struct mg_mgr *);
> >      > struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
> >     milliseconds,
> >      > unsigned flags, void (*fn)(void *), void *arg);
> >      >
> >      > -// Low-level IO primives used by TLS layer
> >      > -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> >      > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len);
> >      > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
> >      > -
> >      >
> >      >
> >      >
> >      > @@ -1127,7 +2090,6 @@ struct mg_http_message {
> >      > struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers
> >      > struct mg_str body; // Body
> >      > struct mg_str head; // Request + headers
> >      > - struct mg_str chunk; // Chunk for chunked encoding, or partial
> >     body
> >      > struct mg_str message; // Request + headers + body
> >      > };
> >      >
> >      > @@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *,
> >     const char *name, char *, size_t);
> >      > int mg_url_decode(const char *s, size_t n, char *to, size_t
> >     to_len, int form);
> >      > size_t mg_url_encode(const char *s, size_t n, char *buf, size_t
> >     len);
> >      > void mg_http_creds(struct mg_http_message *, char *, size_t, char
> >     *, size_t);
> >      > -bool mg_http_match_uri(const struct mg_http_message *, const
> >     char *glob);
> >      > long mg_http_upload(struct mg_connection *c, struct
> >     mg_http_message *hm,
> >      > - struct mg_fs *fs, const char *path, size_t max_size);
> >      > + struct mg_fs *fs, const char *dir, size_t max_size);
> >      > void mg_http_bauth(struct mg_connection *, const char *user,
> >     const char *pass);
> >      > struct mg_str mg_http_get_header_var(struct mg_str s, struct
> >     mg_str v);
> >      > size_t mg_http_next_multipart(struct mg_str, size_t, struct
> >     mg_http_part *);
> >      > @@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct
> >     mg_connection *c, const char *root,
> >      > const char *fullpath);
> >      >
> >      >
> >      > +#define MG_TLS_NONE 0 // No TLS support
> >      > +#define MG_TLS_MBED 1 // mbedTLS
> >      > +#define MG_TLS_OPENSSL 2 // OpenSSL
> >      > +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL)
> >      > +#define MG_TLS_BUILTIN 3 // Built-in
> >      > +#define MG_TLS_CUSTOM 4 // Custom implementation
> >      > +
> >      > +#ifndef MG_TLS
> >      > +#define MG_TLS MG_TLS_NONE
> >      > +#endif
> >      > +
> >      >
> >      >
> >      >
> >      >
> >      > struct mg_tls_opts {
> >      > - const char *ca; // CA certificate file. For both listeners and
> >     clients
> >      > - const char *crl; // Certificate Revocation List. For clients
> >      > - const char *cert; // Certificate
> >      > - const char *certkey; // Certificate key
> >      > - const char *ciphers; // Cipher list
> >      > - struct mg_str srvname; // If not empty, enables server name
> >     verification
> >      > - struct mg_fs *fs; // FS API for reading certificate files
> >      > + struct mg_str ca; // PEM or DER
> >      > + struct mg_str cert; // PEM or DER
> >      > + struct mg_str key; // PEM or DER
> >      > + struct mg_str name; // If not empty, enable host name verification
> >      > + int skip_verification; // Skip certificate and host name
> >     verification
> >      > };
> >      >
> >      > -void mg_tls_init(struct mg_connection *, const struct
> >     mg_tls_opts *);
> >      > +void mg_tls_init(struct mg_connection *, const struct
> >     mg_tls_opts *opts);
> >      > void mg_tls_free(struct mg_connection *);
> >      > long mg_tls_send(struct mg_connection *, const void *buf, size_t
> >     len);
> >      > long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
> >      > size_t mg_tls_pending(struct mg_connection *);
> >      > void mg_tls_handshake(struct mg_connection *);
> >      >
> >      > +// Private
> >      > +void mg_tls_ctx_init(struct mg_mgr *);
> >      > +void mg_tls_ctx_free(struct mg_mgr *);
> >      > +
> >      > +// Low-level IO primives used by TLS layer
> >      > +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
> >      > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
> >     len);
> >      > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
> >      >
> >      >
> >      >
> >      >
> >      >
> >      >
> >      > -#if MG_ENABLE_MBEDTLS
> >      > +
> >      > +#if MG_TLS == MG_TLS_MBED
> >      > #include <mbedtls/debug.h>
> >      > #include <mbedtls/net_sockets.h>
> >      > #include <mbedtls/ssl.h>
> >      > +#include <mbedtls/ssl_ticket.h>
> >      > +
> >      > +struct mg_tls_ctx {
> >      > + int dummy;
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + mbedtls_ssl_ticket_context tickets;
> >      > +#endif
> >      > +};
> >      >
> >      > struct mg_tls {
> >      > - char *cafile; // CA certificate path
> >      > mbedtls_x509_crt ca; // Parsed CA certificate
> >      > mbedtls_x509_crt cert; // Parsed certificate
> >      > + mbedtls_pk_context pk; // Private key context
> >      > mbedtls_ssl_context ssl; // SSL/TLS context
> >      > mbedtls_ssl_config conf; // SSL-TLS config
> >      > - mbedtls_pk_context pk; // Private key context
> >      > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
> >      > + mbedtls_ssl_ticket_context ticket; // Session tickets context
> >      > +#endif
> >      > };
> >      > #endif
> >      >
> >      >
> >      > -#if MG_ENABLE_OPENSSL
> >      > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
> >      >
> >      > -#ifdef CONFIG_SSL_IMPL_WOLFSSL
> >      > -#include <wolfssl/openssl/err.h>
> >      > -#include <wolfssl/openssl/ssl.h>
> >      > -#else
> >      > #include <openssl/err.h>
> >      > #include <openssl/ssl.h>
> >      > -#endif
> >      >
> >      > struct mg_tls {
> >      > + BIO_METHOD *bm;
> >      > SSL_CTX *ctx;
> >      > SSL *ssl;
> >      > };
> >      > @@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char
> >     *buf, size_t len);
> >      > #define MQTT_CMD_DISCONNECT 14
> >      > #define MQTT_CMD_AUTH 15
> >      >
> >      > +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
> >      > +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
> >      > +#define MQTT_PROP_CONTENT_TYPE 0x03
> >      > +#define MQTT_PROP_RESPONSE_TOPIC 0x08
> >      > +#define MQTT_PROP_CORRELATION_DATA 0x09
> >      > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
> >      > +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
> >      > +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
> >      > +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
> >      > +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
> >      > +#define MQTT_PROP_AUTHENTICATION_DATA 0x16
> >      > +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
> >      > +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
> >      > +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
> >      > +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
> >      > +#define MQTT_PROP_SERVER_REFERENCE 0x1C
> >      > +#define MQTT_PROP_REASON_STRING 0x1F
> >      > +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
> >      > +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
> >      > +#define MQTT_PROP_TOPIC_ALIAS 0x23
> >      > +#define MQTT_PROP_MAXIMUM_QOS 0x24
> >      > +#define MQTT_PROP_RETAIN_AVAILABLE 0x25
> >      > +#define MQTT_PROP_USER_PROPERTY 0x26
> >      > +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
> >      > +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
> >      > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
> >      > +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
> >      > +
> >      > +enum {
> >      > + MQTT_PROP_TYPE_BYTE,
> >      > + MQTT_PROP_TYPE_STRING,
> >      > + MQTT_PROP_TYPE_STRING_PAIR,
> >      > + MQTT_PROP_TYPE_BINARY_DATA,
> >      > + MQTT_PROP_TYPE_VARIABLE_INT,
> >      > + MQTT_PROP_TYPE_INT,
> >      > + MQTT_PROP_TYPE_SHORT
> >      > +};
> >      > +
> >      > enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
> >      >
> >      > +struct mg_mqtt_prop {
> >      > + uint8_t id; // Enumerated at MQTT5 Reference
> >      > + uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types
> >      > + struct mg_str key; // Non-NULL only for user property type
> >      > + struct mg_str val; // Non-NULL only for UTF-8 types and user
> >     properties
> >      > +};
> >      > +
> >      > struct mg_mqtt_opts {
> >      > - struct mg_str user; // Username, can be empty
> >      > - struct mg_str pass; // Password, can be empty
> >      > - struct mg_str client_id; // Client ID
> >      > - struct mg_str will_topic; // Will topic
> >      > - struct mg_str will_message; // Will message
> >      > - uint8_t will_qos; // Will message quality of service
> >      > - uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4.
> >      > - uint16_t keepalive; // Keep-alive timer in seconds
> >      > - bool will_retain; // Retain last will
> >      > - bool clean; // Use clean session, 0 or 1
> >      > + struct mg_str user; // Username, can be empty
> >      > + struct mg_str pass; // Password, can be empty
> >      > + struct mg_str client_id; // Client ID
> >      > + struct mg_str topic; // message/subscription topic
> >      > + struct mg_str message; // message content
> >      > + uint8_t qos; // message quality of service
> >      > + uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4
> >      > + uint16_t keepalive; // Keep-alive timer in seconds
> >      > + uint16_t retransmit_id; // For PUBLISH, init to 0
> >      > + bool retain; // Retain flag
> >      > + bool clean; // Clean session flag
> >      > + struct mg_mqtt_prop *props; // MQTT5 props array
> >      > + size_t num_props; // number of props
> >      > + struct mg_mqtt_prop *will_props; // Valid only for CONNECT
> >     packet (MQTT5)
> >      > + size_t num_will_props; // Number of will props
> >      > };
> >      >
> >      > struct mg_mqtt_message {
> >      > - struct mg_str topic; // Parsed topic
> >      > - struct mg_str data; // Parsed message
> >      > - struct mg_str dgram; // Whole MQTT datagram, including headers
> >      > - uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP,
> >     SUBACK, PUBLISH
> >      > - uint8_t cmd; // MQTT command, one of MQTT_CMD_*
> >      > - uint8_t qos; // Quality of service
> >      > - uint8_t ack; // Connack return code. 0 - success
> >      > + struct mg_str topic; // Parsed topic for PUBLISH
> >      > + struct mg_str data; // Parsed message for PUBLISH
> >      > + struct mg_str dgram; // Whole MQTT packet, including headers
> >      > + uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK,
> >     PUBLISH
> >      > + uint8_t cmd; // MQTT command, one of MQTT_CMD_*
> >      > + uint8_t qos; // Quality of service
> >      > + uint8_t ack; // CONNACK return code, 0 = success
> >      > + size_t props_start; // Offset to the start of the properties
> >     (MQTT5)
> >      > + size_t props_size; // Length of the properties
> >      > };
> >      >
> >      > struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char
> >     *url,
> >      > @@ -1327,15 +2364,16 @@ struct mg_connection
> >     *mg_mqtt_connect(struct mg_mgr *, const char *url,
> >      > struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
> >     char *url,
> >      > mg_event_handler_t fn, void *fn_data);
> >      > void mg_mqtt_login(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts);
> >      > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
> >      > - struct mg_str data, int qos, bool retain);
> >      > -void mg_mqtt_sub(struct mg_connection *, struct mg_str topic,
> >     int qos);
> >      > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
> >     mg_mqtt_opts *opts);
> >      > +void mg_mqtt_sub(struct mg_connection *, const struct
> >     mg_mqtt_opts *opts);
> >      > int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct
> >     mg_mqtt_message *);
> >      > void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd,
> >     uint8_t flags,
> >      > uint32_t len);
> >      > void mg_mqtt_ping(struct mg_connection *);
> >      > void mg_mqtt_pong(struct mg_connection *);
> >      > -void mg_mqtt_disconnect(struct mg_connection *);
> >      > +void mg_mqtt_disconnect(struct mg_connection *, const struct
> >     mg_mqtt_opts *);
> >      > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct
> >     mg_mqtt_prop *,
> >      > + size_t ofs);
> >      >
> >      >
> >      >
> >      > @@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf,
> >     size_t len, size_t ofs,
> >      > enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2,
> >     MG_JSON_NOT_FOUND = -3 };
> >      > int mg_json_get(struct mg_str json, const char *path, int *toklen);
> >      >
> >      > +struct mg_str mg_json_get_tok(struct mg_str json, const char
> >     *path);
> >      > bool mg_json_get_num(struct mg_str json, const char *path, double
> >     *v);
> >      > bool mg_json_get_bool(struct mg_str json, const char *path, bool
> >     *v);
> >      > long mg_json_get_long(struct mg_str json, const char *path, long
> >     dflt);
> >      > @@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json,
> >     const char *path);
> >      > char *mg_json_get_hex(struct mg_str json, const char *path, int
> >     *len);
> >      > char *mg_json_get_b64(struct mg_str json, const char *path, int
> >     *len);
> >      >
> >      > +bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
> >      > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
> >     *key,
> >      > + struct mg_str *val);
> >      > +
> >      >
> >      >
> >      >
> >      > @@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *,
> >     const char *fmt, va_list *ap);
> >      > void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt,
> >     ...);
> >      > void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt,
> >     va_list *);
> >      > void mg_rpc_list(struct mg_rpc_req *r);
> >      > +// Copyright (c) 2023 Cesanta Software Limited
> >      > +// All rights reserved
> >      >
> >      >
> >      >
> >      >
> >      >
> >      > -struct mip_if; // MIP network interface
> >      > +#define MG_OTA_NONE 0 // No OTA support
> >      > +#define MG_OTA_FLASH 1 // OTA via an internal flash
> >      > +#define MG_OTA_ESP32 2 // ESP32 OTA implementation
> >      > +#define MG_OTA_CUSTOM 100 // Custom implementation
> >      >
> >      > -struct mip_driver {
> >      > - bool (*init)(struct mip_if *); // Initialise driver
> >      > - size_t (*tx)(const void *, size_t, struct mip_if *); //
> >     Transmit frame
> >      > - size_t (*rx)(void *buf, size_t len, struct mip_if *); //
> >     Receive frame (poll)
> >      > - bool (*up)(struct mip_if *); // Up/down status
> >      > -};
> >      > +#ifndef MG_OTA
> >      > +#define MG_OTA MG_OTA_NONE
> >      > +#endif
> >      > +
> >      > +#if defined(__GNUC__) && !defined(__APPLE__)
> >      > +#define MG_IRAM __attribute__((section(".iram")))
> >      > +#else
> >      > +#define MG_IRAM
> >      > +#endif
> >      >
> >      > -// Receive queue - single producer, single consumer queue.
> >     Interrupt-based
> >      > -// drivers copy received frames to the queue in interrupt
> >     context. mip_poll()
> >      > -// function runs in event loop context, reads from the queue
> >      > -struct queue {
> >      > - uint8_t *buf;
> >      > - size_t len;
> >      > - volatile size_t tail, head;
> >      > +// Firmware update API
> >      > +bool mg_ota_begin(size_t new_firmware_size); // Start writing
> >      > +bool mg_ota_write(const void *buf, size_t len); // Write chunk,
> >     aligned to 1k
> >      > +bool mg_ota_end(void); // Stop writing
> >      > +
> >      > +enum {
> >      > + MG_OTA_UNAVAILABLE = 0, // No OTA information is present
> >      > + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after
> >     the OTA
> >      > + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback
> >      > + MG_OTA_COMMITTED = 3 // The firmware is good
> >      > };
> >      > +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
> >      > +
> >      > +int mg_ota_status(int firmware); // Return firmware status MG_OTA_*
> >      > +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
> >      > +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp,
> >     UNIX UTC epoch
> >      > +size_t mg_ota_size(int firmware); // Firmware size
> >      > +
> >      > +bool mg_ota_commit(void); // Commit current firmware
> >      > +bool mg_ota_rollback(void); // Rollback to the previous firmware
> >      > +MG_IRAM void mg_ota_boot(void); // Bootloader function
> >      > +// Copyright (c) 2023 Cesanta Software Limited
> >      > +// All rights reserved
> >      >
> >      > -#define MIP_ARP_ENTRIES 5 // Number of ARP cache entries.
> >     Maximum 21
> >      > -#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES) // ARP cache size
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#define MG_DEVICE_NONE 0 // Dummy system
> >      > +
> >      > +#define MG_DEVICE_STM32H5 1 // STM32 H5
> >      > +#define MG_DEVICE_STM32H7 2 // STM32 H7
> >      > +#define MG_DEVICE_CH32V307 100 // WCH CH32V307
> >      > +#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
> >      > +#define MG_DEVICE_RT1020 300 // IMXRT1020
> >      > +#define MG_DEVICE_RT1060 301 // IMXRT1060
> >      > +#define MG_DEVICE_CUSTOM 1000 // Custom implementation
> >      > +
> >      > +#ifndef MG_DEVICE
> >      > +#define MG_DEVICE MG_DEVICE_NONE
> >      > +#endif
> >      > +
> >      > +// Flash information
> >      > +void *mg_flash_start(void); // Return flash start address
> >      > +size_t mg_flash_size(void); // Return flash size
> >      > +size_t mg_flash_sector_size(void); // Return flash sector size
> >      > +size_t mg_flash_write_align(void); // Return flash write align,
> >     minimum 4
> >      > +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
> >      > +
> >      > +// Write, erase, swap bank
> >      > +bool mg_flash_write(void *addr, const void *buf, size_t len);
> >      > +bool mg_flash_erase(void *sector);
> >      > +bool mg_flash_swap_bank(void);
> >      > +
> >      > +// Convenience functions to store data on a flash sector with
> >     wear levelling
> >      > +// If `sector` is NULL, then the last sector of flash is used
> >      > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
> >     len);
> >      > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
> >     size_t len);
> >      > +
> >      > +void mg_device_reset(void); // Reboot device immediately
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +
> >      > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
> >      > +struct mg_tcpip_if; // Mongoose TCP/IP network interface
> >      > +
> >      > +struct mg_tcpip_driver {
> >      > + bool (*init)(struct mg_tcpip_if *); // Init driver
> >      > + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); //
> >     Transmit frame
> >      > + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); //
> >     Receive frame
> >      > + bool (*up)(struct mg_tcpip_if *); // Up/down status
> >      > +};
> >      >
> >      > // Network interface
> >      > -struct mip_if {
> >      > - uint8_t mac[6]; // MAC address. Must be set to a valid MAC
> >      > - uint32_t ip, mask, gw; // IP address, mask, default gateway
> >      > - struct mg_str rx; // Output (TX) buffer
> >      > - struct mg_str tx; // Input (RX) buffer
> >      > - bool enable_dhcp_client; // Enable DCHP client
> >      > - bool enable_dhcp_server; // Enable DCHP server
> >      > - struct mip_driver *driver; // Low level driver
> >      > - void *driver_data; // Driver-specific data
> >      > - struct mg_mgr *mgr; // Mongoose event manager
> >      > - struct queue queue; // Set queue.len for interrupt based drivers
> >      > +struct mg_tcpip_if {
> >      > + uint8_t mac[6]; // MAC address. Must be set to a valid MAC
> >      > + uint32_t ip, mask, gw; // IP address, mask, default gateway
> >      > + struct mg_str tx; // Output (TX) buffer
> >      > + bool enable_dhcp_client; // Enable DCHP client
> >      > + bool enable_dhcp_server; // Enable DCHP server
> >      > + bool enable_get_gateway; // DCHP server sets client as gateway
> >      > + bool enable_crc32_check; // Do a CRC check on RX frames and
> >     strip it
> >      > + bool enable_mac_check; // Do a MAC check on RX frames
> >      > + struct mg_tcpip_driver *driver; // Low level driver
> >      > + void *driver_data; // Driver-specific data
> >      > + struct mg_mgr *mgr; // Mongoose event manager
> >      > + struct mg_queue recv_queue; // Receive queue
> >      > + uint16_t mtu; // Interface MTU
> >      > +#define MG_TCPIP_MTU_DEFAULT 1500
> >      >
> >      > // Internal state, user can use it but should not change it
> >      > - uint64_t now; // Current time
> >      > - uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
> >      > - uint64_t lease_expire; // Lease expiration time
> >      > - uint8_t arp_cache[MIP_ARP_CS]; // Each entry is 12 bytes
> >      > - uint16_t eport; // Next ephemeral port
> >      > - uint16_t dropped; // Number of dropped frames
> >      > - uint8_t state; // Current state
> >      > -#define MIP_STATE_DOWN 0 // Interface is down
> >      > -#define MIP_STATE_UP 1 // Interface is up
> >      > -#define MIP_STATE_READY 2 // Interface is up and has IP
> >      > + uint8_t gwmac[6]; // Router's MAC
> >      > + uint64_t now; // Current time
> >      > + uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
> >      > + uint64_t lease_expire; // Lease expiration time, in ms
> >      > + uint16_t eport; // Next ephemeral port
> >      > + volatile uint32_t ndrop; // Number of received, but dropped frames
> >      > + volatile uint32_t nrecv; // Number of received frames
> >      > + volatile uint32_t nsent; // Number of transmitted frames
> >      > + volatile uint32_t nerr; // Number of driver errors
> >      > + uint8_t state; // Current state
> >      > +#define MG_TCPIP_STATE_DOWN 0 // Interface is down
> >      > +#define MG_TCPIP_STATE_UP 1 // Interface is up
> >      > +#define MG_TCPIP_STATE_REQ 2 // Interface is up and has
> >     requested an IP
> >      > +#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP
> >     assigned
> >      > };
> >      >
> >      > -void mip_init(struct mg_mgr *, struct mip_if *);
> >      > -void mip_free(struct mip_if *);
> >      > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
> >      > -size_t mip_qread(void *buf, struct mip_if *ifp);
> >      > -// conveniency rx function for IRQ-driven drivers
> >      > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
> >      > -
> >      > -extern struct mip_driver mip_driver_stm32;
> >      > -extern struct mip_driver mip_driver_w5500;
> >      > -extern struct mip_driver mip_driver_tm4c;
> >      > +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
> >      > +void mg_tcpip_free(struct mg_tcpip_if *);
> >      > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
> >     *ifp);
> >      > +
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_same54;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_ra;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
> >      > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
> >      >
> >      > // Drivers that require SPI, can use this SPI abstraction
> >      > -struct mip_spi {
> >      > +struct mg_tcpip_spi {
> >      > void *spi; // Opaque SPI bus descriptor
> >      > void (*begin)(void *); // SPI begin: slave select low
> >      > void (*end)(void *); // SPI end: slave select high
> >      > uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1
> >     byte, read reply
> >      > };
> >      > +#endif
> >      >
> >      > -#ifdef MIP_QPROFILE
> >      > -enum {
> >      > - QP_IRQTRIGGERED = 0, // payload is number of interrupts so far
> >      > - QP_FRAMEPUSHED, // available space in the frame queue
> >      > - QP_FRAMEPOPPED, // available space in the frame queue
> >      > - QP_FRAMEDONE, // available space in the frame queue
> >      > - QP_FRAMEDROPPED, // number of dropped frames
> >      > - QP_QUEUEOVF // profiling queue is full, payload is number of
> >     frame drops
> >      > +
> >      > +
> >      > +// Macros to record timestamped events that happens with a
> >     connection.
> >      > +// They are saved into a c->prof IO buffer, each event is a name
> >     and a 32-bit
> >      > +// timestamp in milliseconds since connection init time.
> >      > +//
> >      > +// Test (run in two separate terminals):
> >      > +// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
> >      > +// curl localhost:8000
> >      > +// Output:
> >      > +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile:
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE
> >      > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE
> >      > +//
> >      > +// Usage:
> >      > +// Enable profiling by setting MG_ENABLE_PROFILE=1
> >      > +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like
> >     to measure
> >      > +
> >      > +#if MG_ENABLE_PROFILE
> >      > +struct mg_profitem {
> >      > + const char *name; // Event name
> >      > + uint32_t timestamp; // Milliseconds since connection creation
> >     (MG_EV_OPEN)
> >      > };
> >      >
> >      > -void qp_mark(unsigned int type, int len);
> >      > -void qp_log(void); // timestamp, type, payload
> >      > -void qp_init(void);
> >      > +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items
> >     wih to realloc
> >      > +
> >      > +// Adding a profile item to the c->prof. Must be as fast as
> >     possible.
> >      > +// Reallocation of the c->prof iobuf is not desirable here,
> >     that's why we
> >      > +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
> >      > +// This macro just inits and copies 8 bytes, and calls mg_millis(),
> >      > +// which should be fast enough.
> >      > +#define MG_PROF_ADD(c, name_) \
> >      > + do { \
> >      > + struct mg_iobuf *io = &c->prof; \
> >      > + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \
> >      > + struct mg_profitem item = {name_, (uint32_t) mg_millis() -
> >     inittime}; \
> >      > + mg_iobuf_add(io, io->len, &item, sizeof(item)); \
> >      > + } while (0)
> >      > +
> >      > +// Initialising profile for a new connection. Not time sensitive
> >      > +#define MG_PROF_INIT(c) \
> >      > + do { \
> >      > + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
> >      > + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \
> >      > + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \
> >      > + } while (0)
> >      > +
> >      > +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
> >      > +
> >      > +// Dumping the profile. Not time sensitive
> >      > +#define MG_PROF_DUMP(c) \
> >      > + do { \
> >      > + struct mg_iobuf *io = &c->prof; \
> >      > + struct mg_profitem *p = (struct mg_profitem *) io->buf; \
> >      > + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \
> >      > + MG_INFO(("%lu profile:", c->id)); \
> >      > + while (p < e) { \
> >      > + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
> >      > + p++; \
> >      > + } \
> >      > + } while (0)
> >      > +
> >      > #else
> >      > -#define qp_mark(a, b)
> >      > +#define MG_PROF_INIT(c)
> >      > +#define MG_PROF_FREE(c)
> >      > +#define MG_PROF_ADD(c, name)
> >      > +#define MG_PROF_DUMP(c)
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
> >     MG_ENABLE_DRIVER_CMSIS
> >      > +
> >      > +#include "Driver_ETH_MAC.h" // keep this include
> >      > +#include "Driver_ETH_PHY.h" // keep this include
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
> >     MG_ENABLE_DRIVER_IMXRT
> >      > +
> >      > +struct mg_tcpip_driver_imxrt_data {
> >      > + // MDC clock divider. MDC clock is derived from IPS Bus clock
> >     (ipg_clk),
> >      > + // must not exceed 2.5MHz. Configuration for clock range
> >     2.36~2.50 MHz
> >      > + // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
> >      > + // ipg_clk mdc_cr VALUE
> >      > + // --------------------------
> >      > + // -1 <-- TODO() tell driver to guess the value
> >      > + // 25 MHz 4
> >      > + // 33 MHz 6
> >      > + // 40 MHz 7
> >      > + // 50 MHz 9
> >      > + // 66 MHz 13
> >      > + int mdc_cr; // Valid values: -1 to 63
> >      > +
> >      > + uint8_t phy_addr; // PHY address
> >      > +};
> >      > +
> >      > +#ifndef MG_TCPIP_PHY_ADDR
> >      > +#define MG_TCPIP_PHY_ADDR 2
> >      > +#endif
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 24
> >      > +#endif
> >      > +
> >      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> >      > + do { \
> >      > + static struct mg_tcpip_driver_imxrt_data driver_data_; \
> >      > + static struct mg_tcpip_if mif_; \
> >      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> >      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> >      > + mif_.ip = MG_TCPIP_IP; \
> >      > + mif_.mask = MG_TCPIP_MASK; \
> >      > + mif_.gw = MG_TCPIP_GW; \
> >      > + mif_.driver = &mg_tcpip_driver_imxrt; \
> >      > + mif_.driver_data = &driver_data_; \
> >      > + MG_SET_MAC_ADDRESS(mif_.mac); \
> >      > + mg_tcpip_init(mgr, &mif_); \
> >      > + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
> >      > + } while (0)
> >      > +
> >      > #endif
> >      >
> >      >
> >      > -struct mip_driver_stm32_data {
> >      > +
> >      > +
> >      > +struct mg_phy {
> >      > + uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
> >      > + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
> >      > +};
> >      > +
> >      > +// PHY configuration settings, bitmask
> >      > +enum {
> >      > + MG_PHY_LEDS_ACTIVE_HIGH =
> >      > + (1 << 0), // Set if PHY LEDs are connected to ground
> >      > + MG_PHY_CLOCKS_MAC =
> >      > + (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
> >      > +};
> >      > +
> >      > +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
> >      > +
> >      > +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
> >      > +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
> >      > + uint8_t *speed);
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
> >     MG_ENABLE_DRIVER_RA
> >      > +
> >      > +struct mg_tcpip_driver_ra_data {
> >      > + // MDC clock "divider". MDC clock is software generated,
> >      > + uint32_t clock; // core clock frequency in Hz
> >      > + uint16_t irqno; // IRQn, R_ICU->IELSR[irqno]
> >      > + uint8_t phy_addr; // PHY address
> >      > +};
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
> >     MG_ENABLE_DRIVER_SAME54
> >      > +
> >      > +struct mg_tcpip_driver_same54_data {
> >      > + int mdc_cr;
> >      > +};
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 5
> >      > +#endif
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
> >      > + MG_ENABLE_DRIVER_STM32F
> >      > +
> >      > +struct mg_tcpip_driver_stm32f_data {
> >      > // MDC clock divider. MDC clock is derived from HCLK, must not
> >     exceed 2.5MHz
> >      > // HCLK range DIVIDER mdc_cr VALUE
> >      > // -------------------------------------
> >      > @@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
> >      > // 216-310 MHz HCLK/124 5
> >      > // 110, 111 Reserved
> >      > int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> >      > +
> >      > + uint8_t phy_addr; // PHY address
> >      > +};
> >      > +
> >      > +#ifndef MG_TCPIP_PHY_ADDR
> >      > +#define MG_TCPIP_PHY_ADDR 0
> >      > +#endif
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 4
> >      > +#endif
> >      > +
> >      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> >      > + do { \
> >      > + static struct mg_tcpip_driver_stm32f_data driver_data_; \
> >      > + static struct mg_tcpip_if mif_; \
> >      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> >      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> >      > + mif_.ip = MG_TCPIP_IP; \
> >      > + mif_.mask = MG_TCPIP_MASK; \
> >      > + mif_.gw = MG_TCPIP_GW; \
> >      > + mif_.driver = &mg_tcpip_driver_stm32f; \
> >      > + mif_.driver_data = &driver_data_; \
> >      > + MG_SET_MAC_ADDRESS(mif_.mac); \
> >      > + mg_tcpip_init(mgr, &mif_); \
> >      > + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
> >      > + } while (0)
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
> >      > + MG_ENABLE_DRIVER_STM32H
> >      > +
> >      > +struct mg_tcpip_driver_stm32h_data {
> >      > + // MDC clock divider. MDC clock is derived from HCLK, must not
> >     exceed 2.5MHz
> >      > + // HCLK range DIVIDER mdc_cr VALUE
> >      > + // -------------------------------------
> >      > + // -1 <-- tell driver to guess the value
> >      > + // 60-100 MHz HCLK/42 0
> >      > + // 100-150 MHz HCLK/62 1
> >      > + // 20-35 MHz HCLK/16 2
> >      > + // 35-60 MHz HCLK/26 3
> >      > + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI
> >      > + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI
> >      > + // 110, 111 Reserved
> >      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> >      > +
> >      > + uint8_t phy_addr; // PHY address
> >      > + uint8_t phy_conf; // PHY config
> >      > };
> >      >
> >      > +#ifndef MG_TCPIP_PHY_ADDR
> >      > +#define MG_TCPIP_PHY_ADDR 0
> >      > +#endif
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 4
> >      > +#endif
> >      > +
> >      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> >      > + do { \
> >      > + static struct mg_tcpip_driver_stm32h_data driver_data_; \
> >      > + static struct mg_tcpip_if mif_; \
> >      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> >      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> >      > + mif_.ip = MG_TCPIP_IP; \
> >      > + mif_.mask = MG_TCPIP_MASK; \
> >      > + mif_.gw = MG_TCPIP_GW; \
> >      > + mif_.driver = &mg_tcpip_driver_stm32h; \
> >      > + mif_.driver_data = &driver_data_; \
> >      > + MG_SET_MAC_ADDRESS(mif_.mac); \
> >      > + mg_tcpip_init(mgr, &mif_); \
> >      > + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
> >      > + } while (0)
> >      > +
> >      > +#endif
> >      > +
> >      >
> >      > -struct mip_driver_tm4c_data {
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
> >     MG_ENABLE_DRIVER_TM4C
> >      > +
> >      > +struct mg_tcpip_driver_tm4c_data {
> >      > // MDC clock divider. MDC clock is derived from SYSCLK, must not
> >     exceed 2.5MHz
> >      > // SYSCLK range DIVIDER mdc_cr VALUE
> >      > // -------------------------------------
> >      > @@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
> >      > int mdc_cr; // Valid values: -1, 0, 1, 2, 3
> >      > };
> >      >
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 1
> >      > +#endif
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
> >     MG_ENABLE_DRIVER_W5500
> >      > +
> >      > +#undef MG_ENABLE_TCPIP_DRIVER_INIT
> >      > +#define MG_ENABLE_TCPIP_DRIVER_INIT 0
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
> >     MG_ENABLE_DRIVER_XMC7
> >      > +
> >      > +struct mg_tcpip_driver_xmc7_data {
> >      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> >      > + uint8_t phy_addr;
> >      > +};
> >      > +
> >      > +#ifndef MG_TCPIP_PHY_ADDR
> >      > +#define MG_TCPIP_PHY_ADDR 0
> >      > +#endif
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 3
> >      > +#endif
> >      > +
> >      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> >      > + do { \
> >      > + static struct mg_tcpip_driver_xmc7_data driver_data_; \
> >      > + static struct mg_tcpip_if mif_; \
> >      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> >      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> >      > + mif_.ip = MG_TCPIP_IP; \
> >      > + mif_.mask = MG_TCPIP_MASK; \
> >      > + mif_.gw = MG_TCPIP_GW; \
> >      > + mif_.driver = &mg_tcpip_driver_xmc7; \
> >      > + mif_.driver_data = &driver_data_; \
> >      > + MG_SET_MAC_ADDRESS(mif_.mac); \
> >      > + mg_tcpip_init(mgr, &mif_); \
> >      > + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
> >      > + } while (0)
> >      > +
> >      > +#endif
> >      > +
> >      > +
> >      > +
> >      > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
> >     MG_ENABLE_DRIVER_XMC
> >      > +
> >      > +struct mg_tcpip_driver_xmc_data {
> >      > + // 13.2.8.1 Station Management Functions
> >      > + // MDC clock divider (). MDC clock is derived from ETH MAC clock
> >      > + // It must not exceed 2.5MHz
> >      > + // ETH Clock range DIVIDER mdc_cr VALUE
> >      > + // --------------------------------------------
> >      > + // -1 <-- tell driver to guess the value
> >      > + // 60-100 MHz ETH Clock/42 0
> >      > + // 100-150 MHz ETH Clock/62 1
> >      > + // 20-35 MHz ETH Clock/16 2
> >      > + // 35-60 MHz ETH Clock/26 3
> >      > + // 150-250 MHz ETH Clock/102 4
> >      > + // 250-300 MHz ETH Clock/124 5
> >      > + // 110, 111 Reserved
> >      > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
> >      > + uint8_t phy_addr;
> >      > +};
> >      > +
> >      > +#ifndef MG_TCPIP_PHY_ADDR
> >      > +#define MG_TCPIP_PHY_ADDR 0
> >      > +#endif
> >      > +
> >      > +#ifndef MG_DRIVER_MDC_CR
> >      > +#define MG_DRIVER_MDC_CR 4
> >      > +#endif
> >      > +
> >      > +#define MG_TCPIP_DRIVER_INIT(mgr) \
> >      > + do { \
> >      > + static struct mg_tcpip_driver_xmc_data driver_data_; \
> >      > + static struct mg_tcpip_if mif_; \
> >      > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
> >      > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
> >      > + mif_.ip = MG_TCPIP_IP; \
> >      > + mif_.mask = MG_TCPIP_MASK; \
> >      > + mif_.gw = MG_TCPIP_GW; \
> >      > + mif_.driver = &mg_tcpip_driver_xmc; \
> >      > + mif_.driver_data = &driver_data_; \
> >      > + MG_SET_MAC_ADDRESS(mif_.mac); \
> >      > + mg_tcpip_init(mgr, &mif_); \
> >      > + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
> >      > + } while (0)
> >      > +
> >      > +#endif
> >      > +
> >      > #ifdef __cplusplus
> >      > }
> >      > #endif
> >
> > --
> > You received this message because you are subscribed to the Google
> > Groups "swupdate" group.
> > To unsubscribe from this group and stop receiving emails from it, send
> > an email to swupdate+unsubscribe@googlegroups.com
> > <mailto:swupdate+unsubscribe@googlegroups.com>.
> > To view this discussion on the web visit
> > https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com <https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com?utm_medium=email&utm_source=footer>.
Stefano Babic July 14, 2024, 9:18 a.m. UTC | #5
Hi Michael,

On 09.07.24 21:36, Michael wrote:
> Hi Stefano,
>
> Am Di., 9. Juli 2024 um 14:21 Uhr schrieb Stefano Babic
> <stefano.babic@swupdate.org>:
>>
>> Hi Michael,
>>
>> On 09.07.24 13:31, Michael Glembotzki wrote:
>>> Problem still exists with V3. The patch files I created with format
>>> patch are OK, but when sending it to Patchwork something seems to break
>>> with the header.
>>
>> It depends how you send the patch. You should still use git, "git
>> send-email". This guarantees thatmails are not damaged by your mailer.
>
> Sure, I used "git send-email" as always. Since I received a copy of
> the patch files in CC, I cannot confirm that there is a problem on my
> end. The patches were easy to apply with "git am" . Things are
> different in patchwork. Everything is okay in the bottom view with
> syntax highlighting, but the content at the top looks different. Is
> there a size limit for patch files in Patchwork?
>
> I can't recreate the cause and don't want to continue spamming with a
> V4, so here are the commits in Github:
> https://github.com/linkjumper/swupdate/tree/feature/migl/mongoose_update_V3
>
> Is this suitable for the review?

Ok - I have tested them on a tryout branch. coverity is reported new
issues  - issues in mongoose.c should be fixed upstream to mongoose, but
there are a couple in mongoose_interface. c and mongoose_multipart.c.
Could you take a look to them and fix ? Thanks.

Best regards,
Stefano


>
> Best regards
> Michael
>
>
>
>> Best regards,
>> Stefano
>>
>>> I could post a github link, or do you have any idea
>>> what the problem could be?
>>>
>>> Best regards
>>> Michael
>>> Stefano Babic schrieb am Sonntag, 7. Juli 2024 um 11:41:01 UTC+2:
>>>
>>>      Hi Michael,
>>>
>>>      I wanted to work on this, but I cannot apply clean, git am reports a
>>>      malformed patch. Can you check and repost ? Thanks.
>>>
>>>      Best regards,
>>>      Stefano
>>>
>>>      On 15.06.24 21:11, Michael Glembotzki wrote:
>>>       > mongoose rev-id: 6bb40e6ec96f95bfd36a816b430ea2726fac9d05
>>>       >
>>>       > Signed-off-by: Michael Glembotzki <Michael.G...@iris-sensing.com>
>>>       > ---
>>>       > mongoose/mongoose.c | 21614
>>>      +++++++++++++++++++++++++++++++-----------
>>>       > mongoose/mongoose.h | 2003 +++-
>>>       > 2 files changed, 17594 insertions(+), 6023 deletions(-)
>>>       >
>>>       > diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
>>>       > index 83d8fe18..3fc0e14a 100644
>>>       > --- a/mongoose/mongoose.c
>>>       > +++ b/mongoose/mongoose.c
>>>       > @@ -1,5 +1,5 @@
>>>       > // Copyright (c) 2004-2013 Sergey Lyubka
>>>       > -// Copyright (c) 2013-2022 Cesanta Software Limited
>>>       > +// Copyright (c) 2013-2024 Cesanta Software Limited
>>>       > // All rights reserved
>>>       > //
>>>       > // This software is dual-licensed: you can redistribute it and/or
>>>      modify
>>>       > @@ -15,7 +15,7 @@
>>>       > // Alternatively, you can license this software under a commercial
>>>       > // license, as set out in https://www.mongoose.ws/licensing/
>>>      <https://www.mongoose.ws/licensing/>
>>>       > //
>>>       > -// SPDX-License-Identifier: GPL-2.0-only
>>>       > +// SPDX-License-Identifier: GPL-2.0-only or commercial
>>>       >
>>>       > #include "mongoose.h"
>>>       >
>>>       > @@ -24,8 +24,7 @@
>>>       > #endif
>>>       >
>>>       >
>>>       > -
>>>       > -static int mg_b64idx(int c) {
>>>       > +static int mg_base64_encode_single(int c) {
>>>       > if (c < 26) {
>>>       > return c + 'A';
>>>       > } else if (c < 52) {
>>>       > @@ -37,7 +36,7 @@ static int mg_b64idx(int c) {
>>>       > }
>>>       > }
>>>       >
>>>       > -static int mg_b64rev(int c) {
>>>       > +static int mg_base64_decode_single(int c) {
>>>       > if (c >= 'A' && c <= 'Z') {
>>>       > return c - 'A';
>>>       > } else if (c >= 'a' && c <= 'z') {
>>>       > @@ -55,24 +54,24 @@ static int mg_b64rev(int c) {
>>>       > }
>>>       > }
>>>       >
>>>       > -int mg_base64_update(unsigned char ch, char *to, int n) {
>>>       > - int rem = (n & 3) % 3;
>>>       > +size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
>>>       > + unsigned long rem = (n & 3) % 3;
>>>       > if (rem == 0) {
>>>       > - to[n] = (char) mg_b64idx(ch >> 2);
>>>       > + to[n] = (char) mg_base64_encode_single(ch >> 2);
>>>       > to[++n] = (char) ((ch & 3) << 4);
>>>       > } else if (rem == 1) {
>>>       > - to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
>>>       > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
>>>       > to[++n] = (char) ((ch & 15) << 2);
>>>       > } else {
>>>       > - to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
>>>       > - to[++n] = (char) mg_b64idx(ch & 63);
>>>       > + to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
>>>       > + to[++n] = (char) mg_base64_encode_single(ch & 63);
>>>       > n++;
>>>       > }
>>>       > return n;
>>>       > }
>>>       >
>>>       > -int mg_base64_final(char *to, int n) {
>>>       > - int saved = n;
>>>       > +size_t mg_base64_final(char *to, size_t n) {
>>>       > + size_t saved = n;
>>>       > // printf("---[%.*s]\n", n, to);
>>>       > if (n & 3) n = mg_base64_update(0, to, n);
>>>       > if ((saved & 3) == 2) n--;
>>>       > @@ -82,20 +81,27 @@ int mg_base64_final(char *to, int n) {
>>>       > return n;
>>>       > }
>>>       >
>>>       > -int mg_base64_encode(const unsigned char *p, int n, char *to) {
>>>       > - int i, len = 0;
>>>       > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
>>>      *to, size_t dl) {
>>>       > + size_t i, len = 0;
>>>       > + if (dl > 0) to[0] = '\0';
>>>       > + if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
>>>       > for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
>>>       > len = mg_base64_final(to, len);
>>>       > return len;
>>>       > }
>>>       >
>>>       > -int mg_base64_decode(const char *src, int n, char *dst) {
>>>       > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
>>>      size_t dl) {
>>>       > const char *end = src == NULL ? NULL : src + n; // Cannot add to
>>>      NULL
>>>       > - int len = 0;
>>>       > + size_t len = 0;
>>>       > + if (dl < n / 4 * 3 + 1) goto fail;
>>>       > while (src != NULL && src + 3 < end) {
>>>       > - int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c =
>>>      mg_b64rev(src[2]),
>>>       > - d = mg_b64rev(src[3]);
>>>       > - if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0)
>>>      return 0;
>>>       > + int a = mg_base64_decode_single(src[0]),
>>>       > + b = mg_base64_decode_single(src[1]),
>>>       > + c = mg_base64_decode_single(src[2]),
>>>       > + d = mg_base64_decode_single(src[3]);
>>>       > + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
>>>       > + goto fail;
>>>       > + }
>>>       > dst[len++] = (char) ((a << 2) | (b >> 4));
>>>       > if (src[2] != '=') {
>>>       > dst[len++] = (char) ((b << 4) | (c >> 2));
>>>       > @@ -105,1942 +111,2915 @@ int mg_base64_decode(const char *src,
>>>      int n, char *dst) {
>>>       > }
>>>       > dst[len] = '\0';
>>>       > return len;
>>>       > +fail:
>>>       > + if (dl > 0) dst[0] = '\0';
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/dns.c"
>>>       > +#line 1 "src/device_ch32v307.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       > +#if MG_DEVICE == MG_DEVICE_CH32V307
>>>       > +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
>>>      <https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html>
>>>       >
>>>       > +#define FLASH_BASE 0x40022000
>>>       > +#define FLASH_ACTLR (FLASH_BASE + 0)
>>>       > +#define FLASH_KEYR (FLASH_BASE + 4)
>>>       > +#define FLASH_OBKEYR (FLASH_BASE + 8)
>>>       > +#define FLASH_STATR (FLASH_BASE + 12)
>>>       > +#define FLASH_CTLR (FLASH_BASE + 16)
>>>       > +#define FLASH_ADDR (FLASH_BASE + 20)
>>>       > +#define FLASH_OBR (FLASH_BASE + 28)
>>>       > +#define FLASH_WPR (FLASH_BASE + 32)
>>>       >
>>>       > -
>>>       > -
>>>       > -struct dns_data {
>>>       > - struct dns_data *next;
>>>       > - struct mg_connection *c;
>>>       > - uint64_t expire;
>>>       > - uint16_t txnid;
>>>       > -};
>>>       > -
>>>       > -static void mg_sendnsreq(struct mg_connection *, struct mg_str
>>>      *, int,
>>>       > - struct mg_dns *, bool);
>>>       > -
>>>       > -static void mg_dns_free(struct mg_connection *c, struct dns_data
>>>      *d) {
>>>       > - LIST_DELETE(struct dns_data,
>>>       > - (struct dns_data **) &c->mgr->active_dns_requests, d);
>>>       > - free(d);
>>>       > +void *mg_flash_start(void) {
>>>       > + return (void *) 0x08000000;
>>>       > }
>>>       > -
>>>       > -void mg_resolve_cancel(struct mg_connection *c) {
>>>       > - struct dns_data *tmp, *d = (struct dns_data *)
>>>      c->mgr->active_dns_requests;
>>>       > - for (; d != NULL; d = tmp) {
>>>       > - tmp = d->next;
>>>       > - if (d->c == c) mg_dns_free(c, d);
>>>       > +size_t mg_flash_size(void) {
>>>       > + return 480 * 1024; // First 320k is 0-wait
>>>       > +}
>>>       > +size_t mg_flash_sector_size(void) {
>>>       > + return 4096;
>>>       > +}
>>>       > +size_t mg_flash_write_align(void) {
>>>       > + return 4;
>>>       > +}
>>>       > +int mg_flash_bank(void) {
>>>       > + return 0;
>>>       > +}
>>>       > +void mg_device_reset(void) {
>>>       > + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; //
>>>      NVIC_SystemReset()
>>>       > +}
>>>       > +static void flash_unlock(void) {
>>>       > + static bool unlocked;
>>>       > + if (unlocked == false) {
>>>       > + MG_REG(FLASH_KEYR) = 0x45670123;
>>>       > + MG_REG(FLASH_KEYR) = 0xcdef89ab;
>>>       > + unlocked = true;
>>>       > }
>>>       > }
>>>       > +static void flash_wait(void) {
>>>       > + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
>>>       > +}
>>>       >
>>>       > -static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
>>>      len, size_t ofs,
>>>       > - char *to, size_t tolen, size_t j,
>>>       > - int depth) {
>>>       > - size_t i = 0;
>>>       > - if (tolen > 0 && depth == 0) to[0] = '\0';
>>>       > - if (depth > 5) return 0;
>>>       > - // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
>>>      + 1]));
>>>       > - while (ofs + i + 1 < len) {
>>>       > - size_t n = s[ofs + i];
>>>       > - if (n == 0) {
>>>       > - i++;
>>>       > - break;
>>>       > - }
>>>       > - if (n & 0xc0) {
>>>       > - size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
>>>      len
>>>       > - // MG_INFO(("PTR %lx", (unsigned long) ptr));
>>>       > - if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
>>>       > - mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
>>>      == 0)
>>>       > - return 0;
>>>       > - i += 2;
>>>       > - break;
>>>       > - }
>>>       > - if (ofs + i + n + 1 >= len) return 0;
>>>       > - if (j > 0) {
>>>       > - if (j < tolen) to[j] = '.';
>>>       > - j++;
>>>       > - }
>>>       > - if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
>>>       > - j += n;
>>>       > - i += n + 1;
>>>       > - if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
>>>       > - // MG_INFO(("--> [%s]", to));
>>>       > - }
>>>       > - if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
>>>      is nul-term
>>>       > - return i;
>>>       > +bool mg_flash_erase(void *addr) {
>>>       > + //MG_INFO(("%p", addr));
>>>       > + flash_unlock();
>>>       > + flash_wait();
>>>       > + MG_REG(FLASH_ADDR) = (uint32_t) addr;
>>>       > + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT;
>>>       > + flash_wait();
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
>>>      size_t ofs,
>>>       > - char *dst, size_t dstlen) {
>>>       > - return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
>>>       > +static bool is_page_boundary(const void *addr) {
>>>       > + uint32_t val = (uint32_t) addr;
>>>       > + return (val & (mg_flash_sector_size() - 1)) == 0;
>>>       > }
>>>       >
>>>       > -size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
>>>       > - bool is_question, struct mg_dns_rr *rr) {
>>>       > - const uint8_t *s = buf + ofs, *e = &buf[len];
>>>       > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>>>       > + //MG_INFO(("%p %p %lu", addr, buf, len));
>>>       > + //mg_hexdump(buf, len);
>>>       > + flash_unlock();
>>>       > + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
>>>       > + uint16_t *dst = (uint16_t *) addr;
>>>       > + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG
>>>       > + //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
>>>       > + while (src < end) {
>>>       > + if (is_page_boundary(dst)) mg_flash_erase(dst);
>>>       > + *dst++ = *src++;
>>>       > + flash_wait();
>>>       > + }
>>>       > + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG
>>>       > + return true;
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > - memset(rr, 0, sizeof(*rr));
>>>       > - if (len < sizeof(struct mg_dns_header)) return 0; // Too small
>>>       > - if (len > 512) return 0; // Too large, we don't expect that
>>>       > - if (s >= e) return 0; // Overflow
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/device_dummy.c"
>>>       > +#endif
>>>       >
>>>       > - if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
>>>      NULL, 0)) == 0)
>>>       > - return 0;
>>>       > - s += rr->nlen + 4;
>>>       > - if (s > e) return 0;
>>>       > - rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
>>>       > - rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>>>       > - if (is_question) return (size_t) (rr->nlen + 4);
>>>       >
>>>       > - s += 6;
>>>       > - if (s > e) return 0;
>>>       > - rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>>>       > - if (s + rr->alen > e) return 0;
>>>       > - return (size_t) (rr->nlen + rr->alen + 10);
>>>       > +#if MG_DEVICE == MG_DEVICE_NONE
>>>       > +void *mg_flash_start(void) {
>>>       > + return NULL;
>>>       > +}
>>>       > +size_t mg_flash_size(void) {
>>>       > + return 0;
>>>       > +}
>>>       > +size_t mg_flash_sector_size(void) {
>>>       > + return 0;
>>>       > +}
>>>       > +size_t mg_flash_write_align(void) {
>>>       > + return 0;
>>>       > +}
>>>       > +int mg_flash_bank(void) {
>>>       > + return 0;
>>>       > +}
>>>       > +bool mg_flash_erase(void *location) {
>>>       > + (void) location;
>>>       > + return false;
>>>       > +}
>>>       > +bool mg_flash_swap_bank(void) {
>>>       > + return true;
>>>       > +}
>>>       > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>>>       > + (void) addr, (void) buf, (void) len;
>>>       > + return false;
>>>       > +}
>>>       > +void mg_device_reset(void) {
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -bool mg_dns_parse(const uint8_t *buf, size_t len, struct
>>>      mg_dns_message *dm) {
>>>       > - const struct mg_dns_header *h = (struct mg_dns_header *) buf;
>>>       > - struct mg_dns_rr rr;
>>>       > - size_t i, n, ofs = sizeof(*h);
>>>       > - memset(dm, 0, sizeof(*dm));
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/device_flash.c"
>>>       > +#endif
>>>       >
>>>       > - if (len < sizeof(*h)) return 0; // Too small, headers dont fit
>>>       > - if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
>>>       > - if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity
>>>       > - dm->txnid = mg_ntohs(h->txnid);
>>>       >
>>>       > - for (i = 0; i < mg_ntohs(h->num_questions); i++) {
>>>       > - if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
>>>      return false;
>>>       > - // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
>>>       > - ofs += n;
>>>       > +#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE ==
>>>      MG_DEVICE_STM32H5 || \
>>>       > + MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
>>>       > +// Flash can be written only if it is erased. Erased flash is
>>>      0xff (all bits 1)
>>>       > +// Writes must be mg_flash_write_align() - aligned. Thus if we
>>>      want to save an
>>>       > +// object, we pad it at the end for alignment.
>>>       > +//
>>>       > +// Objects in the flash sector are stored sequentially:
>>>       > +// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size
>>>      | ......
>>>       > +//
>>>       > +// In order to get to the next object, read its size, then align
>>>      up.
>>>       > +
>>>       > +// Traverse the list of saved objects
>>>       > +size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t
>>>      *size) {
>>>       > + size_t aligned_size = 0, align = mg_flash_write_align(), left =
>>>      end - p;
>>>       > + uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
>>>       > + if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
>>>       > + if (size) *size = (size_t) p32[0];
>>>       > + if (key) *key = p32[1];
>>>       > + aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
>>>       > + if (left < aligned_size) aligned_size = 0; // Out of bounds, fail
>>>       > }
>>>       > - for (i = 0; i < mg_ntohs(h->num_answers); i++) {
>>>       > - if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
>>>      return false;
>>>       > - // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
>>>      rr.aclass,
>>>       > - // dm->name));
>>>       > - mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
>>>       > - ofs += n;
>>>       > -
>>>       > - if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
>>>       > - dm->addr.is_ip6 = false;
>>>       > - memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
>>>       > - dm->resolved = true;
>>>       > - break; // Return success
>>>       > - } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
>>>       > - dm->addr.is_ip6 = true;
>>>       > - memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
>>>       > - dm->resolved = true;
>>>       > - break; // Return success
>>>       > + return aligned_size;
>>>       > +}
>>>       > +
>>>       > +// Return the last sector of Bank 2
>>>       > +static char *flash_last_sector(void) {
>>>       > + size_t ss = mg_flash_sector_size(), size = mg_flash_size();
>>>       > + char *base = (char *) mg_flash_start(), *last = base + size - ss;
>>>       > + if (mg_flash_bank() == 2) last -= size / 2;
>>>       > + return last;
>>>       > +}
>>>       > +
>>>       > +// Find a saved object with a given key
>>>       > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>>>      len) {
>>>       > + char *base = (char *) mg_flash_start(), *s = (char *) sector,
>>>      *res = NULL;
>>>       > + size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
>>>       > + bool ok = false;
>>>       > + if (s == NULL) s = flash_last_sector();
>>>       > + if (s < base || s >= base + mg_flash_size()) {
>>>       > + MG_ERROR(("%p is outsize of flash", sector));
>>>       > + } else if (((s - base) % ss) != 0) {
>>>       > + MG_ERROR(("%p is not a sector boundary", sector));
>>>       > + } else {
>>>       > + uint32_t k, scanned = 0;
>>>       > + while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
>>>       > + // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k,
>>>      key));
>>>       > + // mg_hexdump(s + ofs, n);
>>>       > + if (k == key && sz == len) {
>>>       > + res = s + ofs + sizeof(uint32_t) * 2;
>>>       > + memcpy(buf, res, len); // Copy object
>>>       > + ok = true; // Keep scanning for the newer versions of it
>>>       > + }
>>>       > + ofs += n, scanned++;
>>>       > }
>>>       > + MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key,
>>>      res));
>>>       > }
>>>       > - return true;
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
>>>       > - void *fn_data) {
>>>       > - struct dns_data *d, *tmp;
>>>       > - if (ev == MG_EV_POLL) {
>>>       > - uint64_t now = *(uint64_t *) ev_data;
>>>       > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>>>      NULL;
>>>       > - d = tmp) {
>>>       > - tmp = d->next;
>>>       > - // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
>>>       > - if (now > d->expire) mg_error(d->c, "DNS timeout");
>>>       > +// For all saved objects in the sector, delete old versions of
>>>      objects
>>>       > +static void mg_flash_sector_cleanup(char *sector) {
>>>       > + // Buffer all saved objects into an IO buffer (backed by RAM)
>>>       > + // erase sector, and re-save them.
>>>       > + struct mg_iobuf io = {0, 0, 0, 2048};
>>>       > + size_t ss = mg_flash_sector_size();
>>>       > + size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
>>>       > + uint32_t key;
>>>       > + // Traverse all objects
>>>       > + MG_DEBUG(("Cleaning up sector %p", sector));
>>>       > + while ((n = mg_flash_next(sector + ofs, sector + ss, &key,
>>>      &size)) > 0) {
>>>       > + // Delete an old copy of this object in the cache
>>>       > + for (size_t o = 0; o < io.len; o += size2 + hs) {
>>>       > + uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
>>>       > + size2 = *(uint32_t *) (io.buf + o);
>>>       > + if (k == key) {
>>>       > + mg_iobuf_del(&io, o, size2 + hs);
>>>       > + break;
>>>       > + }
>>>       > }
>>>       > - } else if (ev == MG_EV_READ) {
>>>       > - struct mg_dns_message dm;
>>>       > - int resolved = 0;
>>>       > - if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
>>>       > - MG_ERROR(("Unexpected DNS response:"));
>>>       > - mg_hexdump(c->recv.buf, c->recv.len);
>>>       > - } else {
>>>       > - // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
>>>       > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>>>      NULL;
>>>       > - d = tmp) {
>>>       > - tmp = d->next;
>>>       > - // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
>>>       > - if (dm.txnid != d->txnid) continue;
>>>       > - if (d->c->is_resolving) {
>>>       > - if (dm.resolved) {
>>>       > - dm.addr.port = d->c->rem.port; // Save port
>>>       > - d->c->rem = dm.addr; // Copy resolved address
>>>       > - MG_DEBUG(
>>>       > - ("%lu %s is %I", d->c->id, dm.name <http://dm.name>,
>>>      d->c->rem.is_ip6 ? 16 : 4,
>>>       > - d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
>>>       > - mg_connect_resolved(d->c);
>>>       > -#if MG_ENABLE_IPV6
>>>       > - } else if (dm.addr.is_ip6 == false && dm.name
>>>      <http://dm.name>[0] != '\0' &&
>>>       > - c->mgr->use_dns6 == false) {
>>>       > - struct mg_str x = mg_str(dm.name <http://dm.name>);
>>>       > - mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
>>>       > -#endif
>>>       > - } else {
>>>       > - mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
>>>       > + // And add the new copy
>>>       > + mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
>>>       > + ofs += n;
>>>       > + }
>>>       > + // All objects are cached in RAM now
>>>       > + if (mg_flash_erase(sector)) { // Erase sector. If successful,
>>>       > + for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse
>>>      cached objects
>>>       > + size = *(uint32_t *) (io.buf + ofs);
>>>       > + key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
>>>       > + mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to
>>>      flash
>>>       > + }
>>>       > + }
>>>       > + mg_iobuf_free(&io);
>>>       > +}
>>>       > +
>>>       > +// Save an object with a given key - append to the end of an
>>>      object list
>>>       > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>>>      size_t len) {
>>>       > + char *base = (char *) mg_flash_start(), *s = (char *) sector;
>>>       > + size_t ss = mg_flash_sector_size(), ofs = 0, n;
>>>       > + bool ok = false;
>>>       > + if (s == NULL) s = flash_last_sector();
>>>       > + if (s < base || s >= base + mg_flash_size()) {
>>>       > + MG_ERROR(("%p is outsize of flash", sector));
>>>       > + } else if (((s - base) % ss) != 0) {
>>>       > + MG_ERROR(("%p is not a sector boundary", sector));
>>>       > + } else {
>>>       > + char ab[mg_flash_write_align()]; // Aligned write block
>>>       > + uint32_t hdr[2] = {(uint32_t) len, key};
>>>       > + size_t needed = sizeof(hdr) + len;
>>>       > + size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
>>>       > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
>>>      ofs += n;
>>>       > +
>>>       > + // If there is not enough space left, cleanup sector and
>>>      re-eval ofs
>>>       > + if (ofs + needed_aligned >= ss) {
>>>       > + mg_flash_sector_cleanup(s);
>>>       > + ofs = 0;
>>>       > + while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0)
>>>      ofs += n;
>>>       > + }
>>>       > +
>>>       > + if (ofs + needed_aligned <= ss) {
>>>       > + // Enough space to save this object
>>>       > + if (sizeof(ab) < sizeof(hdr)) {
>>>       > + // Flash write granularity is 32 bit or less, write with no
>>>      buffering
>>>       > + ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
>>>       > + if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
>>>       > + } else {
>>>       > + // Flash granularity is sizeof(hdr) or more. We need to save in
>>>       > + // 3 chunks: initial block, bulk, rest. This is because we have
>>>       > + // two memory chunks to write: hdr and buf, on aligned boundaries.
>>>       > + n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
>>>       > + if (n > len) n = len; // is
>>>       > + memset(ab, 0xff, sizeof(ab)); // initialized to all-one
>>>       > + memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
>>>       > + memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
>>>       > + MG_INFO(("saving initial block of %lu", sizeof(ab)));
>>>       > + ok = mg_flash_write(s + ofs, ab, sizeof(ab));
>>>       > + if (ok && len > n) {
>>>       > + size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
>>>       > + if (n2 > 0) {
>>>       > + MG_INFO(("saving bulk, %lu", n2));
>>>       > + ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
>>>       > + }
>>>       > + if (ok && len > n) {
>>>       > + size_t n3 = len - n - n2;
>>>       > + if (n3 > sizeof(ab)) n3 = sizeof(ab);
>>>       > + memset(ab, 0xff, sizeof(ab));
>>>       > + memcpy(ab, (char *) buf + n + n2, n3);
>>>       > + MG_INFO(("saving rest, %lu", n3));
>>>       > + ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
>>>       > }
>>>       > - } else {
>>>       > - MG_ERROR(("%lu already resolved", d->c->id));
>>>       > }
>>>       > - mg_dns_free(c, d);
>>>       > - resolved = 1;
>>>       > }
>>>       > - }
>>>       > - if (!resolved) MG_ERROR(("stray DNS reply"));
>>>       > - c->recv.len = 0;
>>>       > - } else if (ev == MG_EV_CLOSE) {
>>>       > - for (d = (struct dns_data *) c->mgr->active_dns_requests; d !=
>>>      NULL;
>>>       > - d = tmp) {
>>>       > - tmp = d->next;
>>>       > - mg_error(d->c, "DNS error");
>>>       > - mg_dns_free(c, d);
>>>       > + MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len,
>>>      needed_aligned,
>>>       > + s + ofs, key, ok));
>>>       > + MG_DEBUG(("Sector space left: %lu bytes", ss - ofs -
>>>      needed_aligned));
>>>       > + } else {
>>>       > + MG_ERROR(("Sector is full"));
>>>       > }
>>>       > }
>>>       > - (void) fn_data;
>>>       > + return ok;
>>>       > +}
>>>       > +#else
>>>       > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>>>      size_t len) {
>>>       > + (void) sector, (void) key, (void) buf, (void) len;
>>>       > + return false;
>>>       > +}
>>>       > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>>>      len) {
>>>       > + (void) sector, (void) key, (void) buf, (void) len;
>>>       > + return false;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -static bool mg_dns_send(struct mg_connection *c, const struct
>>>      mg_str *name,
>>>       > - uint16_t txnid, bool ipv6) {
>>>       > - struct {
>>>       > - struct mg_dns_header header;
>>>       > - uint8_t data[256];
>>>       > - } pkt;
>>>       > - size_t i, n;
>>>       > - memset(&pkt, 0, sizeof(pkt));
>>>       > - pkt.header.txnid = mg_htons(txnid);
>>>       > - pkt.header.flags = mg_htons(0x100);
>>>       > - pkt.header.num_questions = mg_htons(1);
>>>       > - for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
>>>       > - if (name->ptr[i] == '.' || i >= name->len) {
>>>       > - pkt.data[n] = (uint8_t) (i - n);
>>>       > - memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
>>>       > - n = i + 1;
>>>       > - }
>>>       > - if (i >= name->len) break;
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/device_imxrt.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
>>>       > +
>>>       > +struct mg_flexspi_lut_seq {
>>>       > + uint8_t seqNum;
>>>       > + uint8_t seqId;
>>>       > + uint16_t reserved;
>>>       > +};
>>>       > +
>>>       > +struct mg_flexspi_mem_config {
>>>       > + uint32_t tag;
>>>       > + uint32_t version;
>>>       > + uint32_t reserved0;
>>>       > + uint8_t readSampleClkSrc;
>>>       > + uint8_t csHoldTime;
>>>       > + uint8_t csSetupTime;
>>>       > + uint8_t columnAddressWidth;
>>>       > + uint8_t deviceModeCfgEnable;
>>>       > + uint8_t deviceModeType;
>>>       > + uint16_t waitTimeCfgCommands;
>>>       > + struct mg_flexspi_lut_seq deviceModeSeq;
>>>       > + uint32_t deviceModeArg;
>>>       > + uint8_t configCmdEnable;
>>>       > + uint8_t configModeType[3];
>>>       > + struct mg_flexspi_lut_seq configCmdSeqs[3];
>>>       > + uint32_t reserved1;
>>>       > + uint32_t configCmdArgs[3];
>>>       > + uint32_t reserved2;
>>>       > + uint32_t controllerMiscOption;
>>>       > + uint8_t deviceType;
>>>       > + uint8_t sflashPadType;
>>>       > + uint8_t serialClkFreq;
>>>       > + uint8_t lutCustomSeqEnable;
>>>       > + uint32_t reserved3[2];
>>>       > + uint32_t sflashA1Size;
>>>       > + uint32_t sflashA2Size;
>>>       > + uint32_t sflashB1Size;
>>>       > + uint32_t sflashB2Size;
>>>       > + uint32_t csPadSettingOverride;
>>>       > + uint32_t sclkPadSettingOverride;
>>>       > + uint32_t dataPadSettingOverride;
>>>       > + uint32_t dqsPadSettingOverride;
>>>       > + uint32_t timeoutInMs;
>>>       > + uint32_t commandInterval;
>>>       > + uint16_t dataValidTime[2];
>>>       > + uint16_t busyOffset;
>>>       > + uint16_t busyBitPolarity;
>>>       > + uint32_t lookupTable[64];
>>>       > + struct mg_flexspi_lut_seq lutCustomSeq[12];
>>>       > + uint32_t reserved4[4];
>>>       > +};
>>>       > +
>>>       > +struct mg_flexspi_nor_config {
>>>       > + struct mg_flexspi_mem_config memConfig;
>>>       > + uint32_t pageSize;
>>>       > + uint32_t sectorSize;
>>>       > + uint8_t ipcmdSerialClkFreq;
>>>       > + uint8_t isUniformBlockSize;
>>>       > + uint8_t reserved0[2];
>>>       > + uint8_t serialNorType;
>>>       > + uint8_t needExitNoCmdMode;
>>>       > + uint8_t halfClkForNonReadCmd;
>>>       > + uint8_t needRestoreNoCmdMode;
>>>       > + uint32_t blockSize;
>>>       > + uint32_t reserve2[11];
>>>       > +};
>>>       > +
>>>       > +/* FLEXSPI memory config block related defintions */
>>>       > +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB"
>>>      Big Endian
>>>       > +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0
>>>       > +
>>>       > +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \
>>>       > + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0)
>>>      | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
>>>       > + MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) |
>>>      MG_FLEXSPI_LUT_OPCODE1(cmd1))
>>>       > +
>>>       > +#define MG_CMD_SDR 0x01
>>>       > +#define MG_CMD_DDR 0x21
>>>       > +#define MG_DUMMY_SDR 0x0C
>>>       > +#define MG_DUMMY_DDR 0x2C
>>>       > +#define MG_RADDR_SDR 0x02
>>>       > +#define MG_RADDR_DDR 0x22
>>>       > +#define MG_READ_SDR 0x09
>>>       > +#define MG_READ_DDR 0x29
>>>       > +#define MG_WRITE_SDR 0x08
>>>       > +#define MG_WRITE_DDR 0x28
>>>       > +#define MG_STOP 0
>>>       > +
>>>       > +#define MG_FLEXSPI_1PAD 0
>>>       > +#define MG_FLEXSPI_2PAD 1
>>>       > +#define MG_FLEXSPI_4PAD 2
>>>       > +#define MG_FLEXSPI_8PAD 3
>>>       > +
>>>       > +#define MG_FLEXSPI_QSPI_LUT \
>>>       > + { \
>>>       > + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB,
>>>      MG_RADDR_SDR, MG_FLEXSPI_4PAD, \
>>>       > + 0x18), \
>>>       > + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06,
>>>      MG_READ_SDR, MG_FLEXSPI_4PAD, \
>>>       > + 0x04), \
>>>       > + [4 * 1 + 0] = \
>>>       > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05,
>>>      MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \
>>>       > + [4 * 3 + 0] = \
>>>       > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP,
>>>      MG_FLEXSPI_1PAD, 0x0), \
>>>       > + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>>>      0x20, MG_RADDR_SDR, \
>>>       > + MG_FLEXSPI_1PAD, 0x18), \
>>>       > + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>>>      0xD8, MG_RADDR_SDR, \
>>>       > + MG_FLEXSPI_1PAD, 0x18), \
>>>       > + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD,
>>>      0x02, MG_RADDR_SDR, \
>>>       > + MG_FLEXSPI_1PAD, 0x18), \
>>>       > + [4 * 9 + 1] = \
>>>       > + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04,
>>>      MG_STOP, MG_FLEXSPI_1PAD, 0x0), \
>>>       > + [4 * 11 + 0] = \
>>>       > + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP,
>>>      MG_FLEXSPI_1PAD, 0x0), \
>>>       > }
>>>       > - memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
>>>       > - n += 5;
>>>       > - if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
>>>       > - // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
>>>      query
>>>       > - // n += 6;
>>>       > - return mg_send(c, &pkt, sizeof(pkt.header) + n);
>>>       > +
>>>       > +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t)
>>>      (x)))) & 0xFFU)
>>>       > +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t)
>>>      (x)) << 8U)) & 0x300U)
>>>       > +#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x))
>>>      << 10U)) & 0xFC00U)
>>>       > +#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t)
>>>      (x)) << 16U)) & 0xFF0000U)
>>>       > +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t)
>>>      (x)) << 24U)) & 0x3000000U)
>>>       > +#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x))
>>>      << 26U)) & 0xFC000000U)
>>>       > +
>>>       > +#define FLEXSPI_NOR_INSTANCE 0
>>>       > +
>>>       > +#if MG_DEVICE == MG_DEVICE_RT1020
>>>       > +struct mg_flexspi_nor_driver_interface {
>>>       > + uint32_t version;
>>>       > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config);
>>>       > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config, uint32_t dst_addr,
>>>       > + const uint32_t *src);
>>>       > + uint32_t reserved;
>>>       > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config, uint32_t start,
>>>       > + uint32_t lengthInBytes);
>>>       > + uint32_t reserved2;
>>>       > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
>>>      uint32_t *lutBase,
>>>       > + uint32_t seqNumber);
>>>       > + int (*xfer)(uint32_t instance, char *xfer);
>>>       > + void (*clear_cache)(uint32_t instance);
>>>       > +};
>>>       > +#elif MG_DEVICE == MG_DEVICE_RT1060
>>>       > +struct mg_flexspi_nor_driver_interface {
>>>       > + uint32_t version;
>>>       > + int (*init)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config);
>>>       > + int (*program)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config, uint32_t dst_addr,
>>>       > + const uint32_t *src);
>>>       > + int (*erase_all)(uint32_t instance, struct
>>>      mg_flexspi_nor_config *config);
>>>       > + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config, uint32_t start,
>>>       > + uint32_t lengthInBytes);
>>>       > + int (*read)(uint32_t instance, struct mg_flexspi_nor_config
>>>      *config, uint32_t *dst, uint32_t addr,
>>>       > + uint32_t lengthInBytes);
>>>       > + void (*clear_cache)(uint32_t instance);
>>>       > + int (*xfer)(uint32_t instance, char *xfer);
>>>       > + int (*update_lut)(uint32_t instance, uint32_t seqIndex, const
>>>      uint32_t *lutBase,
>>>       > + uint32_t seqNumber);
>>>       > + int (*get_config)(uint32_t instance, struct
>>>      mg_flexspi_nor_config *config, uint32_t *option);
>>>       > +};
>>>       > +#endif
>>>       > +
>>>       > +#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
>>>       > + (*(uint32_t*)0x0020001c + 16)))
>>>       > +
>>>       > +static bool s_flash_irq_disabled;
>>>       > +
>>>       > +MG_IRAM void *mg_flash_start(void) {
>>>       > + return (void *) 0x60000000;
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_size(void) {
>>>       > + return 8 * 1024 * 1024;
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_sector_size(void) {
>>>       > + return 4 * 1024; // 4k
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_write_align(void) {
>>>       > + return 256;
>>>       > +}
>>>       > +MG_IRAM int mg_flash_bank(void) {
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -static void mg_sendnsreq(struct mg_connection *c, struct mg_str
>>>      *name, int ms,
>>>       > - struct mg_dns *dnsc, bool ipv6) {
>>>       > - struct dns_data *d = NULL;
>>>       > - if (dnsc->url == NULL) {
>>>       > - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
>>>       > - } else if (dnsc->c == NULL) {
>>>       > - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
>>>       > - if (dnsc->c != NULL) {
>>>       > - dnsc->c->pfn = dns_cb;
>>>       > - // dnsc->c->is_hexdumping = 1;
>>>       > - }
>>>       > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
>>>       > + char *base = (char *) mg_flash_start(), *end = base +
>>>      mg_flash_size();
>>>       > + volatile char *p = (char *) dst;
>>>       > + return p >= base && p < end && ((p - base) %
>>>      mg_flash_sector_size()) == 0;
>>>       > +}
>>>       > +
>>>       > +// Note: the get_config function below works both for RT1020 and
>>>      1060
>>>       > +#if MG_DEVICE == MG_DEVICE_RT1020
>>>       > +MG_IRAM static int flexspi_nor_get_config(struct
>>>      mg_flexspi_nor_config *config) {
>>>       > + struct mg_flexspi_nor_config default_config = {
>>>       > + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
>>>       > + .version = MG_FLEXSPI_CFG_BLK_VERSION,
>>>       > + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad
>>>       > + .csHoldTime = 3,
>>>       > + .csSetupTime = 3,
>>>       > + .controllerMiscOption = MG_BIT(4),
>>>       > + .deviceType = 1, // serial NOR
>>>       > + .sflashPadType = 4,
>>>       > + .serialClkFreq = 7, // 133MHz
>>>       > + .sflashA1Size = 8 * 1024 * 1024,
>>>       > + .lookupTable = MG_FLEXSPI_QSPI_LUT},
>>>       > + .pageSize = 256,
>>>       > + .sectorSize = 4 * 1024,
>>>       > + .ipcmdSerialClkFreq = 1,
>>>       > + .blockSize = 64 * 1024,
>>>       > + .isUniformBlockSize = false};
>>>       > +
>>>       > + *config = default_config;
>>>       > + return 0;
>>>       > +}
>>>       > +#else
>>>       > +MG_IRAM static int flexspi_nor_get_config(struct
>>>      mg_flexspi_nor_config *config) {
>>>       > + uint32_t options[] = {0xc0000000, 0x00};
>>>       > +
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + uint32_t status =
>>>       > + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
>>>       > + if (!s_flash_irq_disabled) {
>>>       > + MG_ARM_ENABLE_IRQ();
>>>       > }
>>>       > - if (dnsc->c == NULL) {
>>>       > - mg_error(c, "resolver");
>>>       > - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
>>>      NULL) {
>>>       > - mg_error(c, "resolve OOM");
>>>       > - } else {
>>>       > - struct dns_data *reqs = (struct dns_data *)
>>>      c->mgr->active_dns_requests;
>>>       > - d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
>>>       > - d->next = (struct dns_data *) c->mgr->active_dns_requests;
>>>       > - c->mgr->active_dns_requests = d;
>>>       > - d->expire = mg_millis() + (uint64_t) ms;
>>>       > - d->c = c;
>>>       > - c->is_resolving = 1;
>>>       > - MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
>>>      name->len,
>>>       > - name->ptr, &dnsc->url, d->txnid));
>>>       > - if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
>>>       > - mg_error(dnsc->c, "DNS send");
>>>       > - }
>>>       > + if (status) {
>>>       > + MG_ERROR(("Failed to extract flash configuration: status %u",
>>>      status));
>>>       > }
>>>       > + return status;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -void mg_resolve(struct mg_connection *c, const char *url) {
>>>       > - struct mg_str host = mg_url_host(url);
>>>       > - c->rem.port = mg_htons(mg_url_port(url));
>>>       > - if (mg_aton(host, &c->rem)) {
>>>       > - // host is an IP address, do not fire name resolution
>>>       > - mg_connect_resolved(c);
>>>       > - } else {
>>>       > - // host is not an IP, send DNS resolution request
>>>       > - struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
>>>      &c->mgr->dns4;
>>>       > - mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
>>>       > +MG_IRAM bool mg_flash_erase(void *addr) {
>>>       > + struct mg_flexspi_nor_config config;
>>>       > + if (flexspi_nor_get_config(&config) != 0) {
>>>       > + return false;
>>>       > + }
>>>       > + if (flash_page_start(addr) == false) {
>>>       > + MG_ERROR(("%p is not on a sector boundary", addr));
>>>       > + return false;
>>>       > + }
>>>       > +
>>>       > + void *dst = (void *)((char *) addr - (char *) mg_flash_start());
>>>       > +
>>>       > + // Note: Interrupts must be disabled before any call to the ROM
>>>      API on RT1020
>>>       > + // and 1060
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config,
>>>      (uint32_t) dst,
>>>       > + mg_flash_sector_size()) == 0);
>>>       > + if (!s_flash_irq_disabled) {
>>>       > + MG_ARM_ENABLE_IRQ(); // Reenable them after the call
>>>       > }
>>>       > + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok"
>>>      : "fail"));
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/event.c"
>>>       > -#endif
>>>       > +MG_IRAM bool mg_flash_swap_bank(void) {
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static inline void spin(volatile uint32_t count) {
>>>       > + while (count--) (void) 0;
>>>       > +}
>>>       > +
>>>       > +static inline void flash_wait(void) {
>>>       > + while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) &
>>>      MG_BIT(1)) == 0)
>>>       > + spin(1);
>>>       > +}
>>>       >
>>>       > +MG_IRAM static void *flash_code_location(void) {
>>>       > + return (void *) ((char *) mg_flash_start() + 0x2000);
>>>       > +}
>>>       >
>>>       > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
>>>      len) {
>>>       > + struct mg_flexspi_nor_config config;
>>>       > + if (flexspi_nor_get_config(&config) != 0) {
>>>       > + return false;
>>>       > + }
>>>       > + if ((len % mg_flash_write_align()) != 0) {
>>>       > + MG_ERROR(("%lu is not aligned to %lu", len,
>>>      mg_flash_write_align()));
>>>       > + return false;
>>>       > + }
>>>       >
>>>       > + if ((char *) addr < (char *) mg_flash_start()) {
>>>       > + MG_ERROR(("Invalid flash write address: %p", addr));
>>>       > + return false;
>>>       > + }
>>>       >
>>>       > + uint32_t *dst = (uint32_t *) addr;
>>>       > + uint32_t *src = (uint32_t *) buf;
>>>       > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>>>       > + bool ok = true;
>>>       > +
>>>       > + // Note: If we overwrite the flash irq section of the image, we
>>>      must also
>>>       > + // make sure interrupts are disabled and are not reenabled
>>>      until we write
>>>       > + // this sector with another irq table.
>>>       > + if ((char *) addr == (char *) flash_code_location()) {
>>>       > + s_flash_irq_disabled = true;
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + }
>>>       >
>>>       > -void mg_call(struct mg_connection *c, int ev, void *ev_data) {
>>>       > - // Run user-defined handler first, in order to give it an ability
>>>       > - // to intercept processing (e.g. clean input buffer) before the
>>>       > - // protocol handler kicks in
>>>       > - if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
>>>       > - if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
>>>       > + while (ok && src < end) {
>>>       > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
>>>       > + break;
>>>       > + }
>>>       > + uint32_t status;
>>>       > + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
>>>       > + if ((char *) buf >= (char *) mg_flash_start()) {
>>>       > + // If we copy from FLASH to FLASH, then we first need to copy
>>>      the source
>>>       > + // to RAM
>>>       > + size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
>>>       > + uint32_t tmp[tmp_buf_size];
>>>       > +
>>>       > + for (size_t i = 0; i < tmp_buf_size; i++) {
>>>       > + flash_wait();
>>>       > + tmp[i] = src[i];
>>>       > + }
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
>>>       > + (uint32_t) dst_ofs, tmp);
>>>       > + } else {
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
>>>       > + (uint32_t) dst_ofs, src);
>>>       > + }
>>>       > + if (!s_flash_irq_disabled) {
>>>       > + MG_ARM_ENABLE_IRQ();
>>>       > + }
>>>       > + src = (uint32_t *) ((char *) src + mg_flash_write_align());
>>>       > + dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
>>>       > + if (status != 0) {
>>>       > + ok = false;
>>>       > + }
>>>       > + }
>>>       > + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ?
>>>      "ok" : "fail"));
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -void mg_error(struct mg_connection *c, const char *fmt, ...) {
>>>       > - char buf[64];
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
>>>       > - va_end(ap);
>>>       > - MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
>>>       > - c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
>>>       > - mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it
>>>       > +MG_IRAM void mg_device_reset(void) {
>>>       > + MG_DEBUG(("Resetting device..."));
>>>       > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>>>       > }
>>>       >
>>>       > +#endif
>>>       > +
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/fmt.c"
>>>       > +#line 1 "src/device_stm32h5.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       > +#if MG_DEVICE == MG_DEVICE_STM32H5
>>>       >
>>>       > -static void mg_pfn_iobuf_private(char ch, void *param, bool
>>>      expand) {
>>>       > - struct mg_iobuf *io = (struct mg_iobuf *) param;
>>>       > - if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
>>>      io->len + 2);
>>>       > - if (io->len + 2 <= io->size) {
>>>       > - io->buf[io->len++] = (uint8_t) ch;
>>>       > - io->buf[io->len] = 0;
>>>       > - } else if (io->len < io->size) {
>>>       > - io->buf[io->len++] = 0; // Guarantee to 0-terminate
>>>       > - }
>>>       > -}
>>>       > +#define FLASH_BASE 0x40022000 // Base address of the flash
>>>      controller
>>>       > +#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11
>>>       > +#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
>>>       > +#define FLASH_OPTCR (FLASH_BASE + 0x1c)
>>>       > +#define FLASH_NSSR (FLASH_BASE + 0x20)
>>>       > +#define FLASH_NSCR (FLASH_BASE + 0x28)
>>>       > +#define FLASH_NSCCR (FLASH_BASE + 0x30)
>>>       > +#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
>>>       > +#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
>>>       >
>>>       > -static void mg_putchar_iobuf_static(char ch, void *param) {
>>>       > - mg_pfn_iobuf_private(ch, param, false);
>>>       > +void *mg_flash_start(void) {
>>>       > + return (void *) 0x08000000;
>>>       > }
>>>       > -
>>>       > -void mg_pfn_iobuf(char ch, void *param) {
>>>       > - mg_pfn_iobuf_private(ch, param, true);
>>>       > +size_t mg_flash_size(void) {
>>>       > + return 2 * 1024 * 1024; // 2Mb
>>>       > }
>>>       > -
>>>       > -size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>>>      va_list *ap) {
>>>       > - struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
>>>       > - size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
>>>       > - if (n < len) buf[n] = '\0';
>>>       > - return n;
>>>       > +size_t mg_flash_sector_size(void) {
>>>       > + return 8 * 1024; // 8k
>>>       > }
>>>       > -
>>>       > -size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
>>>       > - va_list ap;
>>>       > - size_t n;
>>>       > - va_start(ap, fmt);
>>>       > - n = mg_vsnprintf(buf, len, fmt, &ap);
>>>       > - va_end(ap);
>>>       > - return n;
>>>       > +size_t mg_flash_write_align(void) {
>>>       > + return 16; // 128 bit
>>>       > }
>>>       > -
>>>       > -char *mg_vmprintf(const char *fmt, va_list *ap) {
>>>       > - struct mg_iobuf io = {0, 0, 0, 256};
>>>       > - mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
>>>       > - return (char *) io.buf;
>>>       > +int mg_flash_bank(void) {
>>>       > + return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
>>>       > }
>>>       >
>>>       > -char *mg_mprintf(const char *fmt, ...) {
>>>       > - char *s;
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - s = mg_vmprintf(fmt, &ap);
>>>       > - va_end(ap);
>>>       > - return s;
>>>       > +static void flash_unlock(void) {
>>>       > + static bool unlocked = false;
>>>       > + if (unlocked == false) {
>>>       > + MG_REG(FLASH_KEYR) = 0x45670123;
>>>       > + MG_REG(FLASH_KEYR) = 0Xcdef89ab;
>>>       > + MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
>>>       > + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
>>>       > + unlocked = true;
>>>       > + }
>>>       > }
>>>       >
>>>       > -size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
>>>      char *fmt, ...) {
>>>       > - size_t len = 0;
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - len = mg_vxprintf(out, ptr, fmt, &ap);
>>>       > - va_end(ap);
>>>       > - return len;
>>>       > +static int flash_page_start(volatile uint32_t *dst) {
>>>       > + char *base = (char *) mg_flash_start(), *end = base +
>>>      mg_flash_size();
>>>       > + volatile char *p = (char *) dst;
>>>       > + return p >= base && p < end && ((p - base) %
>>>      mg_flash_sector_size()) == 0;
>>>       > }
>>>       >
>>>       > -static bool is_digit(int c) {
>>>       > - return c >= '0' && c <= '9';
>>>       > +static bool flash_is_err(void) {
>>>       > + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481
>>>      7.11.9
>>>       > }
>>>       >
>>>       > -static int addexp(char *buf, int e, int sign) {
>>>       > - int n = 0;
>>>       > - buf[n++] = 'e';
>>>       > - buf[n++] = (char) sign;
>>>       > - if (e > 400) return 0;
>>>       > - if (e < 10) buf[n++] = '0';
>>>       > - if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
>>>      / 100);
>>>       > - if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
>>>       > - buf[n++] = (char) (e + '0');
>>>       > - return n;
>>>       > +static void flash_wait(void) {
>>>       > + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
>>>       > + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
>>>       > + (void) 0;
>>>       > + }
>>>       > }
>>>       >
>>>       > -static int xisinf(double x) {
>>>       > - union {
>>>       > - double f;
>>>       > - uint64_t u;
>>>       > - } ieee754 = {x};
>>>       > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
>>>      0x7ff00000 &&
>>>       > - ((unsigned) ieee754.u == 0);
>>>       > +static void flash_clear_err(void) {
>>>       > + flash_wait(); // Wait until ready
>>>       > + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all
>>>      errors
>>>       > }
>>>       >
>>>       > -static int xisnan(double x) {
>>>       > - union {
>>>       > - double f;
>>>       > - uint64_t u;
>>>       > - } ieee754 = {x};
>>>       > - return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
>>>       > - ((unsigned) ieee754.u != 0) >
>>>       > - 0x7ff00000;
>>>       > +static bool flash_bank_is_swapped(void) {
>>>       > + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8
>>>       > }
>>>       >
>>>       > -static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
>>>      width) {
>>>       > - char buf[40];
>>>       > - int i, s = 0, n = 0, e = 0;
>>>       > - double t, mul, saved;
>>>       > - if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
>>>       > - if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
>>>      "inf" : "-inf");
>>>       > - if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
>>>       > - if (d < 0.0) d = -d, buf[s++] = '-';
>>>       > -
>>>       > - // Round
>>>       > - saved = d;
>>>       > - mul = 1.0;
>>>       > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
>>>       > - while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
>>>       > - for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
>>>       > - d += t;
>>>       > - // Calculate exponent, and 'mul' for scientific representation
>>>       > - mul = 1.0;
>>>       > - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
>>>       > - while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
>>>       > - // printf(" --> %g %d %g %g\n", saved, e, t, mul);
>>>       > -
>>>       > - if (e >= width) {
>>>       > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
>>>       > - // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
>>>       > - n += addexp(buf + s + n, e, '+');
>>>       > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>>>       > - } else if (e <= -width) {
>>>       > - n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
>>>       > - // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
>>>       > - n += addexp(buf + s + n, -e, '-');
>>>       > - return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>>>       > +bool mg_flash_erase(void *location) {
>>>       > + bool ok = false;
>>>       > + if (flash_page_start(location) == false) {
>>>       > + MG_ERROR(("%p is not on a sector boundary"));
>>>       > } else {
>>>       > - for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
>>>       > - int ch = (int) (d / t);
>>>       > - if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
>>>       > - d -= ch * t;
>>>       > - t /= 10.0;
>>>       > - }
>>>       > - // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
>>>      + n, buf);
>>>       > - if (n == 0) buf[s++] = '0';
>>>       > - while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
>>>      /= 10.0;
>>>       > - if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
>>>       > - // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
>>>       > - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
>>>      i++) {
>>>       > - int ch = (int) (d / t);
>>>       > - buf[s + n++] = (char) (ch + '0');
>>>       > - d -= ch * t;
>>>       > - t /= 10.0;
>>>       > + uintptr_t diff = (char *) location - (char *) mg_flash_start();
>>>       > + uint32_t sector = diff / mg_flash_sector_size();
>>>       > + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
>>>       > + flash_unlock();
>>>       > + flash_clear_err();
>>>       > + MG_REG(FLASH_NSCR) = 0;
>>>       > + if ((sector < 128 && flash_bank_is_swapped()) ||
>>>       > + (sector > 127 && !flash_bank_is_swapped())) {
>>>       > + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL
>>>       > }
>>>       > + if (sector > 127) sector -= 128;
>>>       > + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase |
>>>      sector_num
>>>       > + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing
>>>       > + flash_wait();
>>>       > + ok = !flash_is_err();
>>>       > + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector,
>>>      location,
>>>       > + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
>>>       > + // mg_hexdump(location, 32);
>>>       > + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
>>>       > }
>>>       > - while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
>>>       > - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
>>>       > - n += s;
>>>       > - if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
>>>       > - buf[n] = '\0';
>>>       > - return mg_snprintf(dst, dstlen, "%s", buf);
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -static size_t mg_lld(char *buf, int64_t val, bool is_signed,
>>>      bool is_hex) {
>>>       > - const char *letters = "0123456789abcdef";
>>>       > - uint64_t v = (uint64_t) val;
>>>       > - size_t s = 0, n, i;
>>>       > - if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
>>>       > - // This loop prints a number in reverse order. I guess this is
>>>      because we
>>>       > - // write numbers from right to left: least significant digit
>>>      comes last.
>>>       > - // Maybe because we use Arabic numbers, and Arabs write RTL?
>>>       > - if (is_hex) {
>>>       > - for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
>>>       > - } else {
>>>       > - for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
>>>       > +bool mg_flash_swap_bank(void) {
>>>       > + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
>>>       > + flash_unlock();
>>>       > + flash_clear_err();
>>>       > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
>>>       > + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
>>>       > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
>>>       > + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
>>>       > + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired)
>>>      (void) 0;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +bool mg_flash_write(void *addr, const void *buf, size_t len) {
>>>       > + if ((len % mg_flash_write_align()) != 0) {
>>>       > + MG_ERROR(("%lu is not aligned to %lu", len,
>>>      mg_flash_write_align()));
>>>       > + return false;
>>>       > }
>>>       > - // Reverse a string
>>>       > - for (i = 0; i < n / 2; i++) {
>>>       > - char t = buf[s + i];
>>>       > - buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
>>>       > + uint32_t *dst = (uint32_t *) addr;
>>>       > + uint32_t *src = (uint32_t *) buf;
>>>       > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>>>       > + bool ok = true;
>>>       > + flash_unlock();
>>>       > + flash_clear_err();
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
>>>       > + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag
>>>       > + while (ok && src < end) {
>>>       > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
>>>       > + *(volatile uint32_t *) dst++ = *src++;
>>>       > + flash_wait();
>>>       > + if (flash_is_err()) ok = false;
>>>       > }
>>>       > - if (val == 0) buf[n++] = '0'; // Handle special case
>>>       > - return n + s;
>>>       > + MG_ARM_ENABLE_IRQ();
>>>       > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
>>>      len, dst,
>>>       > + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
>>>       > + MG_REG(FLASH_NSSR)));
>>>       > + MG_REG(FLASH_NSCR) = 0; // Clear flags
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
>>>       > - size_t len) {
>>>       > - size_t i = 0;
>>>       > - while (i < len && buf[i] != '\0') out(buf[i++], ptr);
>>>       > - return i;
>>>       > +void mg_device_reset(void) {
>>>       > + // SCB->AIRCR = ((0x5fa <<
>>>      SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
>>>       > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -static char mg_esc(int c, bool esc) {
>>>       > - const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
>>>       > - for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
>>>       > - if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/device_stm32h7.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_DEVICE == MG_DEVICE_STM32H7
>>>       > +
>>>       > +#define FLASH_BASE1 0x52002000 // Base address for bank1
>>>       > +#define FLASH_BASE2 0x52002100 // Base address for bank2
>>>       > +#define FLASH_KEYR 0x04 // See RM0433 4.9.2
>>>       > +#define FLASH_OPTKEYR 0x08
>>>       > +#define FLASH_OPTCR 0x18
>>>       > +#define FLASH_SR 0x10
>>>       > +#define FLASH_CR 0x0c
>>>       > +#define FLASH_CCR 0x14
>>>       > +#define FLASH_OPTSR_CUR 0x1c
>>>       > +#define FLASH_OPTSR_PRG 0x20
>>>       > +#define FLASH_SIZE_REG 0x1ff1e880
>>>       > +
>>>       > +MG_IRAM void *mg_flash_start(void) {
>>>       > + return (void *) 0x08000000;
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_size(void) {
>>>       > + return MG_REG(FLASH_SIZE_REG) * 1024;
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_sector_size(void) {
>>>       > + return 128 * 1024; // 128k
>>>       > +}
>>>       > +MG_IRAM size_t mg_flash_write_align(void) {
>>>       > + return 32; // 256 bit
>>>       > +}
>>>       > +MG_IRAM int mg_flash_bank(void) {
>>>       > + if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual
>>>      bank support
>>>       > + return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
>>>       > +}
>>>       > +
>>>       > +MG_IRAM static void flash_unlock(void) {
>>>       > + static bool unlocked = false;
>>>       > + if (unlocked == false) {
>>>       > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
>>>       > + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
>>>       > + if (mg_flash_bank() > 0) {
>>>       > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
>>>       > + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
>>>       > + }
>>>       > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is
>>>      "shared"
>>>       > + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus
>>>      unlock once
>>>       > + unlocked = true;
>>>       > }
>>>       > - return 0;
>>>       > }
>>>       >
>>>       > -static char mg_escape(int c) {
>>>       > - return mg_esc(c, true);
>>>       > +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
>>>       > + char *base = (char *) mg_flash_start(), *end = base +
>>>      mg_flash_size();
>>>       > + volatile char *p = (char *) dst;
>>>       > + return p >= base && p < end && ((p - base) %
>>>      mg_flash_sector_size()) == 0;
>>>       > }
>>>       >
>>>       > -static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
>>>       > - size_t len) {
>>>       > - size_t i = 0, extra = 0;
>>>       > - for (i = 0; i < len && buf[i] != '\0'; i++) {
>>>       > - char c = mg_escape(buf[i]);
>>>       > - if (c) {
>>>       > - out('\\', ptr), out(c, ptr), extra++;
>>>       > - } else {
>>>       > - out(buf[i], ptr);
>>>       > - }
>>>       > - }
>>>       > - return i + extra;
>>>       > +MG_IRAM static bool flash_is_err(uint32_t bank) {
>>>       > + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); //
>>>      RM0433 4.9.5
>>>       > }
>>>       >
>>>       > -static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
>>>       > - size_t len) {
>>>       > - size_t n = 2;
>>>       > - out('"', ptr);
>>>       > - n += qcpy(out, ptr, buf, len);
>>>       > - out('"', ptr);
>>>       > - return n;
>>>       > +MG_IRAM static void flash_wait(uint32_t bank) {
>>>       > + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2)))
>>>      (void) 0;
>>>       > }
>>>       >
>>>       > -static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t
>>>      *buf,
>>>       > - size_t len) {
>>>       > - size_t i, n = 0;
>>>       > - const char *t =
>>>       > -
>>>      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
>>>       > - out('"', ptr), n++;
>>>       > - for (i = 0; i < len; i += 3) {
>>>       > - uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
>>>       > - c3 = i + 2 < len ? buf[i + 2] : 0;
>>>       > - char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
>>>      '='};
>>>       > - if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
>>>       > - if (i + 2 < len) tmp[3] = t[c3 & 63];
>>>       > - n += scpy(out, ptr, tmp, sizeof(tmp));
>>>       > - }
>>>       > - out('"', ptr), n++;
>>>       > - return n;
>>>       > +MG_IRAM static void flash_clear_err(uint32_t bank) {
>>>       > + flash_wait(bank); // Wait until ready
>>>       > + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear
>>>      all errors
>>>       > }
>>>       >
>>>       > -size_t mg_vxprintf(void (*out)(char, void *), void *param, const
>>>      char *fmt,
>>>       > - va_list *ap) {
>>>       > - size_t i = 0, n = 0;
>>>       > - while (fmt[i] != '\0') {
>>>       > - if (fmt[i] == '%') {
>>>       > - size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
>>>      prec */;
>>>       > - char pad = ' ', minus = 0, c = fmt[++i];
>>>       > - if (c == '#') x++, c = fmt[++i];
>>>       > - if (c == '-') minus++, c = fmt[++i];
>>>       > - if (c == '0') pad = '0', c = fmt[++i];
>>>       > - while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
>>>      fmt[++i];
>>>       > - if (c == '.') {
>>>       > - c = fmt[++i];
>>>       > - if (c == '*') {
>>>       > - pr = (size_t) va_arg(*ap, int);
>>>       > - c = fmt[++i];
>>>       > - } else {
>>>       > - pr = 0;
>>>       > - while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
>>>      fmt[++i];
>>>       > - }
>>>       > - }
>>>       > - while (c == 'h') c = fmt[++i]; // Treat h and hh as int
>>>       > - if (c == 'l') {
>>>       > - is_long++, c = fmt[++i];
>>>       > - if (c == 'l') is_long++, c = fmt[++i];
>>>       > - }
>>>       > - if (c == 'p') x = 1, is_long = 1;
>>>       > - if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
>>>       > - c == 'g' || c == 'f') {
>>>       > - bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
>>>       > - char tmp[40];
>>>       > - size_t xl = x ? 2 : 0;
>>>       > - if (c == 'g' || c == 'f') {
>>>       > - double v = va_arg(*ap, double);
>>>       > - if (pr == ~0U) pr = 6;
>>>       > - k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
>>>       > - } else if (is_long == 2) {
>>>       > - int64_t v = va_arg(*ap, int64_t);
>>>       > - k = mg_lld(tmp, v, s, h);
>>>       > - } else if (is_long == 1) {
>>>       > - long v = va_arg(*ap, long);
>>>       > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
>>>      s, h);
>>>       > - } else {
>>>       > - int v = va_arg(*ap, int);
>>>       > - k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
>>>       > - }
>>>       > - for (j = 0; j < xl && w > 0; j++) w--;
>>>       > - for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
>>>       > - n += scpy(out, param, &pad, 1);
>>>       > - n += scpy(out, param, (char *) "0x", xl);
>>>       > - for (j = 0; pad == '0' && k < w && j + k < w; j++)
>>>       > - n += scpy(out, param, &pad, 1);
>>>       > - n += scpy(out, param, tmp, k);
>>>       > - for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
>>>       > - n += scpy(out, param, &pad, 1);
>>>       > - } else if (c == 'M') {
>>>       > - mg_pm_t f = va_arg(*ap, mg_pm_t);
>>>       > - n += f(out, param, ap);
>>>       > - } else if (c == 'c') {
>>>       > - int ch = va_arg(*ap, int);
>>>       > - out((char) ch, param);
>>>       > - n++;
>>>       > - } else if (c == 'H') {
>>>       > - // Print hex-encoded double-quoted string
>>>       > - size_t bl = (size_t) va_arg(*ap, int);
>>>       > - uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
>>>       > - const char *hex = "0123456789abcdef";
>>>       > - n += scpy(out, param, (char *) &dquote, 1);
>>>       > - for (j = 0; j < bl; j++) {
>>>       > - n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
>>>       > - n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
>>>       > - }
>>>       > - n += scpy(out, param, (char *) &dquote, 1);
>>>       > - } else if (c == 'I') {
>>>       > - // Print IPv4 or IPv6 address
>>>       > - size_t len = (size_t) va_arg(*ap, int); // Length 16 means IPv6
>>>      address
>>>       > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the IP
>>>      address
>>>       > - if (len == 6) {
>>>       > - uint16_t *p = (uint16_t *) buf;
>>>       > - n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x",
>>>      mg_htons(p[0]),
>>>       > - mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
>>>       > - mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
>>>       > - mg_htons(p[7]));
>>>       > - } else {
>>>       > - n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int)
>>>      buf[1],
>>>       > - (int) buf[2], (int) buf[3]);
>>>       > - }
>>>       > - } else if (c == 'A') {
>>>       > - // Print hardware addresses (currently Ethernet MAC)
>>>       > - uint8_t *buf = va_arg(*ap, uint8_t *); // Pointer to the hw
>>>      address
>>>       > - n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
>>>       > - (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
>>>       > - (int) buf[4], (int) buf[5]);
>>>       > - } else if (c == 'V') {
>>>       > - // Print base64-encoded double-quoted string
>>>       > - size_t len = (size_t) va_arg(*ap, int);
>>>       > - uint8_t *buf = va_arg(*ap, uint8_t *);
>>>       > - n += bcpy(out, param, buf, len);
>>>       > - } else if (c == 's' || c == 'Q' || c == 'q') {
>>>       > - char *p = va_arg(*ap, char *);
>>>       > - size_t (*f)(void (*)(char, void *), void *, char *, size_t) =
>>>      scpy;
>>>       > - if (c == 'Q') f = Qcpy;
>>>       > - if (c == 'q') f = qcpy;
>>>       > - if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
>>>       > - for (j = 0; !minus && pr < w && j + pr < w; j++)
>>>       > - n += f(out, param, &pad, 1);
>>>       > - n += f(out, param, p, pr);
>>>       > - for (j = 0; minus && pr < w && j + pr < w; j++)
>>>       > - n += f(out, param, &pad, 1);
>>>       > - } else if (c == '%') {
>>>       > - out('%', param);
>>>       > - n++;
>>>       > - } else {
>>>       > - out('%', param);
>>>       > - out(c, param);
>>>       > - n += 2;
>>>       > - }
>>>       > - i++;
>>>       > - } else {
>>>       > - out(fmt[i], param), n++, i++;
>>>       > - }
>>>       > - }
>>>       > - return n;
>>>       > +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
>>>       > + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/fs.c"
>>>       > -#endif
>>>       > -
>>>       > -
>>>       > -
>>>       > -struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>>>      flags) {
>>>       > - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
>>>       > - if (fd != NULL) {
>>>       > - fd->fd = fs->op(path, flags);
>>>       > - fd->fs = fs;
>>>       > - if (fd->fd == NULL) {
>>>       > - free(fd);
>>>       > - fd = NULL;
>>>       > - }
>>>       > - }
>>>       > - return fd;
>>>       > +// Figure out flash bank based on the address
>>>       > +MG_IRAM static uint32_t flash_bank(void *addr) {
>>>       > + size_t ofs = (char *) addr - (char *) mg_flash_start();
>>>       > + if (mg_flash_bank() == 0) return FLASH_BASE1;
>>>       > + return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
>>>       > }
>>>       >
>>>       > -void mg_fs_close(struct mg_fd *fd) {
>>>       > - if (fd != NULL) {
>>>       > - fd->fs->cl(fd->fd);
>>>       > - free(fd);
>>>       > +MG_IRAM bool mg_flash_erase(void *addr) {
>>>       > + bool ok = false;
>>>       > + if (flash_page_start(addr) == false) {
>>>       > + MG_ERROR(("%p is not on a sector boundary", addr));
>>>       > + } else {
>>>       > + uintptr_t diff = (char *) addr - (char *) mg_flash_start();
>>>       > + uint32_t sector = diff / mg_flash_sector_size();
>>>       > + uint32_t bank = flash_bank(addr);
>>>       > + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value
>>>       > +
>>>       > + flash_unlock();
>>>       > + if (sector > 7) sector -= 8;
>>>       > +
>>>       > + flash_clear_err(bank);
>>>       > + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism
>>>       > + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
>>>       > + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
>>>       > + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
>>>       > + ok = !flash_is_err(bank);
>>>       > + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector,
>>>      addr,
>>>       > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
>>>       > + MG_REG(bank + FLASH_SR)));
>>>       > + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR
>>>       > }
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
>>>      *sizep) {
>>>       > - struct mg_fd *fd;
>>>       > - char *data = NULL;
>>>       > - size_t size = 0;
>>>       > - fs->st(path, &size, NULL);
>>>       > - if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
>>>       > - data = (char *) calloc(1, size + 1);
>>>       > - if (data != NULL) {
>>>       > - if (fs->rd(fd->fd, data, size) != size) {
>>>       > - free(data);
>>>       > - data = NULL;
>>>       > - } else {
>>>       > - data[size] = '\0';
>>>       > - if (sizep != NULL) *sizep = size;
>>>       > - }
>>>       > - }
>>>       > - mg_fs_close(fd);
>>>       > - }
>>>       > - return data;
>>>       > +MG_IRAM bool mg_flash_swap_bank(void) {
>>>       > + if (mg_flash_bank() == 0) return true;
>>>       > + uint32_t bank = FLASH_BASE1;
>>>       > + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
>>>       > + flash_unlock();
>>>       > + flash_clear_err(bank);
>>>       > + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
>>>       > + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
>>>       > + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
>>>       > + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
>>>       > + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) !=
>>>      desired) (void) 0;
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -bool mg_file_write(struct mg_fs *fs, const char *path, const
>>>      void *buf,
>>>       > - size_t len) {
>>>       > - bool result = false;
>>>       > - struct mg_fd *fd;
>>>       > - char tmp[MG_PATH_MAX];
>>>       > - mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
>>>       > - if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
>>>       > - result = fs->wr(fd->fd, buf, len) == len;
>>>       > - mg_fs_close(fd);
>>>       > - if (result) {
>>>       > - fs->rm(path);
>>>       > - fs->mv(tmp, path);
>>>       > - } else {
>>>       > - fs->rm(tmp);
>>>       > - }
>>>       > +MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t
>>>      len) {
>>>       > + if ((len % mg_flash_write_align()) != 0) {
>>>       > + MG_ERROR(("%lu is not aligned to %lu", len,
>>>      mg_flash_write_align()));
>>>       > + return false;
>>>       > }
>>>       > - return result;
>>>       > + uint32_t bank = flash_bank(addr);
>>>       > + uint32_t *dst = (uint32_t *) addr;
>>>       > + uint32_t *src = (uint32_t *) buf;
>>>       > + uint32_t *end = (uint32_t *) ((char *) buf + len);
>>>       > + bool ok = true;
>>>       > + flash_unlock();
>>>       > + flash_clear_err(bank);
>>>       > + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag
>>>       > + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism
>>>       > + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
>>>       > + MG_ARM_DISABLE_IRQ();
>>>       > + while (ok && src < end) {
>>>       > + if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
>>>       > + *(volatile uint32_t *) dst++ = *src++;
>>>       > + flash_wait(bank);
>>>       > + if (flash_is_err(bank)) ok = false;
>>>       > + }
>>>       > + MG_ARM_ENABLE_IRQ();
>>>       > + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx",
>>>      len, dst,
>>>       > + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
>>>       > + MG_REG(bank + FLASH_SR)));
>>>       > + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -bool mg_file_printf(struct mg_fs *fs, const char *path, const
>>>      char *fmt, ...) {
>>>       > - va_list ap;
>>>       > - char *data;
>>>       > - bool result = false;
>>>       > - va_start(ap, fmt);
>>>       > - data = mg_vmprintf(fmt, &ap);
>>>       > - va_end(ap);
>>>       > - result = mg_file_write(fs, path, data, strlen(data));
>>>       > - free(data);
>>>       > - return result;
>>>       > +MG_IRAM void mg_device_reset(void) {
>>>       > + // SCB->AIRCR = ((0x5fa <<
>>>      SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
>>>       > + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/fs_fat.c"
>>>       > +#line 1 "src/dns.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_FATFS
>>>       > -#include <ff.h>
>>>       >
>>>       > -static int mg_days_from_epoch(int y, int m, int d) {
>>>       > - y -= m <= 2;
>>>       > - int era = y / 400;
>>>       > - int yoe = y - era * 400;
>>>       > - int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
>>>       > - int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
>>>       > - return era * 146097 + doe - 719468;
>>>       > -}
>>>       >
>>>       > -static time_t mg_timegm(const struct tm *t) {
>>>       > - int year = t->tm_year + 1900;
>>>       > - int month = t->tm_mon; // 0-11
>>>       > - if (month > 11) {
>>>       > - year += month / 12;
>>>       > - month %= 12;
>>>       > - } else if (month < 0) {
>>>       > - int years_diff = (11 - month) / 12;
>>>       > - year -= years_diff;
>>>       > - month += 12 * years_diff;
>>>       > - }
>>>       > - int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
>>>       > - return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
>>>       > -}
>>>       >
>>>       > -static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
>>>       > - struct tm tm;
>>>       > - memset(&tm, 0, sizeof(struct tm));
>>>       > - tm.tm_sec = (ftime << 1) & 0x3e;
>>>       > - tm.tm_min = ((ftime >> 5) & 0x3f);
>>>       > - tm.tm_hour = ((ftime >> 11) & 0x1f);
>>>       > - tm.tm_mday = (fdate & 0x1f);
>>>       > - tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
>>>       > - tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
>>>       > - return mg_timegm(&tm);
>>>       > -}
>>>       >
>>>       > -static int ff_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > - FILINFO fi;
>>>       > - if (path[0] == '\0') {
>>>       > - if (size) *size = 0;
>>>       > - if (mtime) *mtime = 0;
>>>       > - return MG_FS_DIR;
>>>       > - } else if (f_stat(path, &fi) == 0) {
>>>       > - if (size) *size = (size_t) fi.fsize;
>>>       > - if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
>>>       > - return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
>>>      MG_FS_DIR : 0);
>>>       > - } else {
>>>       > - return 0;
>>>       > - }
>>>       > -}
>>>       >
>>>       > -static void ff_list(const char *dir, void (*fn)(const char *,
>>>      void *),
>>>       > - void *userdata) {
>>>       > - DIR d;
>>>       > - FILINFO fi;
>>>       > - if (f_opendir(&d, dir) == FR_OK) {
>>>       > - while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
>>>       > - if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
>>>       > - fn(fi.fname, userdata);
>>>       > - }
>>>       > - f_closedir(&d);
>>>       > - }
>>>       > -}
>>>       > +struct dns_data {
>>>       > + struct dns_data *next;
>>>       > + struct mg_connection *c;
>>>       > + uint64_t expire;
>>>       > + uint16_t txnid;
>>>       > +};
>>>       >
>>>       > -static void *ff_open(const char *path, int flags) {
>>>       > - FIL f;
>>>       > - unsigned char mode = FA_READ;
>>>       > - if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
>>>      FA_OPEN_APPEND;
>>>       > - if (f_open(&f, path, mode) == 0) {
>>>       > - FIL *fp = calloc(1, sizeof(*fp));
>>>       > - memcpy(fp, &f, sizeof(*fp));
>>>       > - return fp;
>>>       > - } else {
>>>       > - return NULL;
>>>       > - }
>>>       > +static void mg_sendnsreq(struct mg_connection *, struct mg_str
>>>      *, int,
>>>       > + struct mg_dns *, bool);
>>>       > +
>>>       > +static void mg_dns_free(struct dns_data **head, struct dns_data
>>>      *d) {
>>>       > + LIST_DELETE(struct dns_data, head, d);
>>>       > + free(d);
>>>       > }
>>>       >
>>>       > -static void ff_close(void *fp) {
>>>       > - if (fp != NULL) {
>>>       > - f_close((FIL *) fp);
>>>       > - free(fp);
>>>       > +void mg_resolve_cancel(struct mg_connection *c) {
>>>       > + struct dns_data *tmp, *d;
>>>       > + struct dns_data **head = (struct dns_data **)
>>>      &c->mgr->active_dns_requests;
>>>       > + for (d = *head; d != NULL; d = tmp) {
>>>       > + tmp = d->next;
>>>       > + if (d->c == c) mg_dns_free(head, d);
>>>       > }
>>>       > }
>>>       >
>>>       > -static size_t ff_read(void *fp, void *buf, size_t len) {
>>>       > - UINT n = 0, misalign = ((size_t) buf) & 3;
>>>       > - if (misalign) {
>>>       > - char aligned[4];
>>>       > - f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
>>>       > - memcpy(buf, aligned, n);
>>>       > - } else {
>>>       > - f_read((FIL *) fp, buf, len, &n);
>>>       > +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t
>>>      len, size_t ofs,
>>>       > + char *to, size_t tolen, size_t j,
>>>       > + int depth) {
>>>       > + size_t i = 0;
>>>       > + if (tolen > 0 && depth == 0) to[0] = '\0';
>>>       > + if (depth > 5) return 0;
>>>       > + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs
>>>      + 1]));
>>>       > + while (ofs + i + 1 < len) {
>>>       > + size_t n = s[ofs + i];
>>>       > + if (n == 0) {
>>>       > + i++;
>>>       > + break;
>>>       > + }
>>>       > + if (n & 0xc0) {
>>>       > + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr
>>>      len
>>>       > + // MG_INFO(("PTR %lx", (unsigned long) ptr));
>>>       > + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
>>>       > + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1)
>>>      == 0)
>>>       > + return 0;
>>>       > + i += 2;
>>>       > + break;
>>>       > + }
>>>       > + if (ofs + i + n + 1 >= len) return 0;
>>>       > + if (j > 0) {
>>>       > + if (j < tolen) to[j] = '.';
>>>       > + j++;
>>>       > + }
>>>       > + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
>>>       > + j += n;
>>>       > + i += n + 1;
>>>       > + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk
>>>       > + // MG_INFO(("--> [%s]", to));
>>>       > }
>>>       > - return n;
>>>       > + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it
>>>      is nul-term
>>>       > + return i;
>>>       > }
>>>       >
>>>       > -static size_t ff_write(void *fp, const void *buf, size_t len) {
>>>       > - UINT n = 0;
>>>       > - return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
>>>      : 0;
>>>       > +static size_t mg_dns_parse_name(const uint8_t *s, size_t n,
>>>      size_t ofs,
>>>       > + char *dst, size_t dstlen) {
>>>       > + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
>>>       > }
>>>       >
>>>       > -static size_t ff_seek(void *fp, size_t offset) {
>>>       > - f_lseek((FIL *) fp, offset);
>>>       > - return offset;
>>>       > +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
>>>       > + bool is_question, struct mg_dns_rr *rr) {
>>>       > + const uint8_t *s = buf + ofs, *e = &buf[len];
>>>       > +
>>>       > + memset(rr, 0, sizeof(*rr));
>>>       > + if (len < sizeof(struct mg_dns_header)) return 0; // Too small
>>>       > + if (len > 512) return 0; // Too large, we don't expect that
>>>       > + if (s >= e) return 0; // Overflow
>>>       > +
>>>       > + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs,
>>>      NULL, 0)) == 0)
>>>       > + return 0;
>>>       > + s += rr->nlen + 4;
>>>       > + if (s > e) return 0;
>>>       > + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
>>>       > + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>>>       > + if (is_question) return (size_t) (rr->nlen + 4);
>>>       > +
>>>       > + s += 6;
>>>       > + if (s > e) return 0;
>>>       > + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
>>>       > + if (s + rr->alen > e) return 0;
>>>       > + return (size_t) (rr->nlen + rr->alen + 10);
>>>       > }
>>>       >
>>>       > -static bool ff_rename(const char *from, const char *to) {
>>>       > - return f_rename(from, to) == FR_OK;
>>>       > +bool mg_dns_parse(const uint8_t *buf, size_t len, struct
>>>      mg_dns_message *dm) {
>>>       > + const struct mg_dns_header *h = (struct mg_dns_header *) buf;
>>>       > + struct mg_dns_rr rr;
>>>       > + size_t i, n, num_answers, ofs = sizeof(*h);
>>>       > + memset(dm, 0, sizeof(*dm));
>>>       > +
>>>       > + if (len < sizeof(*h)) return 0; // Too small, headers dont fit
>>>       > + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity
>>>       > + num_answers = mg_ntohs(h->num_answers);
>>>       > + if (num_answers > 10) {
>>>       > + MG_DEBUG(("Got %u answers, ignoring beyond 10th one",
>>>      num_answers));
>>>       > + num_answers = 10; // Sanity cap
>>>       > + }
>>>       > + dm->txnid = mg_ntohs(h->txnid);
>>>       > +
>>>       > + for (i = 0; i < mg_ntohs(h->num_questions); i++) {
>>>       > + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0)
>>>      return false;
>>>       > + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
>>>       > + ofs += n;
>>>       > + }
>>>       > + for (i = 0; i < num_answers; i++) {
>>>       > + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0)
>>>      return false;
>>>       > + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype,
>>>      rr.aclass,
>>>       > + // dm->name));
>>>       > + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
>>>       > + ofs += n;
>>>       > +
>>>       > + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
>>>       > + dm->addr.is_ip6 = false;
>>>       > + memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
>>>       > + dm->resolved = true;
>>>       > + break; // Return success
>>>       > + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
>>>       > + dm->addr.is_ip6 = true;
>>>       > + memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
>>>       > + dm->resolved = true;
>>>       > + break; // Return success
>>>       > + }
>>>       > + }
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -static bool ff_remove(const char *path) {
>>>       > - return f_unlink(path) == FR_OK;
>>>       > +static void dns_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + struct dns_data *d, *tmp;
>>>       > + struct dns_data **head = (struct dns_data **)
>>>      &c->mgr->active_dns_requests;
>>>       > + if (ev == MG_EV_POLL) {
>>>       > + uint64_t now = *(uint64_t *) ev_data;
>>>       > + for (d = *head; d != NULL; d = tmp) {
>>>       > + tmp = d->next;
>>>       > + // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
>>>       > + if (now > d->expire) mg_error(d->c, "DNS timeout");
>>>       > + }
>>>       > + } else if (ev == MG_EV_READ) {
>>>       > + struct mg_dns_message dm;
>>>       > + int resolved = 0;
>>>       > + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
>>>       > + MG_ERROR(("Unexpected DNS response:"));
>>>       > + mg_hexdump(c->recv.buf, c->recv.len);
>>>       > + } else {
>>>       > + // MG_VERBOSE(("%s %d", dm.name <http://dm.name>, dm.resolved));
>>>       > + for (d = *head; d != NULL; d = tmp) {
>>>       > + tmp = d->next;
>>>       > + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
>>>       > + if (dm.txnid != d->txnid) continue;
>>>       > + if (d->c->is_resolving) {
>>>       > + if (dm.resolved) {
>>>       > + dm.addr.port = d->c->rem.port; // Save port
>>>       > + d->c->rem = dm.addr; // Copy resolved address
>>>       > + MG_DEBUG(
>>>       > + ("%lu %s is %M", d->c->id, dm.name <http://dm.name>,
>>>      mg_print_ip, &d->c->rem));
>>>       > + mg_connect_resolved(d->c);
>>>       > +#if MG_ENABLE_IPV6
>>>       > + } else if (dm.addr.is_ip6 == false && dm.name
>>>      <http://dm.name>[0] != '\0' &&
>>>       > + c->mgr->use_dns6 == false) {
>>>       > + struct mg_str x = mg_str(dm.name <http://dm.name>);
>>>       > + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
>>>       > +#endif
>>>       > + } else {
>>>       > + mg_error(d->c, "%s DNS lookup failed", dm.name <http://dm.name>);
>>>       > + }
>>>       > + } else {
>>>       > + MG_ERROR(("%lu already resolved", d->c->id));
>>>       > + }
>>>       > + mg_dns_free(head, d);
>>>       > + resolved = 1;
>>>       > + }
>>>       > + }
>>>       > + if (!resolved) MG_ERROR(("stray DNS reply"));
>>>       > + c->recv.len = 0;
>>>       > + } else if (ev == MG_EV_CLOSE) {
>>>       > + for (d = *head; d != NULL; d = tmp) {
>>>       > + tmp = d->next;
>>>       > + mg_error(d->c, "DNS error");
>>>       > + mg_dns_free(head, d);
>>>       > + }
>>>       > + }
>>>       > }
>>>       >
>>>       > -static bool ff_mkdir(const char *path) {
>>>       > - return f_mkdir(path) == FR_OK;
>>>       > +static bool mg_dns_send(struct mg_connection *c, const struct
>>>      mg_str *name,
>>>       > + uint16_t txnid, bool ipv6) {
>>>       > + struct {
>>>       > + struct mg_dns_header header;
>>>       > + uint8_t data[256];
>>>       > + } pkt;
>>>       > + size_t i, n;
>>>       > + memset(&pkt, 0, sizeof(pkt));
>>>       > + pkt.header.txnid = mg_htons(txnid);
>>>       > + pkt.header.flags = mg_htons(0x100);
>>>       > + pkt.header.num_questions = mg_htons(1);
>>>       > + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
>>>       > + if (name->buf[i] == '.' || i >= name->len) {
>>>       > + pkt.data[n] = (uint8_t) (i - n);
>>>       > + memcpy(&pkt.data[n + 1], name->buf + n, i - n);
>>>       > + n = i + 1;
>>>       > + }
>>>       > + if (i >= name->len) break;
>>>       > + }
>>>       > + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query
>>>       > + n += 5;
>>>       > + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query
>>>       > + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA
>>>      query
>>>       > + // n += 6;
>>>       > + return mg_send(c, &pkt, sizeof(pkt.header) + n);
>>>       > }
>>>       >
>>>       > -struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
>>>      ff_read,
>>>       > - ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
>>>       > -#endif
>>>       > +static void mg_sendnsreq(struct mg_connection *c, struct mg_str
>>>      *name, int ms,
>>>       > + struct mg_dns *dnsc, bool ipv6) {
>>>       > + struct dns_data *d = NULL;
>>>       > + if (dnsc->url == NULL) {
>>>       > + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
>>>       > + } else if (dnsc->c == NULL) {
>>>       > + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
>>>       > + if (dnsc->c != NULL) {
>>>       > + dnsc->c->pfn = dns_cb;
>>>       > + // dnsc->c->is_hexdumping = 1;
>>>       > + }
>>>       > + }
>>>       > + if (dnsc->c == NULL) {
>>>       > + mg_error(c, "resolver");
>>>       > + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) ==
>>>      NULL) {
>>>       > + mg_error(c, "resolve OOM");
>>>       > + } else {
>>>       > + struct dns_data *reqs = (struct dns_data *)
>>>      c->mgr->active_dns_requests;
>>>       > + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
>>>       > + d->next = (struct dns_data *) c->mgr->active_dns_requests;
>>>       > + c->mgr->active_dns_requests = d;
>>>       > + d->expire = mg_millis() + (uint64_t) ms;
>>>       > + d->c = c;
>>>       > + c->is_resolving = 1;
>>>       > + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int)
>>>      name->len,
>>>       > + name->buf, dnsc->url, d->txnid));
>>>       > + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
>>>       > + mg_error(dnsc->c, "DNS send");
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_resolve(struct mg_connection *c, const char *url) {
>>>       > + struct mg_str host = mg_url_host(url);
>>>       > + c->rem.port = mg_htons(mg_url_port(url));
>>>       > + if (mg_aton(host, &c->rem)) {
>>>       > + // host is an IP address, do not fire name resolution
>>>       > + mg_connect_resolved(c);
>>>       > + } else {
>>>       > + // host is not an IP, send DNS resolution request
>>>       > + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 :
>>>      &c->mgr->dns4;
>>>       > + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
>>>       > + }
>>>       > +}
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/fs_packed.c"
>>>       > +#line 1 "src/event.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > -struct packed_file {
>>>       > - const char *data;
>>>       > - size_t size;
>>>       > - size_t pos;
>>>       > -};
>>>       >
>>>       > -const char *mg_unpack(const char *path, size_t *size, time_t
>>>      *mtime);
>>>       > -const char *mg_unlist(size_t no);
>>>       >
>>>       > -#if MG_ENABLE_PACKED_FS
>>>       > -#else
>>>       > -const char *mg_unpack(const char *path, size_t *size, time_t
>>>      *mtime) {
>>>       > - (void) path, (void) size, (void) mtime;
>>>       > - return NULL;
>>>       > -}
>>>       > -const char *mg_unlist(size_t no) {
>>>       > - (void) no;
>>>       > - return NULL;
>>>       > -}
>>>       > +void mg_call(struct mg_connection *c, int ev, void *ev_data) {
>>>       > +#if MG_ENABLE_PROFILE
>>>       > + const char *names[] = {
>>>       > + "EV_ERROR", "EV_OPEN", "EV_POLL", "EV_RESOLVE",
>>>       > + "EV_CONNECT", "EV_ACCEPT", "EV_TLS_HS", "EV_READ",
>>>       > + "EV_WRITE", "EV_CLOSE", "EV_HTTP_MSG", "EV_HTTP_CHUNK",
>>>       > + "EV_WS_OPEN", "EV_WS_MSG", "EV_WS_CTL", "EV_MQTT_CMD",
>>>       > + "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
>>>       > + if (ev != MG_EV_POLL && ev < (int) (sizeof(names) /
>>>      sizeof(names[0]))) {
>>>       > + MG_PROF_ADD(c, names[ev]);
>>>       > + }
>>>       > #endif
>>>       > -
>>>       > -static int is_dir_prefix(const char *prefix, size_t n, const
>>>      char *path) {
>>>       > - // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
>>>       > - return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
>>>       > - (n == 0 || path[n] == '/' || path[n - 1] == '/');
>>>       > + // Fire protocol handler first, user handler second. See #2559
>>>       > + if (c->pfn != NULL) c->pfn(c, ev, ev_data);
>>>       > + if (c->fn != NULL) c->fn(c, ev, ev_data);
>>>       > }
>>>       >
>>>       > -static int packed_stat(const char *path, size_t *size, time_t
>>>      *mtime) {
>>>       > - const char *p;
>>>       > - size_t i, n = strlen(path);
>>>       > - if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
>>>      file
>>>       > - // Scan all files. If `path` is a dir prefix for any of them,
>>>      it's a dir
>>>       > - for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
>>>       > - if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
>>>       > - }
>>>       > - return 0;
>>>       > -}
>>>       > -
>>>       > -static void packed_list(const char *dir, void (*fn)(const char
>>>      *, void *),
>>>       > - void *userdata) {
>>>       > - char buf[MG_PATH_MAX], tmp[sizeof(buf)];
>>>       > - const char *path, *begin, *end;
>>>       > - size_t i, n = strlen(dir);
>>>       > - tmp[0] = '\0'; // Previously listed entry
>>>       > - for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
>>>       > - if (!is_dir_prefix(dir, n, path)) continue;
>>>       > - begin = &path[n + 1];
>>>       > - end = strchr(begin, '/');
>>>       > - if (end == NULL) end = begin + strlen(begin);
>>>       > - mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
>>>       > - buf[sizeof(buf) - 1] = '\0';
>>>       > - // If this entry has been already listed, skip
>>>       > - // NOTE: we're assuming that file list is sorted alphabetically
>>>       > - if (strcmp(buf, tmp) == 0) continue;
>>>       > - fn(buf, userdata); // Not yet listed, call user function
>>>       > - strcpy(tmp, buf); // And save this entry as listed
>>>       > - }
>>>       > -}
>>>       > -
>>>       > -static void *packed_open(const char *path, int flags) {
>>>       > - size_t size = 0;
>>>       > - const char *data = mg_unpack(path, &size, NULL);
>>>       > - struct packed_file *fp = NULL;
>>>       > - if (data == NULL) return NULL;
>>>       > - if (flags & MG_FS_WRITE) return NULL;
>>>       > - fp = (struct packed_file *) calloc(1, sizeof(*fp));
>>>       > - fp->size = size;
>>>       > - fp->data = data;
>>>       > - return (void *) fp;
>>>       > +void mg_error(struct mg_connection *c, const char *fmt, ...) {
>>>       > + char buf[64];
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
>>>       > + va_end(ap);
>>>       > + MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
>>>       > + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL
>>>       > + mg_call(c, MG_EV_ERROR, buf); // Let user handler override it
>>>       > }
>>>       >
>>>       > -static void packed_close(void *fp) {
>>>       > - if (fp != NULL) free(fp);
>>>       > -}
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/fmt.c"
>>>       > +#endif
>>>       >
>>>       > -static size_t packed_read(void *fd, void *buf, size_t len) {
>>>       > - struct packed_file *fp = (struct packed_file *) fd;
>>>       > - if (fp->pos + len > fp->size) len = fp->size - fp->pos;
>>>       > - memcpy(buf, &fp->data[fp->pos], len);
>>>       > - fp->pos += len;
>>>       > - return len;
>>>       > -}
>>>       >
>>>       > -static size_t packed_write(void *fd, const void *buf, size_t len) {
>>>       > - (void) fd, (void) buf, (void) len;
>>>       > - return 0;
>>>       > -}
>>>       >
>>>       > -static size_t packed_seek(void *fd, size_t offset) {
>>>       > - struct packed_file *fp = (struct packed_file *) fd;
>>>       > - fp->pos = offset;
>>>       > - if (fp->pos > fp->size) fp->pos = fp->size;
>>>       > - return fp->pos;
>>>       > -}
>>>       >
>>>       > -static bool packed_rename(const char *from, const char *to) {
>>>       > - (void) from, (void) to;
>>>       > - return false;
>>>       > +static bool is_digit(int c) {
>>>       > + return c >= '0' && c <= '9';
>>>       > }
>>>       >
>>>       > -static bool packed_remove(const char *path) {
>>>       > - (void) path;
>>>       > - return false;
>>>       > +static int addexp(char *buf, int e, int sign) {
>>>       > + int n = 0;
>>>       > + buf[n++] = 'e';
>>>       > + buf[n++] = (char) sign;
>>>       > + if (e > 400) return 0;
>>>       > + if (e < 10) buf[n++] = '0';
>>>       > + if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e
>>>      / 100);
>>>       > + if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
>>>       > + buf[n++] = (char) (e + '0');
>>>       > + return n;
>>>       > }
>>>       >
>>>       > -static bool packed_mkdir(const char *path) {
>>>       > - (void) path;
>>>       > - return false;
>>>       > +static int xisinf(double x) {
>>>       > + union {
>>>       > + double f;
>>>       > + uint64_t u;
>>>       > + } ieee754 = {x};
>>>       > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) ==
>>>      0x7ff00000 &&
>>>       > + ((unsigned) ieee754.u == 0);
>>>       > }
>>>       >
>>>       > -struct mg_fs mg_fs_packed = {
>>>       > - packed_stat, packed_list, packed_open, packed_close, packed_read,
>>>       > - packed_write, packed_seek, packed_rename, packed_remove,
>>>      packed_mkdir};
>>>       > -
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/fs_posix.c"
>>>       > -#endif
>>>       > -
>>>       > -
>>>       > -#if MG_ENABLE_FILE
>>>       > -
>>>       > -#ifndef MG_STAT_STRUCT
>>>       > -#define MG_STAT_STRUCT stat
>>>       > -#endif
>>>       > -
>>>       > -#ifndef MG_STAT_FUNC
>>>       > -#define MG_STAT_FUNC stat
>>>       > -#endif
>>>       > -
>>>       > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > -#if !defined(S_ISDIR)
>>>       > - MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
>>>      mtime));
>>>       > - return 0;
>>>       > -#else
>>>       > -#if MG_ARCH == MG_ARCH_WIN32
>>>       > - struct _stati64 st;
>>>       > - wchar_t tmp[MG_PATH_MAX];
>>>       > - MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
>>>      sizeof(tmp[0]));
>>>       > - if (_wstati64(tmp, &st) != 0) return 0;
>>>       > -#else
>>>       > - struct MG_STAT_STRUCT st;
>>>       > - if (MG_STAT_FUNC(path, &st) != 0) return 0;
>>>       > -#endif
>>>       > - if (size) *size = (size_t) st.st_size;
>>>       > - if (mtime) *mtime = st.st_mtime;
>>>       > - return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
>>>      MG_FS_DIR : 0);
>>>       > -#endif
>>>       > +static int xisnan(double x) {
>>>       > + union {
>>>       > + double f;
>>>       > + uint64_t u;
>>>       > + } ieee754 = {x};
>>>       > + return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
>>>       > + ((unsigned) ieee754.u != 0) >
>>>       > + 0x7ff00000;
>>>       > }
>>>       >
>>>       > -#if MG_ARCH == MG_ARCH_WIN32
>>>       > -struct dirent {
>>>       > - char d_name[MAX_PATH];
>>>       > -};
>>>       > -
>>>       > -typedef struct win32_dir {
>>>       > - HANDLE handle;
>>>       > - WIN32_FIND_DATAW info;
>>>       > - struct dirent result;
>>>       > -} DIR;
>>>       > +static size_t mg_dtoa(char *dst, size_t dstlen, double d, int
>>>      width, bool tz) {
>>>       > + char buf[40];
>>>       > + int i, s = 0, n = 0, e = 0;
>>>       > + double t, mul, saved;
>>>       > + if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
>>>       > + if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ?
>>>      "inf" : "-inf");
>>>       > + if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
>>>       > + if (d < 0.0) d = -d, buf[s++] = '-';
>>>       >
>>>       > -int gettimeofday(struct timeval *tv, void *tz) {
>>>       > - FILETIME ft;
>>>       > - unsigned __int64 tmpres = 0;
>>>       > + // Round
>>>       > + saved = d;
>>>       > + mul = 1.0;
>>>       > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
>>>       > + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
>>>       > + for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
>>>       > + d += t;
>>>       > + // Calculate exponent, and 'mul' for scientific representation
>>>       > + mul = 1.0;
>>>       > + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
>>>       > + while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
>>>       > + // printf(" --> %g %d %g %g\n", saved, e, t, mul);
>>>       >
>>>       > - if (tv != NULL) {
>>>       > - GetSystemTimeAsFileTime(&ft);
>>>       > - tmpres |= ft.dwHighDateTime;
>>>       > - tmpres <<= 32;
>>>       > - tmpres |= ft.dwLowDateTime;
>>>       > - tmpres /= 10; // convert into microseconds
>>>       > - tmpres -= (int64_t) 11644473600000000;
>>>       > - tv->tv_sec = (long) (tmpres / 1000000UL);
>>>       > - tv->tv_usec = (long) (tmpres % 1000000UL);
>>>       > + if (e >= width && width > 1) {
>>>       > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
>>>       > + // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
>>>       > + n += addexp(buf + s + n, e, '+');
>>>       > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>>>       > + } else if (e <= -width && width > 1) {
>>>       > + n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
>>>       > + // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
>>>       > + n += addexp(buf + s + n, -e, '-');
>>>       > + return mg_snprintf(dst, dstlen, "%.*s", n, buf);
>>>       > + } else {
>>>       > + for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
>>>       > + int ch = (int) (d / t);
>>>       > + if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
>>>       > + d -= ch * t;
>>>       > + t /= 10.0;
>>>       > + }
>>>       > + // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s
>>>      + n, buf);
>>>       > + if (n == 0) buf[s++] = '0';
>>>       > + while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t
>>>      /= 10.0;
>>>       > + if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
>>>       > + // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
>>>       > + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width;
>>>      i++) {
>>>       > + int ch = (int) (d / t);
>>>       > + buf[s + n++] = (char) (ch + '0');
>>>       > + d -= ch * t;
>>>       > + t /= 10.0;
>>>       > + }
>>>       > }
>>>       > - (void) tz;
>>>       > - return 0;
>>>       > + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim
>>>      trailing zeroes
>>>       > + if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
>>>       > + n += s;
>>>       > + if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
>>>       > + buf[n] = '\0';
>>>       > + return mg_snprintf(dst, dstlen, "%s", buf);
>>>       > }
>>>       >
>>>       > -static int to_wchar(const char *path, wchar_t *wbuf, size_t
>>>      wbuf_len) {
>>>       > - int ret;
>>>       > - char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
>>>       > - strncpy(buf, path, sizeof(buf));
>>>       > - buf[sizeof(buf) - 1] = '\0';
>>>       > - // Trim trailing slashes. Leave backslash for paths like "X:\"
>>>       > - p = buf + strlen(buf) - 1;
>>>       > - while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
>>>      '/')) *p-- = '\0';
>>>       > - memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
>>>       > - ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
>>>      wbuf_len);
>>>       > - // Convert back to Unicode. If doubly-converted string does not
>>>      match the
>>>       > - // original, something is fishy, reject.
>>>       > - WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
>>>      sizeof(buf2),
>>>       > - NULL, NULL);
>>>       > - if (strcmp(buf, buf2) != 0) {
>>>       > - wbuf[0] = L'\0';
>>>       > - ret = 0;
>>>       > +static size_t mg_lld(char *buf, int64_t val, bool is_signed,
>>>      bool is_hex) {
>>>       > + const char *letters = "0123456789abcdef";
>>>       > + uint64_t v = (uint64_t) val;
>>>       > + size_t s = 0, n, i;
>>>       > + if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
>>>       > + // This loop prints a number in reverse order. I guess this is
>>>      because we
>>>       > + // write numbers from right to left: least significant digit
>>>      comes last.
>>>       > + // Maybe because we use Arabic numbers, and Arabs write RTL?
>>>       > + if (is_hex) {
>>>       > + for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
>>>       > + } else {
>>>       > + for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
>>>       > }
>>>       > - return ret;
>>>       > + // Reverse a string
>>>       > + for (i = 0; i < n / 2; i++) {
>>>       > + char t = buf[s + i];
>>>       > + buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
>>>       > + }
>>>       > + if (val == 0) buf[n++] = '0'; // Handle special case
>>>       > + return n + s;
>>>       > }
>>>       >
>>>       > -DIR *opendir(const char *name) {
>>>       > - DIR *d = NULL;
>>>       > - wchar_t wpath[MAX_PATH];
>>>       > - DWORD attrs;
>>>       > +static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
>>>       > + size_t len) {
>>>       > + size_t i = 0;
>>>       > + while (i < len && buf[i] != '\0') out(buf[i++], ptr);
>>>       > + return i;
>>>       > +}
>>>       >
>>>       > - if (name == NULL) {
>>>       > - SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
>>>       > - SetLastError(ERROR_NOT_ENOUGH_MEMORY);
>>>       > - } else {
>>>       > - to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
>>>       > - attrs = GetFileAttributesW(wpath);
>>>       > - if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
>>>       > - (void) wcscat(wpath, L"\\*");
>>>       > - d->handle = FindFirstFileW(wpath, &d->info);
>>>       > - d->result.d_name[0] = '\0';
>>>       > +size_t mg_xprintf(void (*out)(char, void *), void *ptr, const
>>>      char *fmt, ...) {
>>>       > + size_t len = 0;
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + len = mg_vxprintf(out, ptr, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +size_t mg_vxprintf(void (*out)(char, void *), void *param, const
>>>      char *fmt,
>>>       > + va_list *ap) {
>>>       > + size_t i = 0, n = 0;
>>>       > + while (fmt[i] != '\0') {
>>>       > + if (fmt[i] == '%') {
>>>       > + size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /*
>>>      prec */;
>>>       > + char pad = ' ', minus = 0, c = fmt[++i];
>>>       > + if (c == '#') x++, c = fmt[++i];
>>>       > + if (c == '-') minus++, c = fmt[++i];
>>>       > + if (c == '0') pad = '0', c = fmt[++i];
>>>       > + while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c =
>>>      fmt[++i];
>>>       > + if (c == '.') {
>>>       > + c = fmt[++i];
>>>       > + if (c == '*') {
>>>       > + pr = (size_t) va_arg(*ap, int);
>>>       > + c = fmt[++i];
>>>       > + } else {
>>>       > + pr = 0;
>>>       > + while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c =
>>>      fmt[++i];
>>>       > + }
>>>       > + }
>>>       > + while (c == 'h') c = fmt[++i]; // Treat h and hh as int
>>>       > + if (c == 'l') {
>>>       > + is_long++, c = fmt[++i];
>>>       > + if (c == 'l') is_long++, c = fmt[++i];
>>>       > + }
>>>       > + if (c == 'p') x = 1, is_long = 1;
>>>       > + if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
>>>       > + c == 'g' || c == 'f') {
>>>       > + bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
>>>       > + char tmp[40];
>>>       > + size_t xl = x ? 2 : 0;
>>>       > + if (c == 'g' || c == 'f') {
>>>       > + double v = va_arg(*ap, double);
>>>       > + if (pr == ~0U) pr = 6;
>>>       > + k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
>>>       > + } else if (is_long == 2) {
>>>       > + int64_t v = va_arg(*ap, int64_t);
>>>       > + k = mg_lld(tmp, v, s, h);
>>>       > + } else if (is_long == 1) {
>>>       > + long v = va_arg(*ap, long);
>>>       > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v,
>>>      s, h);
>>>       > + } else {
>>>       > + int v = va_arg(*ap, int);
>>>       > + k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
>>>       > + }
>>>       > + for (j = 0; j < xl && w > 0; j++) w--;
>>>       > + for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
>>>       > + n += scpy(out, param, &pad, 1);
>>>       > + n += scpy(out, param, (char *) "0x", xl);
>>>       > + for (j = 0; pad == '0' && k < w && j + k < w; j++)
>>>       > + n += scpy(out, param, &pad, 1);
>>>       > + n += scpy(out, param, tmp, k);
>>>       > + for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
>>>       > + n += scpy(out, param, &pad, 1);
>>>       > + } else if (c == 'm' || c == 'M') {
>>>       > + mg_pm_t f = va_arg(*ap, mg_pm_t);
>>>       > + if (c == 'm') out('"', param);
>>>       > + n += f(out, param, ap);
>>>       > + if (c == 'm') n += 2, out('"', param);
>>>       > + } else if (c == 'c') {
>>>       > + int ch = va_arg(*ap, int);
>>>       > + out((char) ch, param);
>>>       > + n++;
>>>       > + } else if (c == 's') {
>>>       > + char *p = va_arg(*ap, char *);
>>>       > + if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
>>>       > + for (j = 0; !minus && pr < w && j + pr < w; j++)
>>>       > + n += scpy(out, param, &pad, 1);
>>>       > + n += scpy(out, param, p, pr);
>>>       > + for (j = 0; minus && pr < w && j + pr < w; j++)
>>>       > + n += scpy(out, param, &pad, 1);
>>>       > + } else if (c == '%') {
>>>       > + out('%', param);
>>>       > + n++;
>>>       > + } else {
>>>       > + out('%', param);
>>>       > + out(c, param);
>>>       > + n += 2;
>>>       > + }
>>>       > + i++;
>>>       > } else {
>>>       > - free(d);
>>>       > - d = NULL;
>>>       > + out(fmt[i], param), n++, i++;
>>>       > }
>>>       > }
>>>       > - return d;
>>>       > + return n;
>>>       > }
>>>       >
>>>       > -int closedir(DIR *d) {
>>>       > - int result = 0;
>>>       > - if (d != NULL) {
>>>       > - if (d->handle != INVALID_HANDLE_VALUE)
>>>       > - result = FindClose(d->handle) ? 0 : -1;
>>>       > - free(d);
>>>       > - } else {
>>>       > - result = -1;
>>>       > - SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/fs.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>>>      flags) {
>>>       > + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
>>>       > + if (fd != NULL) {
>>>       > + fd->fd = fs->op(path, flags);
>>>       > + fd->fs = fs;
>>>       > + if (fd->fd == NULL) {
>>>       > + free(fd);
>>>       > + fd = NULL;
>>>       > + }
>>>       > }
>>>       > - return result;
>>>       > + return fd;
>>>       > }
>>>       >
>>>       > -struct dirent *readdir(DIR *d) {
>>>       > - struct dirent *result = NULL;
>>>       > - if (d != NULL) {
>>>       > - memset(&d->result, 0, sizeof(d->result));
>>>       > - if (d->handle != INVALID_HANDLE_VALUE) {
>>>       > - result = &d->result;
>>>       > - WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
>>>      result->d_name,
>>>       > - sizeof(result->d_name), NULL, NULL);
>>>       > - if (!FindNextFileW(d->handle, &d->info)) {
>>>       > - FindClose(d->handle);
>>>       > - d->handle = INVALID_HANDLE_VALUE;
>>>       > - }
>>>       > - } else {
>>>       > - SetLastError(ERROR_FILE_NOT_FOUND);
>>>       > - }
>>>       > - } else {
>>>       > - SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > +void mg_fs_close(struct mg_fd *fd) {
>>>       > + if (fd != NULL) {
>>>       > + fd->fs->cl(fd->fd);
>>>       > + free(fd);
>>>       > }
>>>       > - return result;
>>>       > }
>>>       > -#endif
>>>       >
>>>       > -static void p_list(const char *dir, void (*fn)(const char *,
>>>      void *),
>>>       > - void *userdata) {
>>>       > -#if MG_ENABLE_DIRLIST
>>>       > - struct dirent *dp;
>>>       > - DIR *dirp;
>>>       > - if ((dirp = (opendir(dir))) == NULL) return;
>>>       > - while ((dp = readdir(dirp)) != NULL) {
>>>       > - if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
>>>      continue;
>>>       > - fn(dp->d_name, userdata);
>>>       > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
>>>       > + struct mg_str result = {NULL, 0};
>>>       > + void *fp;
>>>       > + fs->st(path, &result.len, NULL);
>>>       > + if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
>>>       > + result.buf = (char *) calloc(1, result.len + 1);
>>>       > + if (result.buf != NULL &&
>>>       > + fs->rd(fp, (void *) result.buf, result.len) != result.len) {
>>>       > + free((void *) result.buf);
>>>       > + result.buf = NULL;
>>>       > + }
>>>       > + fs->cl(fp);
>>>       > }
>>>       > - closedir(dirp);
>>>       > -#else
>>>       > - (void) dir, (void) fn, (void) userdata;
>>>       > -#endif
>>>       > + if (result.buf == NULL) result.len = 0;
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -static void *p_open(const char *path, int flags) {
>>>       > - const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
>>>       > -#if MG_ARCH == MG_ARCH_WIN32
>>>       > - wchar_t b1[MG_PATH_MAX], b2[10];
>>>       > - MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
>>>      sizeof(b1[0]));
>>>       > - MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
>>>      sizeof(b2[0]));
>>>       > - return (void *) _wfopen(b1, b2);
>>>       > -#else
>>>       > - return (void *) fopen(path, mode);
>>>       > -#endif
>>>       > +bool mg_file_write(struct mg_fs *fs, const char *path, const
>>>      void *buf,
>>>       > + size_t len) {
>>>       > + bool result = false;
>>>       > + struct mg_fd *fd;
>>>       > + char tmp[MG_PATH_MAX];
>>>       > + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
>>>       > + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
>>>       > + result = fs->wr(fd->fd, buf, len) == len;
>>>       > + mg_fs_close(fd);
>>>       > + if (result) {
>>>       > + fs->rm(path);
>>>       > + fs->mv(tmp, path);
>>>       > + } else {
>>>       > + fs->rm(tmp);
>>>       > + }
>>>       > + }
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -static void p_close(void *fp) {
>>>       > - fclose((FILE *) fp);
>>>       > +bool mg_file_printf(struct mg_fs *fs, const char *path, const
>>>      char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + char *data;
>>>       > + bool result = false;
>>>       > + va_start(ap, fmt);
>>>       > + data = mg_vmprintf(fmt, &ap);
>>>       > + va_end(ap);
>>>       > + result = mg_file_write(fs, path, data, strlen(data));
>>>       > + free(data);
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -static size_t p_read(void *fp, void *buf, size_t len) {
>>>       > - return fread(buf, 1, len, (FILE *) fp);
>>>       > +// This helper function allows to scan a filesystem in a
>>>      sequential way,
>>>       > +// without using callback function:
>>>       > +// char buf[100] = "";
>>>       > +// while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
>>>       > +// ...
>>>       > +static void mg_fs_ls_fn(const char *filename, void *param) {
>>>       > + struct mg_str *s = (struct mg_str *) param;
>>>       > + if (s->buf[0] == '\0') {
>>>       > + mg_snprintf((char *) s->buf, s->len, "%s", filename);
>>>       > + } else if (strcmp(s->buf, filename) == 0) {
>>>       > + ((char *) s->buf)[0] = '\0'; // Fetch next file
>>>       > + }
>>>       > }
>>>       >
>>>       > -static size_t p_write(void *fp, const void *buf, size_t len) {
>>>       > - return fwrite(buf, 1, len, (FILE *) fp);
>>>       > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
>>>      size_t len) {
>>>       > + struct mg_str s = {buf, len};
>>>       > + fs->ls(path, mg_fs_ls_fn, &s);
>>>       > + return buf[0] != '\0';
>>>       > }
>>>       >
>>>       > -static size_t p_seek(void *fp, size_t offset) {
>>>       > -#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
>>>       > - (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
>>>       > - (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
>>>       > - if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
>>>       > -#else
>>>       > - if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/fs_fat.c"
>>>       > #endif
>>>       > - return (size_t) ftell((FILE *) fp);
>>>       > -}
>>>       >
>>>       > -static bool p_rename(const char *from, const char *to) {
>>>       > - return rename(from, to) == 0;
>>>       > -}
>>>       >
>>>       > -static bool p_remove(const char *path) {
>>>       > - return remove(path) == 0;
>>>       > -}
>>>       >
>>>       > -static bool p_mkdir(const char *path) {
>>>       > - return mkdir(path, 0775) == 0;
>>>       > -}
>>>       > +#if MG_ENABLE_FATFS
>>>       > +#include <ff.h>
>>>       >
>>>       > -#else
>>>       > +static int mg_days_from_epoch(int y, int m, int d) {
>>>       > + y -= m <= 2;
>>>       > + int era = y / 400;
>>>       > + int yoe = y - era * 400;
>>>       > + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
>>>       > + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
>>>       > + return era * 146097 + doe - 719468;
>>>       > +}
>>>       >
>>>       > -static int p_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > - (void) path, (void) size, (void) mtime;
>>>       > - return 0;
>>>       > +static time_t mg_timegm(const struct tm *t) {
>>>       > + int year = t->tm_year + 1900;
>>>       > + int month = t->tm_mon; // 0-11
>>>       > + if (month > 11) {
>>>       > + year += month / 12;
>>>       > + month %= 12;
>>>       > + } else if (month < 0) {
>>>       > + int years_diff = (11 - month) / 12;
>>>       > + year -= years_diff;
>>>       > + month += 12 * years_diff;
>>>       > + }
>>>       > + int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
>>>       > + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
>>>       > }
>>>       > -static void p_list(const char *path, void (*fn)(const char *,
>>>      void *),
>>>       > - void *userdata) {
>>>       > - (void) path, (void) fn, (void) userdata;
>>>       > -}
>>>       > -static void *p_open(const char *path, int flags) {
>>>       > - (void) path, (void) flags;
>>>       > - return NULL;
>>>       > -}
>>>       > -static void p_close(void *fp) {
>>>       > - (void) fp;
>>>       > -}
>>>       > -static size_t p_read(void *fd, void *buf, size_t len) {
>>>       > - (void) fd, (void) buf, (void) len;
>>>       > - return 0;
>>>       > -}
>>>       > -static size_t p_write(void *fd, const void *buf, size_t len) {
>>>       > - (void) fd, (void) buf, (void) len;
>>>       > - return 0;
>>>       > -}
>>>       > -static size_t p_seek(void *fd, size_t offset) {
>>>       > - (void) fd, (void) offset;
>>>       > - return (size_t) ~0;
>>>       > -}
>>>       > -static bool p_rename(const char *from, const char *to) {
>>>       > - (void) from, (void) to;
>>>       > - return false;
>>>       > -}
>>>       > -static bool p_remove(const char *path) {
>>>       > - (void) path;
>>>       > - return false;
>>>       > -}
>>>       > -static bool p_mkdir(const char *path) {
>>>       > - (void) path;
>>>       > - return false;
>>>       > -}
>>>       > -#endif
>>>       > -
>>>       > -struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
>>>      p_read,
>>>       > - p_write, p_seek, p_rename, p_remove, p_mkdir};
>>>       > -
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/http.c"
>>>       > -#endif
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -// Chunk deletion marker is the MSB in the "processed" counter
>>>       > -#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
>>>       > -
>>>       > -// Multipart POST example:
>>>       > -// --xyz
>>>       > -// Content-Disposition: form-data; name="val"
>>>       > -//
>>>       > -// abcdef
>>>       > -// --xyz
>>>       > -// Content-Disposition: form-data; name="foo"; filename="a.txt"
>>>       > -// Content-Type: text/plain
>>>       > -//
>>>       > -// hello world
>>>       > -//
>>>       > -// --xyz--
>>>       > -size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
>>>       > - struct mg_http_part *part) {
>>>       > - struct mg_str cd = mg_str_n("Content-Disposition", 19);
>>>       > - const char *s = body.ptr;
>>>       > - size_t b = ofs, h1, h2, b1, b2, max = body.len;
>>>       > -
>>>       > - // Init part params
>>>       > - if (part != NULL) part->name = part->filename = part->body =
>>>      mg_str_n(0, 0);
>>>       > -
>>>       > - // Skip boundary
>>>       > - while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
>>>       > - if (b <= ofs || b + 2 >= max) return 0;
>>>       > - // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
>>>      s));
>>>       > -
>>>       > - // Skip headers
>>>       > - h1 = h2 = b + 2;
>>>       > - for (;;) {
>>>       > - while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
>>>       > - if (h2 == h1) break;
>>>       > - if (h2 + 2 >= max) return 0;
>>>       > - // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
>>>       > - if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
>>>      ':' &&
>>>       > - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
>>>       > - struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
>>>      cd.len + 2));
>>>       > - part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
>>>       > - part->filename = mg_http_get_header_var(v, mg_str_n("filename",
>>>      8));
>>>       > - }
>>>       > - h1 = h2 = h2 + 2;
>>>       > - }
>>>       > - b1 = b2 = h2 + 2;
>>>       > - while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
>>>      + 1] == '\n' &&
>>>       > - memcmp(&s[b2 + 2], s, b - ofs) == 0))
>>>       > - b2++;
>>>       >
>>>       > - if (b2 + 2 >= max) return 0;
>>>       > - if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
>>>       > - // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
>>>       > - return b2 + 2;
>>>       > +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
>>>       > + struct tm tm;
>>>       > + memset(&tm, 0, sizeof(struct tm));
>>>       > + tm.tm_sec = (ftime << 1) & 0x3e;
>>>       > + tm.tm_min = ((ftime >> 5) & 0x3f);
>>>       > + tm.tm_hour = ((ftime >> 11) & 0x1f);
>>>       > + tm.tm_mday = (fdate & 0x1f);
>>>       > + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
>>>       > + tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
>>>       > + return mg_timegm(&tm);
>>>       > }
>>>       >
>>>       > -void mg_http_bauth(struct mg_connection *c, const char *user,
>>>       > - const char *pass) {
>>>       > - struct mg_str u = mg_str(user), p = mg_str(pass);
>>>       > - size_t need = c->send.len + 36 + (u.len + p.len) * 2;
>>>       > - if (c->send.size < need) mg_iobuf_resize(&c->send, need);
>>>       > - if (c->send.size >= need) {
>>>       > - int i, n = 0;
>>>       > - char *buf = (char *) &c->send.buf[c->send.len];
>>>       > - memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
>>>       > - for (i = 0; i < (int) u.len; i++) {
>>>       > - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
>>>       > - }
>>>       > - if (p.len > 0) {
>>>       > - n = mg_base64_update(':', buf + 21, n);
>>>       > - for (i = 0; i < (int) p.len; i++) {
>>>       > - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
>>>       > - }
>>>       > - }
>>>       > - n = mg_base64_final(buf + 21, n);
>>>       > - c->send.len += 21 + (size_t) n + 2;
>>>       > - memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
>>>       > +static int ff_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > + FILINFO fi;
>>>       > + if (path[0] == '\0') {
>>>       > + if (size) *size = 0;
>>>       > + if (mtime) *mtime = 0;
>>>       > + return MG_FS_DIR;
>>>       > + } else if (f_stat(path, &fi) == 0) {
>>>       > + if (size) *size = (size_t) fi.fsize;
>>>       > + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
>>>       > + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ?
>>>      MG_FS_DIR : 0);
>>>       > } else {
>>>       > - MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
>>>      need));
>>>       > + return 0;
>>>       > }
>>>       > }
>>>       >
>>>       > -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
>>>       > - struct mg_str k, v, result = mg_str_n(NULL, 0);
>>>       > - while (mg_split(&buf, &k, &v, '&')) {
>>>       > - if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) ==
>>>      0) {
>>>       > - result = v;
>>>       > - break;
>>>       > +static void ff_list(const char *dir, void (*fn)(const char *,
>>>      void *),
>>>       > + void *userdata) {
>>>       > + DIR d;
>>>       > + FILINFO fi;
>>>       > + if (f_opendir(&d, dir) == FR_OK) {
>>>       > + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
>>>       > + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
>>>       > + fn(fi.fname, userdata);
>>>       > }
>>>       > + f_closedir(&d);
>>>       > }
>>>       > - return result;
>>>       > }
>>>       >
>>>       > -int mg_http_get_var(const struct mg_str *buf, const char *name,
>>>      char *dst,
>>>       > - size_t dst_len) {
>>>       > - int len;
>>>       > - if (dst == NULL || dst_len == 0) {
>>>       > - len = -2; // Bad destination
>>>       > - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
>>>       > - len = -1; // Bad source
>>>       > - dst[0] = '\0';
>>>       > - } else {
>>>       > - struct mg_str v = mg_http_var(*buf, mg_str(name));
>>>       > - if (v.ptr == NULL) {
>>>       > - len = -4; // Name does not exist
>>>       > - } else {
>>>       > - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
>>>       > - if (len < 0) len = -3; // Failed to decode
>>>       > +static void *ff_open(const char *path, int flags) {
>>>       > + FIL f;
>>>       > + unsigned char mode = FA_READ;
>>>       > + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS |
>>>      FA_OPEN_APPEND;
>>>       > + if (f_open(&f, path, mode) == 0) {
>>>       > + FIL *fp;
>>>       > + if ((fp = calloc(1, sizeof(*fp))) != NULL) {
>>>       > + memcpy(fp, &f, sizeof(*fp));
>>>       > + return fp;
>>>       > }
>>>       > }
>>>       > - return len;
>>>       > + return NULL;
>>>       > }
>>>       >
>>>       > -static bool isx(int c) {
>>>       > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>>>       > - (c >= 'A' && c <= 'F');
>>>       > +static void ff_close(void *fp) {
>>>       > + if (fp != NULL) {
>>>       > + f_close((FIL *) fp);
>>>       > + free(fp);
>>>       > + }
>>>       > }
>>>       >
>>>       > -int mg_url_decode(const char *src, size_t src_len, char *dst,
>>>      size_t dst_len,
>>>       > - int is_form_url_encoded) {
>>>       > - size_t i, j;
>>>       > - for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
>>>       > - if (src[i] == '%') {
>>>       > - // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
>>>      src_len
>>>       > - if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
>>>       > - mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
>>>       > - i += 2;
>>>       > - } else {
>>>       > - return -1;
>>>       > - }
>>>       > - } else if (is_form_url_encoded && src[i] == '+') {
>>>       > - dst[j] = ' ';
>>>       > - } else {
>>>       > - dst[j] = src[i];
>>>       > - }
>>>       > +static size_t ff_read(void *fp, void *buf, size_t len) {
>>>       > + UINT n = 0, misalign = ((size_t) buf) & 3;
>>>       > + if (misalign) {
>>>       > + char aligned[4];
>>>       > + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
>>>       > + memcpy(buf, aligned, n);
>>>       > + } else {
>>>       > + f_read((FIL *) fp, buf, len, &n);
>>>       > }
>>>       > - if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
>>>       > - return i >= src_len && j < dst_len ? (int) j : -1;
>>>       > + return n;
>>>       > }
>>>       >
>>>       > -static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c
>>>       >= ' '; }
>>>       > -
>>>       > -int mg_http_get_request_len(const unsigned char *buf, size_t
>>>      buf_len) {
>>>       > - size_t i;
>>>       > - for (i = 0; i < buf_len; i++) {
>>>       > - if (!isok(buf[i])) return -1;
>>>       > - if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
>>>       > - (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
>>>      '\n'))
>>>       > - return (int) i + 1;
>>>       > - }
>>>       > - return 0;
>>>       > +static size_t ff_write(void *fp, const void *buf, size_t len) {
>>>       > + UINT n = 0;
>>>       > + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n
>>>      : 0;
>>>       > }
>>>       >
>>>       > -static const char *skip(const char *s, const char *e, const char
>>>      *d,
>>>       > - struct mg_str *v) {
>>>       > - v->ptr = s;
>>>       > - while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
>>>       > - v->len = (size_t) (s - v->ptr);
>>>       > - while (s < e && strchr(d, *s) != NULL) s++;
>>>       > - return s;
>>>       > +static size_t ff_seek(void *fp, size_t offset) {
>>>       > + f_lseek((FIL *) fp, offset);
>>>       > + return offset;
>>>       > }
>>>       >
>>>       > -struct mg_str *mg_http_get_header(struct mg_http_message *h,
>>>      const char *name) {
>>>       > - size_t i, n = strlen(name), max = sizeof(h->headers) /
>>>      sizeof(h->headers[0]);
>>>       > - for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
>>>       > - struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
>>>       > - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
>>>       > - }
>>>       > - return NULL;
>>>       > +static bool ff_rename(const char *from, const char *to) {
>>>       > + return f_rename(from, to) == FR_OK;
>>>       > }
>>>       >
>>>       > -static void mg_http_parse_headers(const char *s, const char *end,
>>>       > - struct mg_http_header *h, int max_headers) {
>>>       > - int i;
>>>       > - for (i = 0; i < max_headers; i++) {
>>>       > - struct mg_str k, v, tmp;
>>>       > - const char *he = skip(s, end, "\n", &tmp);
>>>       > - s = skip(s, he, ": \r\n", &k);
>>>       > - s = skip(s, he, "\r\n", &v);
>>>       > - if (k.len == tmp.len) continue;
>>>       > - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim
>>>      spaces
>>>       > - if (k.len == 0) break;
>>>       > - // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1,
>>>      tmp.ptr,
>>>       > - //(int) k.len, k.ptr, (int) v.len, v.ptr));
>>>       > - h[i].name = k;
>>>       > - h[i].value = v;
>>>       > - }
>>>       > +static bool ff_remove(const char *path) {
>>>       > + return f_unlink(path) == FR_OK;
>>>       > }
>>>       >
>>>       > -int mg_http_parse(const char *s, size_t len, struct
>>>      mg_http_message *hm) {
>>>       > - int is_response, req_len = mg_http_get_request_len((unsigned
>>>      char *) s, len);
>>>       > - const char *end = s == NULL ? NULL : s + req_len, *qs; //
>>>      Cannot add to NULL
>>>       > - struct mg_str *cl;
>>>       > +static bool ff_mkdir(const char *path) {
>>>       > + return f_mkdir(path) == FR_OK;
>>>       > +}
>>>       >
>>>       > - memset(hm, 0, sizeof(*hm));
>>>       > - if (req_len <= 0) return req_len;
>>>       > +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close,
>>>      ff_read,
>>>       > + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
>>>       > +#endif
>>>       >
>>>       > - hm->message.ptr = hm->head.ptr = s;
>>>       > - hm->body.ptr = end;
>>>       > - hm->head.len = (size_t) req_len;
>>>       > - hm->chunk.ptr = end;
>>>       > - hm->message.len = hm->body.len = (size_t) ~0; // Set body
>>>      length to infinite
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/fs_packed.c"
>>>       > +#endif
>>>       >
>>>       > - // Parse request line
>>>       > - s = skip(s, end, " ", &hm->method);
>>>       > - s = skip(s, end, " ", &hm->uri);
>>>       > - s = skip(s, end, "\r\n", &hm->proto);
>>>       >
>>>       > - // Sanity check. Allow protocol/reason to be empty
>>>       > - if (hm->method.len == 0 || hm->uri.len == 0) return -1;
>>>       >
>>>       > - // If URI contains '?' character, setup query string
>>>       > - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len))
>>>      != NULL) {
>>>       > - hm->query.ptr = qs + 1;
>>>       > - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
>>>       > - hm->uri.len = (size_t) (qs - hm->uri.ptr);
>>>       > - }
>>>       >
>>>       > - mg_http_parse_headers(s, end, hm->headers,
>>>       > - sizeof(hm->headers) / sizeof(hm->headers[0]));
>>>       > - if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
>>>       > - hm->body.len = (size_t) mg_to64(*cl);
>>>       > - hm->message.len = (size_t) req_len + hm->body.len;
>>>       > - }
>>>       > +struct packed_file {
>>>       > + const char *data;
>>>       > + size_t size;
>>>       > + size_t pos;
>>>       > +};
>>>       >
>>>       > - // mg_http_parse() is used to parse both HTTP requests and HTTP
>>>       > - // responses. If HTTP response does not have Content-Length
>>>      set, then
>>>       > - // body is read until socket is closed, i.e. body.len is
>>>      infinite (~0).
>>>       > - //
>>>       > - // For HTTP requests though, according to
>>>       > - // http://tools.ietf.org/html/rfc7231#section-8.1.3
>>>      <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
>>>       > - // only POST and PUT methods have defined body semantics.
>>>       > - // Therefore, if Content-Length is not specified and methods are
>>>       > - // not one of PUT or POST, set body length to 0.
>>>       > - //
>>>       > - // So, if it is HTTP request, and Content-Length is not set,
>>>       > - // and method is not (PUT or POST) then reset body length to zero.
>>>       > - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
>>>       > - if (hm->body.len == (size_t) ~0 && !is_response &&
>>>       > - mg_vcasecmp(&hm->method, "PUT") != 0 &&
>>>       > - mg_vcasecmp(&hm->method, "POST") != 0) {
>>>       > - hm->body.len = 0;
>>>       > - hm->message.len = (size_t) req_len;
>>>       > - }
>>>       > +#if MG_ENABLE_PACKED_FS
>>>       > +#else
>>>       > +const char *mg_unpack(const char *path, size_t *size, time_t
>>>      *mtime) {
>>>       > + *size = 0, *mtime = 0;
>>>       > + (void) path;
>>>       > + return NULL;
>>>       > +}
>>>       > +const char *mg_unlist(size_t no) {
>>>       > + (void) no;
>>>       > + return NULL;
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > - // The 204 (No content) responses also have 0 body length
>>>       > - if (hm->body.len == (size_t) ~0 && is_response &&
>>>       > - mg_vcasecmp(&hm->uri, "204") == 0) {
>>>       > - hm->body.len = 0;
>>>       > - hm->message.len = (size_t) req_len;
>>>       > - }
>>>       > +struct mg_str mg_unpacked(const char *path) {
>>>       > + size_t len = 0;
>>>       > + const char *buf = mg_unpack(path, &len, NULL);
>>>       > + return mg_str_n(buf, len);
>>>       > +}
>>>       >
>>>       > - return req_len;
>>>       > +static int is_dir_prefix(const char *prefix, size_t n, const
>>>      char *path) {
>>>       > + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
>>>       > + return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
>>>       > + (n == 0 || path[n] == '/' || path[n - 1] == '/');
>>>       > }
>>>       >
>>>       > -static void mg_http_vprintf_chunk(struct mg_connection *c, const
>>>      char *fmt,
>>>       > - va_list *ap) {
>>>       > - size_t len = c->send.len;
>>>       > - mg_send(c, " \r\n", 10);
>>>       > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>>>       > - if (c->send.len >= len + 10) {
>>>       > - mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
>>>      - len - 10);
>>>       > - c->send.buf[len + 8] = '\r';
>>>       > - if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
>>>      reset marker
>>>       > +static int packed_stat(const char *path, size_t *size, time_t
>>>      *mtime) {
>>>       > + const char *p;
>>>       > + size_t i, n = strlen(path);
>>>       > + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular
>>>      file
>>>       > + // Scan all files. If `path` is a dir prefix for any of them,
>>>      it's a dir
>>>       > + for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
>>>       > + if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
>>>       > }
>>>       > - mg_send(c, "\r\n", 2);
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -void mg_http_printf_chunk(struct mg_connection *c, const char
>>>      *fmt, ...) {
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - mg_http_vprintf_chunk(c, fmt, &ap);
>>>       > - va_end(ap);
>>>       > +static void packed_list(const char *dir, void (*fn)(const char
>>>      *, void *),
>>>       > + void *userdata) {
>>>       > + char buf[MG_PATH_MAX], tmp[sizeof(buf)];
>>>       > + const char *path, *begin, *end;
>>>       > + size_t i, n = strlen(dir);
>>>       > + tmp[0] = '\0'; // Previously listed entry
>>>       > + for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
>>>       > + if (!is_dir_prefix(dir, n, path)) continue;
>>>       > + begin = &path[n + 1];
>>>       > + end = strchr(begin, '/');
>>>       > + if (end == NULL) end = begin + strlen(begin);
>>>       > + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
>>>       > + buf[sizeof(buf) - 1] = '\0';
>>>       > + // If this entry has been already listed, skip
>>>       > + // NOTE: we're assuming that file list is sorted alphabetically
>>>       > + if (strcmp(buf, tmp) == 0) continue;
>>>       > + fn(buf, userdata); // Not yet listed, call user function
>>>       > + strcpy(tmp, buf); // And save this entry as listed
>>>       > + }
>>>       > }
>>>       >
>>>       > -void mg_http_write_chunk(struct mg_connection *c, const char
>>>      *buf, size_t len) {
>>>       > - mg_printf(c, "%lx\r\n", (unsigned long) len);
>>>       > - mg_send(c, buf, len);
>>>       > - mg_send(c, "\r\n", 2);
>>>       > - if (len == 0) c->is_resp = 0;
>>>       > +static void *packed_open(const char *path, int flags) {
>>>       > + size_t size = 0;
>>>       > + const char *data = mg_unpack(path, &size, NULL);
>>>       > + struct packed_file *fp = NULL;
>>>       > + if (data == NULL) return NULL;
>>>       > + if (flags & MG_FS_WRITE) return NULL;
>>>       > + if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) !=
>>>      NULL) {
>>>       > + fp->size = size;
>>>       > + fp->data = data;
>>>       > + }
>>>       > + return (void *) fp;
>>>       > }
>>>       >
>>>       > -// clang-format off
>>>       > -static const char *mg_http_status_code_str(int status_code) {
>>>       > - switch (status_code) {
>>>       > - case 100: return "Continue";
>>>       > - case 201: return "Created";
>>>       > - case 202: return "Accepted";
>>>       > - case 204: return "No Content";
>>>       > - case 206: return "Partial Content";
>>>       > - case 301: return "Moved Permanently";
>>>       > - case 302: return "Found";
>>>       > - case 304: return "Not Modified";
>>>       > - case 400: return "Bad Request";
>>>       > - case 401: return "Unauthorized";
>>>       > - case 403: return "Forbidden";
>>>       > - case 404: return "Not Found";
>>>       > - case 418: return "I'm a teapot";
>>>       > - case 500: return "Internal Server Error";
>>>       > - case 501: return "Not Implemented";
>>>       > - default: return "OK";
>>>       > - }
>>>       > +static void packed_close(void *fp) {
>>>       > + if (fp != NULL) free(fp);
>>>       > }
>>>       > -// clang-format on
>>>       >
>>>       > -void mg_http_reply(struct mg_connection *c, int code, const char
>>>      *headers,
>>>       > - const char *fmt, ...) {
>>>       > - va_list ap;
>>>       > - size_t len;
>>>       > - mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
>>>       > - mg_http_status_code_str(code), headers == NULL ? "" : headers);
>>>       > - len = c->send.len;
>>>       > - va_start(ap, fmt);
>>>       > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
>>>       > - va_end(ap);
>>>       > - if (c->send.len > 15) {
>>>       > - mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
>>>       > - (unsigned long) (c->send.len - len));
>>>       > - c->is_resp = 0;
>>>       > - c->send.buf[len - 4] = '\r'; // Change ending 0 to space
>>>       > - }
>>>       > - c->is_resp = 0;
>>>       > +static size_t packed_read(void *fd, void *buf, size_t len) {
>>>       > + struct packed_file *fp = (struct packed_file *) fd;
>>>       > + if (fp->pos + len > fp->size) len = fp->size - fp->pos;
>>>       > + memcpy(buf, &fp->data[fp->pos], len);
>>>       > + fp->pos += len;
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -static void http_cb(struct mg_connection *, int, void *, void *);
>>>       > -static void restore_http_cb(struct mg_connection *c) {
>>>       > - mg_fs_close((struct mg_fd *) c->pfn_data);
>>>       > - c->pfn_data = NULL;
>>>       > - c->pfn = http_cb;
>>>       > - c->is_resp = 0;
>>>       > +static size_t packed_write(void *fd, const void *buf, size_t len) {
>>>       > + (void) fd, (void) buf, (void) len;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>>>      mtime);
>>>       > -char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>>>      mtime) {
>>>       > - mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
>>>      (int64_t) size);
>>>       > - return buf;
>>>       > +static size_t packed_seek(void *fd, size_t offset) {
>>>       > + struct packed_file *fp = (struct packed_file *) fd;
>>>       > + fp->pos = offset;
>>>       > + if (fp->pos > fp->size) fp->pos = fp->size;
>>>       > + return fp->pos;
>>>       > }
>>>       >
>>>       > -static void static_cb(struct mg_connection *c, int ev, void
>>>      *ev_data,
>>>       > - void *fn_data) {
>>>       > - if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
>>>       > - struct mg_fd *fd = (struct mg_fd *) fn_data;
>>>       > - // Read to send IO buffer directly, avoid extra on-stack buffer
>>>       > - size_t n, max = MG_IO_SIZE, space;
>>>       > - size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
>>>      sizeof(size_t)) /
>>>       > - sizeof(size_t) * sizeof(size_t)];
>>>       > - if (c->send.size < max) mg_iobuf_resize(&c->send, max);
>>>       > - if (c->send.len >= c->send.size) return; // Rate limit
>>>       > - if ((space = c->send.size - c->send.len) > *cl) space = *cl;
>>>       > - n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
>>>       > - c->send.len += n;
>>>       > - *cl -= n;
>>>       > - if (n == 0) restore_http_cb(c);
>>>       > - } else if (ev == MG_EV_CLOSE) {
>>>       > - restore_http_cb(c);
>>>       > - }
>>>       > - (void) ev_data;
>>>       > +static bool packed_rename(const char *from, const char *to) {
>>>       > + (void) from, (void) to;
>>>       > + return false;
>>>       > }
>>>       >
>>>       > -// Known mime types. Keep it outside guess_content_type()
>>>      function, since
>>>       > -// some environments don't like it defined there.
>>>       > -// clang-format off
>>>       > -static struct mg_str s_known_types[] = {
>>>       > - MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
>>>       > - MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
>>>       > - MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
>>>       > - MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
>>>       > - MG_C_STR("gif"), MG_C_STR("image/gif"),
>>>       > - MG_C_STR("png"), MG_C_STR("image/png"),
>>>       > - MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
>>>       > - MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
>>>       > - MG_C_STR("woff"), MG_C_STR("font/woff"),
>>>       > - MG_C_STR("ttf"), MG_C_STR("font/ttf"),
>>>       > - MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
>>>       > - MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
>>>       > - MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
>>>       > - MG_C_STR("csv"), MG_C_STR("text/csv"),
>>>       > - MG_C_STR("doc"), MG_C_STR("application/msword"),
>>>       > - MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
>>>       > - MG_C_STR("gz"), MG_C_STR("application/gzip"),
>>>       > - MG_C_STR("ico"), MG_C_STR("image/x-icon"),
>>>       > - MG_C_STR("json"), MG_C_STR("application/json"),
>>>       > - MG_C_STR("mov"), MG_C_STR("video/quicktime"),
>>>       > - MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
>>>       > - MG_C_STR("mp4"), MG_C_STR("video/mp4"),
>>>       > - MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
>>>       > - MG_C_STR("pdf"), MG_C_STR("application/pdf"),
>>>       > - MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
>>>       > - MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
>>>       > - MG_C_STR("wav"), MG_C_STR("audio/wav"),
>>>       > - MG_C_STR("webp"), MG_C_STR("image/webp"),
>>>       > - MG_C_STR("zip"), MG_C_STR("application/zip"),
>>>       > - MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
>>>       > - {0, 0},
>>>       > -};
>>>       > -// clang-format on
>>>       > +static bool packed_remove(const char *path) {
>>>       > + (void) path;
>>>       > + return false;
>>>       > +}
>>>       >
>>>       > -static struct mg_str guess_content_type(struct mg_str path,
>>>      const char *extra) {
>>>       > - struct mg_str k, v, s = mg_str(extra);
>>>       > - size_t i = 0;
>>>       > +static bool packed_mkdir(const char *path) {
>>>       > + (void) path;
>>>       > + return false;
>>>       > +}
>>>       >
>>>       > - // Shrink path to its extension only
>>>       > - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
>>>       > - path.ptr += path.len - i;
>>>       > - path.len = i;
>>>       > +struct mg_fs mg_fs_packed = {
>>>       > + packed_stat, packed_list, packed_open, packed_close, packed_read,
>>>       > + packed_write, packed_seek, packed_rename, packed_remove,
>>>      packed_mkdir};
>>>       >
>>>       > - // Process user-provided mime type overrides, if any
>>>       > - while (mg_commalist(&s, &k, &v)) {
>>>       > - if (mg_strcmp(path, k) == 0) return v;
>>>       > - }
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/fs_posix.c"
>>>       > +#endif
>>>       >
>>>       > - // Process built-in mime types
>>>       > - for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
>>>       > - if (mg_strcmp(path, s_known_types[i]) == 0) return
>>>      s_known_types[i + 1];
>>>       > - }
>>>       >
>>>       > - return mg_str("text/plain; charset=utf-8");
>>>       > -}
>>>       > +#if MG_ENABLE_POSIX_FS
>>>       >
>>>       > -static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
>>>       > - size_t i, numparsed = 0;
>>>       > - // MG_INFO(("%.*s", (int) s->len, s->ptr));
>>>       > - for (i = 0; i + 6 < s->len; i++) {
>>>       > - if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
>>>       > - struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
>>>       > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
>>>       > - *a = mg_to64(p);
>>>       > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
>>>       > - while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++,
>>>      p.len--;
>>>       > - if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
>>>       > - *b = mg_to64(p);
>>>       > - if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
>>>       > - // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
>>>       > - break;
>>>       > - }
>>>       > - }
>>>       > - return (int) numparsed;
>>>       > -}
>>>       > +#ifndef MG_STAT_STRUCT
>>>       > +#define MG_STAT_STRUCT stat
>>>       > +#endif
>>>       >
>>>       > -void mg_http_serve_file(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - const char *path,
>>>       > - const struct mg_http_serve_opts *opts) {
>>>       > - char etag[64], tmp[MG_PATH_MAX];
>>>       > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > - struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path,
>>>      MG_FS_READ);
>>>       > - size_t size = 0;
>>>       > - time_t mtime = 0;
>>>       > - struct mg_str *inm = NULL;
>>>       > - struct mg_str mime = guess_content_type(mg_str(path),
>>>      opts->mime_types);
>>>       > - bool gzip = false;
>>>       > +#ifndef MG_STAT_FUNC
>>>       > +#define MG_STAT_FUNC stat
>>>       > +#endif
>>>       >
>>>       > - // If file does not exist, we try to open file PATH.gz - and if
>>>      such
>>>       > - // pre-compressed .gz file exists, serve it with the
>>>      Content-Encoding: gzip
>>>       > - // Note - we ignore Accept-Encoding, cause we don't have a choice
>>>       > - if (fd == NULL) {
>>>       > - MG_DEBUG(("NULL [%s]", path));
>>>       > - mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
>>>       > - if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
>>>       > - gzip = true;
>>>       > - path = tmp;
>>>       > - } else if (opts->page404 != NULL) {
>>>       > - // No precompressed file, serve 404
>>>       > - fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
>>>       > - mime = guess_content_type(mg_str(path), opts->mime_types);
>>>       > - path = opts->page404;
>>>       > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > +#if !defined(S_ISDIR)
>>>       > + MG_ERROR(("stat() API is not supported. %p %p %p", path, size,
>>>      mtime));
>>>       > + return 0;
>>>       > +#else
>>>       > +#if MG_ARCH == MG_ARCH_WIN32
>>>       > + struct _stati64 st;
>>>       > + wchar_t tmp[MG_PATH_MAX];
>>>       > + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) /
>>>      sizeof(tmp[0]));
>>>       > + if (_wstati64(tmp, &st) != 0) return 0;
>>>       > + // If path is a symlink, windows reports 0 in st.st_size.
>>>       > + // Get a real file size by opening it and jumping to the end
>>>       > + if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
>>>       > + FILE *fp = _wfopen(tmp, L"rb");
>>>       > + if (fp != NULL) {
>>>       > + fseek(fp, 0, SEEK_END);
>>>       > + if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on
>>>      win10+
>>>       > + fclose(fp);
>>>       > }
>>>       > }
>>>       > +#else
>>>       > + struct MG_STAT_STRUCT st;
>>>       > + if (MG_STAT_FUNC(path, &st) != 0) return 0;
>>>       > +#endif
>>>       > + if (size) *size = (size_t) st.st_size;
>>>       > + if (mtime) *mtime = st.st_mtime;
>>>       > + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ?
>>>      MG_FS_DIR : 0);
>>>       > +#endif
>>>       > +}
>>>       >
>>>       > - if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
>>>       > - mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
>>>       > - mg_fs_close(fd);
>>>       > - // NOTE: mg_http_etag() call should go first!
>>>       > - } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
>>>      NULL &&
>>>       > - (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
>>>       > - mg_vcasecmp(inm, etag) == 0) {
>>>       > - mg_fs_close(fd);
>>>       > - mg_http_reply(c, 304, opts->extra_headers, "");
>>>       > - } else {
>>>       > - int n, status = 200;
>>>       > - char range[100];
>>>       > - int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
>>>       > +#if MG_ARCH == MG_ARCH_WIN32
>>>       > +struct dirent {
>>>       > + char d_name[MAX_PATH];
>>>       > +};
>>>       >
>>>       > - // Handle Range header
>>>       > - struct mg_str *rh = mg_http_get_header(hm, "Range");
>>>       > - range[0] = '\0';
>>>       > - if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0
>>>      && r2 >= 0) {
>>>       > - // If range is specified like "400-", set second limit to
>>>      content len
>>>       > - if (n == 1) r2 = cl - 1;
>>>       > - if (r1 > r2 || r2 >= cl) {
>>>       > - status = 416;
>>>       > - cl = 0;
>>>       > - mg_snprintf(range, sizeof(range), "Content-Range: bytes
>>>      */%lld\r\n",
>>>       > - (int64_t) size);
>>>       > - } else {
>>>       > - status = 206;
>>>       > - cl = r2 - r1 + 1;
>>>       > - mg_snprintf(range, sizeof(range),
>>>       > - "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
>>>       > - (int64_t) size);
>>>       > - fs->sk(fd->fd, (size_t) r1);
>>>       > - }
>>>       > - }
>>>       > - mg_printf(c,
>>>       > - "HTTP/1.1 %d %s\r\n"
>>>       > - "Content-Type: %.*s\r\n"
>>>       > - "Etag: %s\r\n"
>>>       > - "Content-Length: %llu\r\n"
>>>       > - "%s%s%s\r\n",
>>>       > - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
>>>       > - etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
>>>       > - opts->extra_headers ? opts->extra_headers : "");
>>>       > - if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
>>>       > - c->is_draining = 1;
>>>       > - c->is_resp = 0;
>>>       > - mg_fs_close(fd);
>>>       > - } else {
>>>       > - // Track to-be-sent content length at the end of c->data, aligned
>>>       > - size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
>>>      sizeof(size_t)) /
>>>       > - sizeof(size_t) * sizeof(size_t)];
>>>       > - c->pfn = static_cb;
>>>       > - c->pfn_data = fd;
>>>       > - *clp = (size_t) cl;
>>>       > - }
>>>       > +typedef struct win32_dir {
>>>       > + HANDLE handle;
>>>       > + WIN32_FIND_DATAW info;
>>>       > + struct dirent result;
>>>       > +} DIR;
>>>       > +
>>>       > +#if 0
>>>       > +int gettimeofday(struct timeval *tv, void *tz) {
>>>       > + FILETIME ft;
>>>       > + unsigned __int64 tmpres = 0;
>>>       > +
>>>       > + if (tv != NULL) {
>>>       > + GetSystemTimeAsFileTime(&ft);
>>>       > + tmpres |= ft.dwHighDateTime;
>>>       > + tmpres <<= 32;
>>>       > + tmpres |= ft.dwLowDateTime;
>>>       > + tmpres /= 10; // convert into microseconds
>>>       > + tmpres -= (int64_t) 11644473600000000;
>>>       > + tv->tv_sec = (long) (tmpres / 1000000UL);
>>>       > + tv->tv_usec = (long) (tmpres % 1000000UL);
>>>       > }
>>>       > + (void) tz;
>>>       > + return 0;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -struct printdirentrydata {
>>>       > - struct mg_connection *c;
>>>       > - struct mg_http_message *hm;
>>>       > - const struct mg_http_serve_opts *opts;
>>>       > - const char *dir;
>>>       > -};
>>>       > +static int to_wchar(const char *path, wchar_t *wbuf, size_t
>>>      wbuf_len) {
>>>       > + int ret;
>>>       > + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
>>>       > + strncpy(buf, path, sizeof(buf));
>>>       > + buf[sizeof(buf) - 1] = '\0';
>>>       > + // Trim trailing slashes. Leave backslash for paths like "X:\"
>>>       > + p = buf + strlen(buf) - 1;
>>>       > + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] ==
>>>      '/')) *p-- = '\0';
>>>       > + memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
>>>       > + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)
>>>      wbuf_len);
>>>       > + // Convert back to Unicode. If doubly-converted string does not
>>>      match the
>>>       > + // original, something is fishy, reject.
>>>       > + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2,
>>>      sizeof(buf2),
>>>       > + NULL, NULL);
>>>       > + if (strcmp(buf, buf2) != 0) {
>>>       > + wbuf[0] = L'\0';
>>>       > + ret = 0;
>>>       > + }
>>>       > + return ret;
>>>       > +}
>>>       >
>>>       > -static void printdirentry(const char *name, void *userdata) {
>>>       > - struct printdirentrydata *d = (struct printdirentrydata *)
>>>      userdata;
>>>       > - struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
>>>      d->opts->fs;
>>>       > - size_t size = 0;
>>>       > - time_t t = 0;
>>>       > - char path[MG_PATH_MAX], sz[40], mod[40];
>>>       > - int flags, n = 0;
>>>       > +DIR *opendir(const char *name) {
>>>       > + DIR *d = NULL;
>>>       > + wchar_t wpath[MAX_PATH];
>>>       > + DWORD attrs;
>>>       >
>>>       > - // MG_DEBUG(("[%s] [%s]", d->dir, name));
>>>       > - if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
>>>       > - sizeof(path)) {
>>>       > - MG_ERROR(("%s truncated", name));
>>>       > - } else if ((flags = fs->st(path, &size, &t)) == 0) {
>>>       > - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
>>>       > + if (name == NULL) {
>>>       > + SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
>>>       > + SetLastError(ERROR_NOT_ENOUGH_MEMORY);
>>>       > } else {
>>>       > - const char *slash = flags & MG_FS_DIR ? "/" : "";
>>>       > - if (flags & MG_FS_DIR) {
>>>       > - mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
>>>       > + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
>>>       > + attrs = GetFileAttributesW(wpath);
>>>       > + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
>>>       > + (void) wcscat(wpath, L"\\*");
>>>       > + d->handle = FindFirstFileW(wpath, &d->info);
>>>       > + d->result.d_name[0] = '\0';
>>>       > } else {
>>>       > - mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
>>>       > + free(d);
>>>       > + d = NULL;
>>>       > }
>>>       > -#if defined(MG_HTTP_DIRLIST_TIME)
>>>       > - char time_str[30];
>>>       > - struct tm *time_info = localtime(&t);
>>>       > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>>>      time_info);
>>>       > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
>>>       > -#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
>>>       > - char time_str[30];
>>>       > - struct tm *time_info = gmtime(&t);
>>>       > - strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>>>      time_info);
>>>       > - mg_snprintf(mod, sizeof(mod), "%s", time_str);
>>>       > -#else
>>>       > - mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
>>>       > -#endif
>>>       > - n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
>>>       > - mg_printf(d->c,
>>>       > - " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
>>>       > - "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
>>>       > - n, path, slash, name, slash, (unsigned long) t, mod,
>>>       > - flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
>>>       > }
>>>       > + return d;
>>>       > }
>>>       >
>>>       > -static void listdir(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - const struct mg_http_serve_opts *opts, char *dir) {
>>>       > - const char *sort_js_code =
>>>       > - "<script>function srt(tb, sc, so, d) {"
>>>       > - "var tr = Array.prototype.slice.call(tb.rows, 0),"
>>>       > - "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
>>>      b.cells[sc],"
>>>       > - "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
>>>       > - "t1 = a.cells[2].getAttribute('name'), "
>>>       > - "t2 = b.cells[2].getAttribute('name'); "
>>>       > - "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
>>>       > - "n1 ? parseInt(n2) - parseInt(n1) : "
>>>       > - "c1.textContent.trim().localeCompare(c2.textContent.trim()));
>>>      });";
>>>       > - const char *sort_js_code2 =
>>>       > - "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
>>>       > - "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
>>>       > - "};"
>>>       > - "window.onload = function() {"
>>>       > - "var tb = document.getElementById('tb');"
>>>       > - "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
>>>      [0, 2, 1];"
>>>       > - "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
>>>       > - "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
>>>      c, so); "
>>>       > - "sc = c; ev.preventDefault();}};"
>>>       > - "srt(tb, sc, so, true);"
>>>       > - "}"
>>>       > - "</script>";
>>>       > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > - struct printdirentrydata d = {c, hm, opts, dir};
>>>       > - char tmp[10], buf[MG_PATH_MAX];
>>>       > - size_t off, n;
>>>       > - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf,
>>>      sizeof(buf), 0);
>>>       > - struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
>>>      hm->uri;
>>>       > -
>>>       > - mg_printf(c,
>>>       > - "HTTP/1.1 200 OK\r\n"
>>>       > - "Content-Type: text/html; charset=utf-8\r\n"
>>>       > - "%s"
>>>       > - "Content-Length: \r\n\r\n",
>>>       > - opts->extra_headers == NULL ? "" : opts->extra_headers);
>>>       > - off = c->send.len; // Start of body
>>>       > - mg_printf(c,
>>>       > - "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
>>>       > - "<style>th,td {text-align: left; padding-right: 1em; "
>>>       > - "font-family: monospace; }</style></head>"
>>>       > - "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
>>>       > - "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
>>>       > - "<a href=\"#\" rel=\"1\">Modified</a></th>"
>>>       > - "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
>>>       > - "<tr><td colspan=\"3\"><hr></td></tr>"
>>>       > - "</thead>"
>>>       > - "<tbody id=\"tb\">\n",
>>>       > - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int)
>>>      uri.len,
>>>       > - uri.ptr);
>>>       > - mg_printf(c, "%s",
>>>       > - " <tr><td><a href=\"..\">..</a></td>"
>>>       > - "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
>>>       > -
>>>       > - fs->ls(dir, printdirentry, &d);
>>>       > - mg_printf(c,
>>>       > - "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
>>>       > - "</table><address>Mongoose v.%s</address></body></html>\n",
>>>       > - MG_VERSION);
>>>       > - n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
>>>      (c->send.len - off));
>>>       > - if (n > sizeof(tmp)) n = 0;
>>>       > - memcpy(c->send.buf + off - 12, tmp, n); // Set content length
>>>       > - c->is_resp = 0; // Mark response end
>>>       > +int closedir(DIR *d) {
>>>       > + int result = 0;
>>>       > + if (d != NULL) {
>>>       > + if (d->handle != INVALID_HANDLE_VALUE)
>>>       > + result = FindClose(d->handle) ? 0 : -1;
>>>       > + free(d);
>>>       > + } else {
>>>       > + result = -1;
>>>       > + SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > + }
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -// Resolve requested file into `path` and return its fs->st()
>>>      result
>>>       > -static int uri_to_path2(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - struct mg_fs *fs, struct mg_str url, struct mg_str dir,
>>>       > - char *path, size_t path_size) {
>>>       > - int flags, tmp;
>>>       > - // Append URI to the root_dir, and sanitize it
>>>       > - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
>>>      dir.ptr);
>>>       > - if (n > path_size) n = path_size;
>>>       > - path[path_size - 1] = '\0';
>>>       > - if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
>>>       > - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len,
>>>      path + n,
>>>       > - path_size - n, 0);
>>>       > - path[path_size - 1] = '\0'; // Double-check
>>>       > - mg_remove_double_dots(path);
>>>       > - n = strlen(path);
>>>       > - while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
>>>      trailing slashes
>>>       > - flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path,
>>>      NULL, NULL);
>>>       > - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
>>>      hm->uri.ptr, path,
>>>       > - flags));
>>>       > - if (flags == 0) {
>>>       > - // Do nothing - let's caller decide
>>>       > - } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
>>>       > - hm->uri.ptr[hm->uri.len - 1] != '/') {
>>>       > - mg_printf(c,
>>>       > - "HTTP/1.1 301 Moved\r\n"
>>>       > - "Location: %.*s/\r\n"
>>>       > - "Content-Length: 0\r\n"
>>>       > +struct dirent *readdir(DIR *d) {
>>>       > + struct dirent *result = NULL;
>>>       > + if (d != NULL) {
>>>       > + memset(&d->result, 0, sizeof(d->result));
>>>       > + if (d->handle != INVALID_HANDLE_VALUE) {
>>>       > + result = &d->result;
>>>       > + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1,
>>>      result->d_name,
>>>       > + sizeof(result->d_name), NULL, NULL);
>>>       > + if (!FindNextFileW(d->handle, &d->info)) {
>>>       > + FindClose(d->handle);
>>>       > + d->handle = INVALID_HANDLE_VALUE;
>>>       > + }
>>>       > + } else {
>>>       > + SetLastError(ERROR_FILE_NOT_FOUND);
>>>       > + }
>>>       > + } else {
>>>       > + SetLastError(ERROR_BAD_ARGUMENTS);
>>>       > + }
>>>       > + return result;
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +static void p_list(const char *dir, void (*fn)(const char *,
>>>      void *),
>>>       > + void *userdata) {
>>>       > +#if MG_ENABLE_DIRLIST
>>>       > + struct dirent *dp;
>>>       > + DIR *dirp;
>>>       > + if ((dirp = (opendir(dir))) == NULL) return;
>>>       > + while ((dp = readdir(dirp)) != NULL) {
>>>       > + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
>>>      continue;
>>>       > + fn(dp->d_name, userdata);
>>>       > + }
>>>       > + closedir(dirp);
>>>       > +#else
>>>       > + (void) dir, (void) fn, (void) userdata;
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +static void *p_open(const char *path, int flags) {
>>>       > +#if MG_ARCH == MG_ARCH_WIN32
>>>       > + const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
>>>       > + wchar_t b1[MG_PATH_MAX], b2[10];
>>>       > + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) /
>>>      sizeof(b1[0]));
>>>       > + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) /
>>>      sizeof(b2[0]));
>>>       > + return (void *) _wfopen(b1, b2);
>>>       > +#else
>>>       > + const char *mode = flags == MG_FS_READ ? "rbe" : "a+be"; // e
>>>      for CLOEXEC
>>>       > + return (void *) fopen(path, mode);
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +static void p_close(void *fp) {
>>>       > + fclose((FILE *) fp);
>>>       > +}
>>>       > +
>>>       > +static size_t p_read(void *fp, void *buf, size_t len) {
>>>       > + return fread(buf, 1, len, (FILE *) fp);
>>>       > +}
>>>       > +
>>>       > +static size_t p_write(void *fp, const void *buf, size_t len) {
>>>       > + return fwrite(buf, 1, len, (FILE *) fp);
>>>       > +}
>>>       > +
>>>       > +static size_t p_seek(void *fp, size_t offset) {
>>>       > +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
>>>       > + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
>>>       > + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
>>>       > + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
>>>       > +#else
>>>       > + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
>>>       > +#endif
>>>       > + return (size_t) ftell((FILE *) fp);
>>>       > +}
>>>       > +
>>>       > +static bool p_rename(const char *from, const char *to) {
>>>       > + return rename(from, to) == 0;
>>>       > +}
>>>       > +
>>>       > +static bool p_remove(const char *path) {
>>>       > + return remove(path) == 0;
>>>       > +}
>>>       > +
>>>       > +static bool p_mkdir(const char *path) {
>>>       > + return mkdir(path, 0775) == 0;
>>>       > +}
>>>       > +
>>>       > +#else
>>>       > +
>>>       > +static int p_stat(const char *path, size_t *size, time_t *mtime) {
>>>       > + (void) path, (void) size, (void) mtime;
>>>       > + return 0;
>>>       > +}
>>>       > +static void p_list(const char *path, void (*fn)(const char *,
>>>      void *),
>>>       > + void *userdata) {
>>>       > + (void) path, (void) fn, (void) userdata;
>>>       > +}
>>>       > +static void *p_open(const char *path, int flags) {
>>>       > + (void) path, (void) flags;
>>>       > + return NULL;
>>>       > +}
>>>       > +static void p_close(void *fp) {
>>>       > + (void) fp;
>>>       > +}
>>>       > +static size_t p_read(void *fd, void *buf, size_t len) {
>>>       > + (void) fd, (void) buf, (void) len;
>>>       > + return 0;
>>>       > +}
>>>       > +static size_t p_write(void *fd, const void *buf, size_t len) {
>>>       > + (void) fd, (void) buf, (void) len;
>>>       > + return 0;
>>>       > +}
>>>       > +static size_t p_seek(void *fd, size_t offset) {
>>>       > + (void) fd, (void) offset;
>>>       > + return (size_t) ~0;
>>>       > +}
>>>       > +static bool p_rename(const char *from, const char *to) {
>>>       > + (void) from, (void) to;
>>>       > + return false;
>>>       > +}
>>>       > +static bool p_remove(const char *path) {
>>>       > + (void) path;
>>>       > + return false;
>>>       > +}
>>>       > +static bool p_mkdir(const char *path) {
>>>       > + (void) path;
>>>       > + return false;
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
>>>      p_read,
>>>       > + p_write, p_seek, p_rename, p_remove, p_mkdir};
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/http.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +static int mg_ncasecmp(const char *s1, const char *s2, size_t
>>>      len) {
>>>       > + int diff = 0;
>>>       > + if (len > 0) do {
>>>       > + int c = *s1++, d = *s2++;
>>>       > + if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
>>>       > + if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
>>>       > + diff = c - d;
>>>       > + } while (diff == 0 && s1[-1] != '\0' && --len > 0);
>>>       > + return diff;
>>>       > +}
>>>       > +
>>>       > +bool mg_to_size_t(struct mg_str str, size_t *val);
>>>       > +bool mg_to_size_t(struct mg_str str, size_t *val) {
>>>       > + size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0,
>>>      ndigits = 0;
>>>       > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
>>>      '\t')) i++;
>>>       > + if (i < str.len && str.buf[i] == '-') return false;
>>>       > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
>>>       > + size_t digit = (size_t) (str.buf[i] - '0');
>>>       > + if (result > max2) return false; // Overflow
>>>       > + result *= 10;
>>>       > + if (result > max - digit) return false; // Overflow
>>>       > + result += digit;
>>>       > + i++, ndigits++;
>>>       > + }
>>>       > + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] ==
>>>      '\t')) i++;
>>>       > + if (ndigits == 0) return false; // #2322: Content-Length = 1 *
>>>      DIGIT
>>>       > + if (i != str.len) return false; // Ditto
>>>       > + *val = (size_t) result;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +// Chunk deletion marker is the MSB in the "processed" counter
>>>       > +#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
>>>       > +
>>>       > +// Multipart POST example:
>>>       > +// --xyz
>>>       > +// Content-Disposition: form-data; name="val"
>>>       > +//
>>>       > +// abcdef
>>>       > +// --xyz
>>>       > +// Content-Disposition: form-data; name="foo"; filename="a.txt"
>>>       > +// Content-Type: text/plain
>>>       > +//
>>>       > +// hello world
>>>       > +//
>>>       > +// --xyz--
>>>       > +size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
>>>       > + struct mg_http_part *part) {
>>>       > + struct mg_str cd = mg_str_n("Content-Disposition", 19);
>>>       > + const char *s = body.buf;
>>>       > + size_t b = ofs, h1, h2, b1, b2, max = body.len;
>>>       > +
>>>       > + // Init part params
>>>       > + if (part != NULL) part->name = part->filename = part->body =
>>>      mg_str_n(0, 0);
>>>       > +
>>>       > + // Skip boundary
>>>       > + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
>>>       > + if (b <= ofs || b + 2 >= max) return 0;
>>>       > + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs),
>>>      s));
>>>       > +
>>>       > + // Skip headers
>>>       > + h1 = h2 = b + 2;
>>>       > + for (;;) {
>>>       > + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
>>>       > + if (h2 == h1) break;
>>>       > + if (h2 + 2 >= max) return 0;
>>>       > + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
>>>       > + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] ==
>>>      ':' &&
>>>       > + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
>>>       > + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 +
>>>      cd.len + 2));
>>>       > + part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
>>>       > + part->filename = mg_http_get_header_var(v, mg_str_n("filename",
>>>      8));
>>>       > + }
>>>       > + h1 = h2 = h2 + 2;
>>>       > + }
>>>       > + b1 = b2 = h2 + 2;
>>>       > + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2
>>>      + 1] == '\n' &&
>>>       > + memcmp(&s[b2 + 2], s, b - ofs) == 0))
>>>       > + b2++;
>>>       > +
>>>       > + if (b2 + 2 >= max) return 0;
>>>       > + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
>>>       > + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
>>>       > + return b2 + 2;
>>>       > +}
>>>       > +
>>>       > +void mg_http_bauth(struct mg_connection *c, const char *user,
>>>       > + const char *pass) {
>>>       > + struct mg_str u = mg_str(user), p = mg_str(pass);
>>>       > + size_t need = c->send.len + 36 + (u.len + p.len) * 2;
>>>       > + if (c->send.size < need) mg_iobuf_resize(&c->send, need);
>>>       > + if (c->send.size >= need) {
>>>       > + size_t i, n = 0;
>>>       > + char *buf = (char *) &c->send.buf[c->send.len];
>>>       > + memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send!
>>>       > + for (i = 0; i < u.len; i++) {
>>>       > + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
>>>       > + }
>>>       > + if (p.len > 0) {
>>>       > + n = mg_base64_update(':', buf + 21, n);
>>>       > + for (i = 0; i < p.len; i++) {
>>>       > + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
>>>       > + }
>>>       > + }
>>>       > + n = mg_base64_final(buf + 21, n);
>>>       > + c->send.len += 21 + (size_t) n + 2;
>>>       > + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
>>>       > + } else {
>>>       > + MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int)
>>>      need));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
>>>       > + struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
>>>       > + while (mg_span(buf, &entry, &buf, '&')) {
>>>       > + if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
>>>       > + mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
>>>       > + result = v;
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + return result;
>>>       > +}
>>>       > +
>>>       > +int mg_http_get_var(const struct mg_str *buf, const char *name,
>>>      char *dst,
>>>       > + size_t dst_len) {
>>>       > + int len;
>>>       > + if (dst != NULL && dst_len > 0) {
>>>       > + dst[0] = '\0'; // If destination buffer is valid, always
>>>      nul-terminate it
>>>       > + }
>>>       > + if (dst == NULL || dst_len == 0) {
>>>       > + len = -2; // Bad destination
>>>       > + } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
>>>       > + len = -1; // Bad source
>>>       > + } else {
>>>       > + struct mg_str v = mg_http_var(*buf, mg_str(name));
>>>       > + if (v.buf == NULL) {
>>>       > + len = -4; // Name does not exist
>>>       > + } else {
>>>       > + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
>>>       > + if (len < 0) len = -3; // Failed to decode
>>>       > + }
>>>       > + }
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static bool isx(int c) {
>>>       > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>>>       > + (c >= 'A' && c <= 'F');
>>>       > +}
>>>       > +
>>>       > +int mg_url_decode(const char *src, size_t src_len, char *dst,
>>>      size_t dst_len,
>>>       > + int is_form_url_encoded) {
>>>       > + size_t i, j;
>>>       > + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
>>>       > + if (src[i] == '%') {
>>>       > + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small
>>>      src_len
>>>       > + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
>>>       > + mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j],
>>>      sizeof(uint8_t));
>>>       > + i += 2;
>>>       > + } else {
>>>       > + return -1;
>>>       > + }
>>>       > + } else if (is_form_url_encoded && src[i] == '+') {
>>>       > + dst[j] = ' ';
>>>       > + } else {
>>>       > + dst[j] = src[i];
>>>       > + }
>>>       > + }
>>>       > + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
>>>       > + return i >= src_len && j < dst_len ? (int) j : -1;
>>>       > +}
>>>       > +
>>>       > +static bool isok(uint8_t c) {
>>>       > + return c == '\n' || c == '\r' || c >= ' ';
>>>       > +}
>>>       > +
>>>       > +int mg_http_get_request_len(const unsigned char *buf, size_t
>>>      buf_len) {
>>>       > + size_t i;
>>>       > + for (i = 0; i < buf_len; i++) {
>>>       > + if (!isok(buf[i])) return -1;
>>>       > + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
>>>       > + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] ==
>>>      '\n'))
>>>       > + return (int) i + 1;
>>>       > + }
>>>       > + return 0;
>>>       > +}
>>>       > +struct mg_str *mg_http_get_header(struct mg_http_message *h,
>>>      const char *name) {
>>>       > + size_t i, n = strlen(name), max = sizeof(h->headers) /
>>>      sizeof(h->headers[0]);
>>>       > + for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
>>>       > + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
>>>       > + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
>>>       > + }
>>>       > + return NULL;
>>>       > +}
>>>       > +
>>>       > +// Is it a valid utf-8 continuation byte
>>>       > +static bool vcb(uint8_t c) {
>>>       > + return (c & 0xc0) == 0x80;
>>>       > +}
>>>       > +
>>>       > +// Get character length (valid utf-8). Used to parse method,
>>>      URI, headers
>>>       > +static size_t clen(const char *s, const char *end) {
>>>       > + const unsigned char *u = (unsigned char *) s, c = *u;
>>>       > + long n = (long) (end - s);
>>>       > + if (c > ' ' && c < '~') return 1; // Usual ascii printed char
>>>       > + if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; //
>>>      2-byte UTF8
>>>       > + if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2]))
>>>      return 3;
>>>       > + if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) &&
>>>      vcb(u[3]))
>>>       > + return 4;
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +// Skip until the newline. Return advanced `s`, or NULL on error
>>>       > +static const char *skiptorn(const char *s, const char *end,
>>>      struct mg_str *v) {
>>>       > + v->buf = (char *) s;
>>>       > + while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;
>>>      // To newline
>>>       > + if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; //
>>>      Stray \r
>>>       > + if (s < end && s[0] == '\r') s++; // Skip \r
>>>       > + if (s >= end || *s++ != '\n') return NULL; // Skip \n
>>>       > + return s;
>>>       > +}
>>>       > +
>>>       > +static bool mg_http_parse_headers(const char *s, const char *end,
>>>       > + struct mg_http_header *h, size_t max_hdrs) {
>>>       > + size_t i, n;
>>>       > + for (i = 0; i < max_hdrs; i++) {
>>>       > + struct mg_str k = {NULL, 0}, v = {NULL, 0};
>>>       > + if (s >= end) return false;
>>>       > + if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
>>>       > + k.buf = (char *) s;
>>>       > + while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s +=
>>>      n, k.len += n;
>>>       > + if (k.len == 0) return false; // Empty name
>>>       > + if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8
>>>       > + if (*s++ != ':') return false; // Invalid, not followed by :
>>>       > + // if (clen(s, end) == 0) return false; // Invalid UTF-8
>>>       > + while (s < end && s[0] == ' ') s++; // Skip spaces
>>>       > + if ((s = skiptorn(s, end, &v)) == NULL) return false;
>>>       > + while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim
>>>      spaces
>>>       > + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int)
>>>      v.len, v.buf));
>>>       > + h[i].name = k, h[i].value = v; // Success. Assign values
>>>       > + }
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +int mg_http_parse(const char *s, size_t len, struct
>>>      mg_http_message *hm) {
>>>       > + int is_response, req_len = mg_http_get_request_len((unsigned
>>>      char *) s, len);
>>>       > + const char *end = s == NULL ? NULL : s + req_len, *qs; //
>>>      Cannot add to NULL
>>>       > + const struct mg_str *cl;
>>>       > + size_t n;
>>>       > +
>>>       > + memset(hm, 0, sizeof(*hm));
>>>       > + if (req_len <= 0) return req_len;
>>>       > +
>>>       > + hm->message.buf = hm->head.buf = (char *) s;
>>>       > + hm->body.buf = (char *) end;
>>>       > + hm->head.len = (size_t) req_len;
>>>       > + hm->message.len = hm->body.len = (size_t) -1; // Set body
>>>      length to infinite
>>>       > +
>>>       > + // Parse request line
>>>       > + hm->method.buf = (char *) s;
>>>       > + while (s < end && (n = clen(s, end)) > 0) s += n,
>>>      hm->method.len += n;
>>>       > + while (s < end && s[0] == ' ') s++; // Skip spaces
>>>       > + hm->uri.buf = (char *) s;
>>>       > + while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len
>>>      += n;
>>>       > + while (s < end && s[0] == ' ') s++; // Skip spaces
>>>       > + if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
>>>       > +
>>>       > + // If URI contains '?' character, setup query string
>>>       > + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len))
>>>      != NULL) {
>>>       > + hm->query.buf = (char *) qs + 1;
>>>       > + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
>>>       > + hm->uri.len = (size_t) (qs - hm->uri.buf);
>>>       > + }
>>>       > +
>>>       > + // Sanity check. Allow protocol/reason to be empty
>>>       > + // Do this check after hm->method.len and hm->uri.len are
>>>      finalised
>>>       > + if (hm->method.len == 0 || hm->uri.len == 0) return -1;
>>>       > +
>>>       > + if (!mg_http_parse_headers(s, end, hm->headers,
>>>       > + sizeof(hm->headers) / sizeof(hm->headers[0])))
>>>       > + return -1; // error when parsing
>>>       > + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
>>>       > + if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
>>>       > + hm->message.len = (size_t) req_len + hm->body.len;
>>>       > + }
>>>       > +
>>>       > + // mg_http_parse() is used to parse both HTTP requests and HTTP
>>>       > + // responses. If HTTP response does not have Content-Length
>>>      set, then
>>>       > + // body is read until socket is closed, i.e. body.len is
>>>      infinite (~0).
>>>       > + //
>>>       > + // For HTTP requests though, according to
>>>       > + // http://tools.ietf.org/html/rfc7231#section-8.1.3
>>>      <http://tools.ietf.org/html/rfc7231#section-8.1.3>,
>>>       > + // only POST and PUT methods have defined body semantics.
>>>       > + // Therefore, if Content-Length is not specified and methods are
>>>       > + // not one of PUT or POST, set body length to 0.
>>>       > + //
>>>       > + // So, if it is HTTP request, and Content-Length is not set,
>>>       > + // and method is not (PUT or POST) then reset body length to zero.
>>>       > + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
>>>       > + if (hm->body.len == (size_t) ~0 && !is_response &&
>>>       > + mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
>>>       > + mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
>>>       > + hm->body.len = 0;
>>>       > + hm->message.len = (size_t) req_len;
>>>       > + }
>>>       > +
>>>       > + // The 204 (No content) responses also have 0 body length
>>>       > + if (hm->body.len == (size_t) ~0 && is_response &&
>>>       > + mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
>>>       > + hm->body.len = 0;
>>>       > + hm->message.len = (size_t) req_len;
>>>       > + }
>>>       > + if (hm->message.len < (size_t) req_len) return -1; // Overflow
>>>      protection
>>>       > +
>>>       > + return req_len;
>>>       > +}
>>>       > +
>>>       > +static void mg_http_vprintf_chunk(struct mg_connection *c, const
>>>      char *fmt,
>>>       > + va_list *ap) {
>>>       > + size_t len = c->send.len;
>>>       > + mg_send(c, " \r\n", 10);
>>>       > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>>>       > + if (c->send.len >= len + 10) {
>>>       > + mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len
>>>      - len - 10);
>>>       > + c->send.buf[len + 8] = '\r';
>>>       > + if (c->send.len == len + 10) c->is_resp = 0; // Last chunk,
>>>      reset marker
>>>       > + }
>>>       > + mg_send(c, "\r\n", 2);
>>>       > +}
>>>       > +
>>>       > +void mg_http_printf_chunk(struct mg_connection *c, const char
>>>      *fmt, ...) {
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + mg_http_vprintf_chunk(c, fmt, &ap);
>>>       > + va_end(ap);
>>>       > +}
>>>       > +
>>>       > +void mg_http_write_chunk(struct mg_connection *c, const char
>>>      *buf, size_t len) {
>>>       > + mg_printf(c, "%lx\r\n", (unsigned long) len);
>>>       > + mg_send(c, buf, len);
>>>       > + mg_send(c, "\r\n", 2);
>>>       > + if (len == 0) c->is_resp = 0;
>>>       > +}
>>>       > +
>>>       > +// clang-format off
>>>       > +static const char *mg_http_status_code_str(int status_code) {
>>>       > + switch (status_code) {
>>>       > + case 100: return "Continue";
>>>       > + case 101: return "Switching Protocols";
>>>       > + case 102: return "Processing";
>>>       > + case 200: return "OK";
>>>       > + case 201: return "Created";
>>>       > + case 202: return "Accepted";
>>>       > + case 203: return "Non-authoritative Information";
>>>       > + case 204: return "No Content";
>>>       > + case 205: return "Reset Content";
>>>       > + case 206: return "Partial Content";
>>>       > + case 207: return "Multi-Status";
>>>       > + case 208: return "Already Reported";
>>>       > + case 226: return "IM Used";
>>>       > + case 300: return "Multiple Choices";
>>>       > + case 301: return "Moved Permanently";
>>>       > + case 302: return "Found";
>>>       > + case 303: return "See Other";
>>>       > + case 304: return "Not Modified";
>>>       > + case 305: return "Use Proxy";
>>>       > + case 307: return "Temporary Redirect";
>>>       > + case 308: return "Permanent Redirect";
>>>       > + case 400: return "Bad Request";
>>>       > + case 401: return "Unauthorized";
>>>       > + case 402: return "Payment Required";
>>>       > + case 403: return "Forbidden";
>>>       > + case 404: return "Not Found";
>>>       > + case 405: return "Method Not Allowed";
>>>       > + case 406: return "Not Acceptable";
>>>       > + case 407: return "Proxy Authentication Required";
>>>       > + case 408: return "Request Timeout";
>>>       > + case 409: return "Conflict";
>>>       > + case 410: return "Gone";
>>>       > + case 411: return "Length Required";
>>>       > + case 412: return "Precondition Failed";
>>>       > + case 413: return "Payload Too Large";
>>>       > + case 414: return "Request-URI Too Long";
>>>       > + case 415: return "Unsupported Media Type";
>>>       > + case 416: return "Requested Range Not Satisfiable";
>>>       > + case 417: return "Expectation Failed";
>>>       > + case 418: return "I'm a teapot";
>>>       > + case 421: return "Misdirected Request";
>>>       > + case 422: return "Unprocessable Entity";
>>>       > + case 423: return "Locked";
>>>       > + case 424: return "Failed Dependency";
>>>       > + case 426: return "Upgrade Required";
>>>       > + case 428: return "Precondition Required";
>>>       > + case 429: return "Too Many Requests";
>>>       > + case 431: return "Request Header Fields Too Large";
>>>       > + case 444: return "Connection Closed Without Response";
>>>       > + case 451: return "Unavailable For Legal Reasons";
>>>       > + case 499: return "Client Closed Request";
>>>       > + case 500: return "Internal Server Error";
>>>       > + case 501: return "Not Implemented";
>>>       > + case 502: return "Bad Gateway";
>>>       > + case 503: return "Service Unavailable";
>>>       > + case 504: return "Gateway Timeout";
>>>       > + case 505: return "HTTP Version Not Supported";
>>>       > + case 506: return "Variant Also Negotiates";
>>>       > + case 507: return "Insufficient Storage";
>>>       > + case 508: return "Loop Detected";
>>>       > + case 510: return "Not Extended";
>>>       > + case 511: return "Network Authentication Required";
>>>       > + case 599: return "Network Connect Timeout Error";
>>>       > + default: return "";
>>>       > + }
>>>       > +}
>>>       > +// clang-format on
>>>       > +
>>>       > +void mg_http_reply(struct mg_connection *c, int code, const char
>>>      *headers,
>>>       > + const char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + size_t len;
>>>       > + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code,
>>>       > + mg_http_status_code_str(code), headers == NULL ? "" : headers);
>>>       > + len = c->send.len;
>>>       > + va_start(ap, fmt);
>>>       > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + if (c->send.len > 16) {
>>>       > + size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11,
>>>      "%-10lu",
>>>       > + (unsigned long) (c->send.len - len));
>>>       > + c->send.buf[len - 15 + n] = ' '; // Change ending 0 to space
>>>       > + }
>>>       > + c->is_resp = 0;
>>>       > +}
>>>       > +
>>>       > +static void http_cb(struct mg_connection *, int, void *);
>>>       > +static void restore_http_cb(struct mg_connection *c) {
>>>       > + mg_fs_close((struct mg_fd *) c->pfn_data);
>>>       > + c->pfn_data = NULL;
>>>       > + c->pfn = http_cb;
>>>       > + c->is_resp = 0;
>>>       > +}
>>>       > +
>>>       > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>>>      mtime);
>>>       > +char *mg_http_etag(char *buf, size_t len, size_t size, time_t
>>>      mtime) {
>>>       > + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime,
>>>      (int64_t) size);
>>>       > + return buf;
>>>       > +}
>>>       > +
>>>       > +static void static_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
>>>       > + struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
>>>       > + // Read to send IO buffer directly, avoid extra on-stack buffer
>>>       > + size_t n, max = MG_IO_SIZE, space;
>>>       > + size_t *cl = (size_t *) &c->data[(sizeof(c->data) -
>>>      sizeof(size_t)) /
>>>       > + sizeof(size_t) * sizeof(size_t)];
>>>       > + if (c->send.size < max) mg_iobuf_resize(&c->send, max);
>>>       > + if (c->send.len >= c->send.size) return; // Rate limit
>>>       > + if ((space = c->send.size - c->send.len) > *cl) space = *cl;
>>>       > + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
>>>       > + c->send.len += n;
>>>       > + *cl -= n;
>>>       > + if (n == 0) restore_http_cb(c);
>>>       > + } else if (ev == MG_EV_CLOSE) {
>>>       > + restore_http_cb(c);
>>>       > + }
>>>       > + (void) ev_data;
>>>       > +}
>>>       > +
>>>       > +// Known mime types. Keep it outside guess_content_type()
>>>      function, since
>>>       > +// some environments don't like it defined there.
>>>       > +// clang-format off
>>>       > +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
>>>       > +static struct mg_str s_known_types[] = {
>>>       > + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
>>>       > + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
>>>       > + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
>>>       > + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
>>>       > + MG_C_STR("gif"), MG_C_STR("image/gif"),
>>>       > + MG_C_STR("png"), MG_C_STR("image/png"),
>>>       > + MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
>>>       > + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
>>>       > + MG_C_STR("woff"), MG_C_STR("font/woff"),
>>>       > + MG_C_STR("ttf"), MG_C_STR("font/ttf"),
>>>       > + MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
>>>       > + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
>>>       > + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
>>>       > + MG_C_STR("csv"), MG_C_STR("text/csv"),
>>>       > + MG_C_STR("doc"), MG_C_STR("application/msword"),
>>>       > + MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
>>>       > + MG_C_STR("gz"), MG_C_STR("application/gzip"),
>>>       > + MG_C_STR("ico"), MG_C_STR("image/x-icon"),
>>>       > + MG_C_STR("json"), MG_C_STR("application/json"),
>>>       > + MG_C_STR("mov"), MG_C_STR("video/quicktime"),
>>>       > + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
>>>       > + MG_C_STR("mp4"), MG_C_STR("video/mp4"),
>>>       > + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
>>>       > + MG_C_STR("pdf"), MG_C_STR("application/pdf"),
>>>       > + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
>>>       > + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
>>>       > + MG_C_STR("wav"), MG_C_STR("audio/wav"),
>>>       > + MG_C_STR("webp"), MG_C_STR("image/webp"),
>>>       > + MG_C_STR("zip"), MG_C_STR("application/zip"),
>>>       > + MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
>>>       > + {0, 0},
>>>       > +};
>>>       > +// clang-format on
>>>       > +
>>>       > +static struct mg_str guess_content_type(struct mg_str path,
>>>      const char *extra) {
>>>       > + struct mg_str entry, k, v, s = mg_str(extra);
>>>       > + size_t i = 0;
>>>       > +
>>>       > + // Shrink path to its extension only
>>>       > + while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
>>>       > + path.buf += path.len - i;
>>>       > + path.len = i;
>>>       > +
>>>       > + // Process user-provided mime type overrides, if any
>>>       > + while (mg_span(s, &entry, &s, ',')) {
>>>       > + if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0)
>>>      return v;
>>>       > + }
>>>       > +
>>>       > + // Process built-in mime types
>>>       > + for (i = 0; s_known_types[i].buf != NULL; i += 2) {
>>>       > + if (mg_strcmp(path, s_known_types[i]) == 0) return
>>>      s_known_types[i + 1];
>>>       > + }
>>>       > +
>>>       > + return mg_str("text/plain; charset=utf-8");
>>>       > +}
>>>       > +
>>>       > +static int getrange(struct mg_str *s, size_t *a, size_t *b) {
>>>       > + size_t i, numparsed = 0;
>>>       > + for (i = 0; i + 6 < s->len; i++) {
>>>       > + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
>>>       > + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
>>>       > + if (mg_span(v, &k, &v, '-')) {
>>>       > + if (mg_to_size_t(k, a)) numparsed++;
>>>       > + if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
>>>       > + } else {
>>>       > + if (mg_to_size_t(v, a)) numparsed++;
>>>       > + }
>>>       > + break;
>>>       > + }
>>>       > + return (int) numparsed;
>>>       > +}
>>>       > +
>>>       > +void mg_http_serve_file(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + const char *path,
>>>       > + const struct mg_http_serve_opts *opts) {
>>>       > + char etag[64], tmp[MG_PATH_MAX];
>>>       > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > + struct mg_fd *fd = NULL;
>>>       > + size_t size = 0;
>>>       > + time_t mtime = 0;
>>>       > + struct mg_str *inm = NULL;
>>>       > + struct mg_str mime = guess_content_type(mg_str(path),
>>>      opts->mime_types);
>>>       > + bool gzip = false;
>>>       > +
>>>       > + if (path != NULL) {
>>>       > + // If a browser sends us "Accept-Encoding: gzip", try to open
>>>      .gz first
>>>       > + struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
>>>       > + if (ae != NULL) {
>>>       > + char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
>>>       > + if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
>>>       > + mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
>>>       > + fd = mg_fs_open(fs, tmp, MG_FS_READ);
>>>       > + if (fd != NULL) gzip = true, path = tmp;
>>>       > + }
>>>       > + free(ae_);
>>>       > + }
>>>       > + // No luck opening .gz? Open what we've told to open
>>>       > + if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
>>>       > + }
>>>       > +
>>>       > + // Failed to open, and page404 is configured? Open it, then
>>>       > + if (fd == NULL && opts->page404 != NULL) {
>>>       > + fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
>>>       > + path = opts->page404;
>>>       > + mime = guess_content_type(mg_str(path), opts->mime_types);
>>>       > + }
>>>       > +
>>>       > + if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
>>>       > + mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
>>>       > + mg_fs_close(fd);
>>>       > + // NOTE: mg_http_etag() call should go first!
>>>       > + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) !=
>>>      NULL &&
>>>       > + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
>>>       > + mg_strcasecmp(*inm, mg_str(etag)) == 0) {
>>>       > + mg_fs_close(fd);
>>>       > + mg_http_reply(c, 304, opts->extra_headers, "");
>>>       > + } else {
>>>       > + int n, status = 200;
>>>       > + char range[100];
>>>       > + size_t r1 = 0, r2 = 0, cl = size;
>>>       > +
>>>       > + // Handle Range header
>>>       > + struct mg_str *rh = mg_http_get_header(hm, "Range");
>>>       > + range[0] = '\0';
>>>       > + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
>>>       > + // If range is specified like "400-", set second limit to
>>>      content len
>>>       > + if (n == 1) r2 = cl - 1;
>>>       > + if (r1 > r2 || r2 >= cl) {
>>>       > + status = 416;
>>>       > + cl = 0;
>>>       > + mg_snprintf(range, sizeof(range), "Content-Range: bytes
>>>      */%lld\r\n",
>>>       > + (int64_t) size);
>>>       > + } else {
>>>       > + status = 206;
>>>       > + cl = r2 - r1 + 1;
>>>       > + mg_snprintf(range, sizeof(range),
>>>       > + "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
>>>       > + (uint64_t) (r1 + cl - 1), (uint64_t) size);
>>>       > + fs->sk(fd->fd, r1);
>>>       > + }
>>>       > + }
>>>       > + mg_printf(c,
>>>       > + "HTTP/1.1 %d %s\r\n"
>>>       > + "Content-Type: %.*s\r\n"
>>>       > + "Etag: %s\r\n"
>>>       > + "Content-Length: %llu\r\n"
>>>       > + "%s%s%s\r\n",
>>>       > + status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
>>>       > + etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
>>>       > + range, opts->extra_headers ? opts->extra_headers : "");
>>>       > + if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
>>>       > + c->is_draining = 1;
>>>       > + c->is_resp = 0;
>>>       > + mg_fs_close(fd);
>>>       > + } else {
>>>       > + // Track to-be-sent content length at the end of c->data, aligned
>>>       > + size_t *clp = (size_t *) &c->data[(sizeof(c->data) -
>>>      sizeof(size_t)) /
>>>       > + sizeof(size_t) * sizeof(size_t)];
>>>       > + c->pfn = static_cb;
>>>       > + c->pfn_data = fd;
>>>       > + *clp = cl;
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +struct printdirentrydata {
>>>       > + struct mg_connection *c;
>>>       > + struct mg_http_message *hm;
>>>       > + const struct mg_http_serve_opts *opts;
>>>       > + const char *dir;
>>>       > +};
>>>       > +
>>>       > +#if MG_ENABLE_DIRLIST
>>>       > +static void printdirentry(const char *name, void *userdata) {
>>>       > + struct printdirentrydata *d = (struct printdirentrydata *)
>>>      userdata;
>>>       > + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix :
>>>      d->opts->fs;
>>>       > + size_t size = 0;
>>>       > + time_t t = 0;
>>>       > + char path[MG_PATH_MAX], sz[40], mod[40];
>>>       > + int flags, n = 0;
>>>       > +
>>>       > + // MG_DEBUG(("[%s] [%s]", d->dir, name));
>>>       > + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
>>>       > + sizeof(path)) {
>>>       > + MG_ERROR(("%s truncated", name));
>>>       > + } else if ((flags = fs->st(path, &size, &t)) == 0) {
>>>       > + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
>>>       > + } else {
>>>       > + const char *slash = flags & MG_FS_DIR ? "/" : "";
>>>       > + if (flags & MG_FS_DIR) {
>>>       > + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
>>>       > + } else {
>>>       > + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
>>>       > + }
>>>       > +#if defined(MG_HTTP_DIRLIST_TIME_FMT)
>>>       > + {
>>>       > + char time_str[40];
>>>       > + struct tm *time_info = localtime(&t);
>>>       > + strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S",
>>>      time_info);
>>>       > + mg_snprintf(mod, sizeof(mod), "%s", time_str);
>>>       > + }
>>>       > +#else
>>>       > + mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
>>>       > +#endif
>>>       > + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
>>>       > + mg_printf(d->c,
>>>       > + " <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
>>>       > + "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
>>>       > + n, path, slash, name, slash, (unsigned long) t, mod,
>>>       > + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void listdir(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + const struct mg_http_serve_opts *opts, char *dir) {
>>>       > + const char *sort_js_code =
>>>       > + "<script>function srt(tb, sc, so, d) {"
>>>       > + "var tr = Array.prototype.slice.call(tb.rows, 0),"
>>>       > + "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 =
>>>      b.cells[sc],"
>>>       > + "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
>>>       > + "t1 = a.cells[2].getAttribute('name'), "
>>>       > + "t2 = b.cells[2].getAttribute('name'); "
>>>       > + "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
>>>       > + "n1 ? parseInt(n2) - parseInt(n1) : "
>>>       > + "c1.textContent.trim().localeCompare(c2.textContent.trim()));
>>>      });";
>>>       > + const char *sort_js_code2 =
>>>       > + "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
>>>       > + "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
>>>       > + "};"
>>>       > + "window.onload = function() {"
>>>       > + "var tb = document.getElementById('tb');"
>>>       > + "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) ||
>>>      [0, 2, 1];"
>>>       > + "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
>>>       > + "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb,
>>>      c, so); "
>>>       > + "sc = c; ev.preventDefault();}};"
>>>       > + "srt(tb, sc, so, true);"
>>>       > + "}"
>>>       > + "</script>";
>>>       > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > + struct printdirentrydata d = {c, hm, opts, dir};
>>>       > + char tmp[10], buf[MG_PATH_MAX];
>>>       > + size_t off, n;
>>>       > + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf,
>>>      sizeof(buf), 0);
>>>       > + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) :
>>>      hm->uri;
>>>       > +
>>>       > + mg_printf(c,
>>>       > + "HTTP/1.1 200 OK\r\n"
>>>       > + "Content-Type: text/html; charset=utf-8\r\n"
>>>       > + "%s"
>>>       > + "Content-Length: \r\n\r\n",
>>>       > + opts->extra_headers == NULL ? "" : opts->extra_headers);
>>>       > + off = c->send.len; // Start of body
>>>       > + mg_printf(c,
>>>       > + "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
>>>       > + "<style>th,td {text-align: left; padding-right: 1em; "
>>>       > + "font-family: monospace; }</style></head>"
>>>       > + "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
>>>       > + "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
>>>       > + "<a href=\"#\" rel=\"1\">Modified</a></th>"
>>>       > + "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
>>>       > + "<tr><td colspan=\"3\"><hr></td></tr>"
>>>       > + "</thead>"
>>>       > + "<tbody id=\"tb\">\n",
>>>       > + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int)
>>>      uri.len,
>>>       > + uri.buf);
>>>       > + mg_printf(c, "%s",
>>>       > + " <tr><td><a href=\"..\">..</a></td>"
>>>       > + "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
>>>       > +
>>>       > + fs->ls(dir, printdirentry, &d);
>>>       > + mg_printf(c,
>>>       > + "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
>>>       > + "</table><address>Mongoose v.%s</address></body></html>\n",
>>>       > + MG_VERSION);
>>>       > + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)
>>>      (c->send.len - off));
>>>       > + if (n > sizeof(tmp)) n = 0;
>>>       > + memcpy(c->send.buf + off - 12, tmp, n); // Set content length
>>>       > + c->is_resp = 0; // Mark response end
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +// Resolve requested file into `path` and return its fs->st()
>>>      result
>>>       > +static int uri_to_path2(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + struct mg_fs *fs, struct mg_str url, struct mg_str dir,
>>>       > + char *path, size_t path_size) {
>>>       > + int flags, tmp;
>>>       > + // Append URI to the root_dir, and sanitize it
>>>       > + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len,
>>>      dir.buf);
>>>       > + if (n + 2 >= path_size) {
>>>       > + mg_http_reply(c, 400, "", "Exceeded path size");
>>>       > + return -1;
>>>       > + }
>>>       > + path[path_size - 1] = '\0';
>>>       > + // Terminate root dir with slash
>>>       > + if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
>>>       > + if (url.len < hm->uri.len) {
>>>       > + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len,
>>>      path + n,
>>>       > + path_size - n, 0);
>>>       > + }
>>>       > + path[path_size - 1] = '\0'; // Double-check
>>>       > + if (!mg_path_is_sane(mg_str_n(path, path_size))) {
>>>       > + mg_http_reply(c, 400, "", "Invalid path");
>>>       > + return -1;
>>>       > + }
>>>       > + n = strlen(path);
>>>       > + while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim
>>>      trailing slashes
>>>       > + flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
>>>       > + : fs->st(path, NULL, NULL);
>>>       > + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len,
>>>      hm->uri.buf, path,
>>>       > + flags));
>>>       > + if (flags == 0) {
>>>       > + // Do nothing - let's caller decide
>>>       > + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
>>>       > + hm->uri.buf[hm->uri.len - 1] != '/') {
>>>       > + mg_printf(c,
>>>       > + "HTTP/1.1 301 Moved\r\n"
>>>       > + "Location: %.*s/\r\n"
>>>       > + "Content-Length: 0\r\n"
>>>       > "\r\n",
>>>       > - (int) hm->uri.len, hm->uri.ptr);
>>>       > + (int) hm->uri.len, hm->uri.buf);
>>>       > c->is_resp = 0;
>>>       > flags = -1;
>>>       > } else if (flags & MG_FS_DIR) {
>>>       > @@ -2057,3318 +3036,11754 @@ static int uri_to_path2(struct
>>>      mg_connection *c, struct mg_http_message *hm,
>>>       > path[n + 1 + strlen(MG_HTTP_INDEX)] =
>>>       > '\0'; // Remove appended .gz in index file name
>>>       > } else {
>>>       > - path[n] = '\0'; // Remove appended index file name
>>>       > + path[n] = '\0'; // Remove appended index file name
>>>       > + }
>>>       > + }
>>>       > + return flags;
>>>       > +}
>>>       > +
>>>       > +static int uri_to_path(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + const struct mg_http_serve_opts *opts, char *path,
>>>       > + size_t path_size) {
>>>       > + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > + struct mg_str k, v, part, s = mg_str(opts->root_dir), u =
>>>      {NULL, 0}, p = u;
>>>       > + while (mg_span(s, &part, &s, ',')) {
>>>       > + if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
>>>       > + if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
>>>       > + if (hm->uri.len < k.len) continue;
>>>       > + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
>>>       > + u = k, p = v;
>>>       > + }
>>>       > + return uri_to_path2(c, hm, fs, u, p, path, path_size);
>>>       > +}
>>>       > +
>>>       > +void mg_http_serve_dir(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + const struct mg_http_serve_opts *opts) {
>>>       > + char path[MG_PATH_MAX];
>>>       > + const char *sp = opts->ssi_pattern;
>>>       > + int flags = uri_to_path(c, hm, opts, path, sizeof(path));
>>>       > + if (flags < 0) {
>>>       > + // Do nothing: the response has already been sent by uri_to_path()
>>>       > + } else if (flags & MG_FS_DIR) {
>>>       > +#if MG_ENABLE_DIRLIST
>>>       > + listdir(c, hm, opts, path);
>>>       > +#else
>>>       > + mg_http_reply(c, 403, "", "Forbidden\n");
>>>       > +#endif
>>>       > + } else if (flags && sp != NULL && mg_match(mg_str(path),
>>>      mg_str(sp), NULL)) {
>>>       > + mg_http_serve_ssi(c, opts->root_dir, path);
>>>       > + } else {
>>>       > + mg_http_serve_file(c, hm, path, opts);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static bool mg_is_url_safe(int c) {
>>>       > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
>>>       > + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
>>>      == '~';
>>>       > +}
>>>       > +
>>>       > +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
>>>      len) {
>>>       > + size_t i, n = 0;
>>>       > + for (i = 0; i < sl; i++) {
>>>       > + int c = *(unsigned char *) &s[i];
>>>       > + if (n + 4 >= len) return 0;
>>>       > + if (mg_is_url_safe(c)) {
>>>       > + buf[n++] = s[i];
>>>       > + } else {
>>>       > + mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
>>>       > + n += 3;
>>>       > + }
>>>       > + }
>>>       > + if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
>>>      the destination
>>>       > + if (len > 0) buf[len - 1] = '\0'; // Always.
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +void mg_http_creds(struct mg_http_message *hm, char *user,
>>>      size_t userlen,
>>>       > + char *pass, size_t passlen) {
>>>       > + struct mg_str *v = mg_http_get_header(hm, "Authorization");
>>>       > + user[0] = pass[0] = '\0';
>>>       > + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
>>>       > + char buf[256];
>>>       > + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf,
>>>      sizeof(buf));
>>>       > + const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
>>>       > + if (p != NULL) {
>>>       > + mg_snprintf(user, userlen, "%.*s", p - buf, buf);
>>>       > + mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1,
>>>      p + 1);
>>>       > + }
>>>       > + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ",
>>>      7) == 0) {
>>>       > + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
>>>       > + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
>>>       > + struct mg_str t = mg_http_get_header_var(*v,
>>>      mg_str_n("access_token", 12));
>>>       > + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
>>>      t.buf);
>>>       > + } else {
>>>       > + mg_http_get_var(&hm->query, "access_token", pass, passlen);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static struct mg_str stripquotes(struct mg_str s) {
>>>       > + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
>>>       > + ? mg_str_n(s.buf + 1, s.len - 2)
>>>       > + : s;
>>>       > +}
>>>       > +
>>>       > +struct mg_str mg_http_get_header_var(struct mg_str s, struct
>>>      mg_str v) {
>>>       > + size_t i;
>>>       > + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
>>>       > + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len)
>>>      == 0) {
>>>       > + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
>>>       > + int q = p < x && *p == '"' ? 1 : 0;
>>>       > + while (p < x &&
>>>       > + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
>>>       > + p++;
>>>       > + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int)
>>>      v.len,
>>>       > + // v.buf, (int) (p - b), b));
>>>       > + return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
>>>       > + }
>>>       > + }
>>>       > + return mg_str_n(NULL, 0);
>>>       > +}
>>>       > +
>>>       > +long mg_http_upload(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > + struct mg_fs *fs, const char *dir, size_t max_size) {
>>>       > + char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
>>>       > + long res = 0, offset;
>>>       > + mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
>>>       > + mg_http_get_var(&hm->query, "file", file, sizeof(file));
>>>       > + offset = strtol(buf, NULL, 0);
>>>       > + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
>>>       > + if (hm->body.len == 0) {
>>>       > + mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
>>>       > + } else if (file[0] == '\0') {
>>>       > + mg_http_reply(c, 400, "", "file required");
>>>       > + res = -1;
>>>       > + } else if (mg_path_is_sane(mg_str(file)) == false) {
>>>       > + mg_http_reply(c, 400, "", "%s: invalid file", file);
>>>       > + res = -2;
>>>       > + } else if (offset < 0) {
>>>       > + mg_http_reply(c, 400, "", "offset required");
>>>       > + res = -3;
>>>       > + } else if ((size_t) offset + hm->body.len > max_size) {
>>>       > + mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
>>>       > + (unsigned long) max_size);
>>>       > + res = -4;
>>>       > + } else {
>>>       > + struct mg_fd *fd;
>>>       > + size_t current_size = 0;
>>>       > + MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
>>>       > + if (offset == 0) fs->rm(path); // If offset if 0, truncate file
>>>       > + fs->st(path, &current_size, NULL);
>>>       > + if (offset > 0 && current_size != (size_t) offset) {
>>>       > + mg_http_reply(c, 400, "", "%s: offset mismatch", path);
>>>       > + res = -5;
>>>       > + } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
>>>       > + mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
>>>       > + res = -6;
>>>       > + } else {
>>>       > + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
>>>       > + mg_fs_close(fd);
>>>       > + mg_http_reply(c, 200, "", "%ld", res);
>>>       > + }
>>>       > + }
>>>       > + return res;
>>>       > +}
>>>       > +
>>>       > +int mg_http_status(const struct mg_http_message *hm) {
>>>       > + return atoi(hm->uri.buf);
>>>       > +}
>>>       > +
>>>       > +static bool is_hex_digit(int c) {
>>>       > + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
>>>       > + (c >= 'A' && c <= 'F');
>>>       > +}
>>>       > +
>>>       > +static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
>>>       > + int i = 0, n = 0;
>>>       > + if (len < 3) return 0;
>>>       > + while (i < len && is_hex_digit(buf[i])) i++;
>>>       > + if (i == 0) return -1; // Error, no length specified
>>>       > + if (i > (int) sizeof(int) * 2) return -1; // Chunk length is
>>>      too big
>>>       > + if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return
>>>      -1; // Error
>>>       > + if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n,
>>>      sizeof(int)) == false)
>>>       > + return -1; // Decode chunk length, overflow
>>>       > + if (n < 0) return -1; // Error. TODO(): some checks now redundant
>>>       > + if (n > len - i - 4) return 0; // Chunk not yet fully buffered
>>>       > + if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return
>>>      -1; // Error
>>>       > + *pl = i + 2, *dl = n;
>>>       > + return i + 2 + n + 2;
>>>       > +}
>>>       > +
>>>       > +static void http_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
>>>       > + struct mg_http_message hm;
>>>       > + size_t ofs = 0; // Parsing offset
>>>       > + while (c->is_resp == 0 && ofs < c->recv.len) {
>>>       > + const char *buf = (char *) c->recv.buf + ofs;
>>>       > + int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
>>>       > + struct mg_str *te; // Transfer - encoding header
>>>       > + bool is_chunked = false;
>>>       > + if (n < 0) {
>>>       > + // We don't use mg_error() here, to avoid closing pipelined
>>>      requests
>>>       > + // prematurely, see #2592
>>>       > + MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
>>>       > + c->is_draining = 1;
>>>       > + mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
>>>       > + c->recv.len = 0;
>>>       > + return;
>>>       > + }
>>>       > + if (n == 0) break; // Request is not buffered yet
>>>       > + mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers
>>>       > + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
>>>       > + hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG
>>>       > + hm.body.len = hm.message.len - (size_t) (hm.body.buf -
>>>      hm.message.buf);
>>>       > + }
>>>       > + if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
>>>       > + if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
>>>       > + is_chunked = true;
>>>       > + } else {
>>>       > + mg_error(c, "Invalid Transfer-Encoding"); // See #2460
>>>       > + return;
>>>       > + }
>>>       > + } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
>>>       > + // #2593: HTTP packets must contain either Transfer-Encoding or
>>>       > + // Content-length
>>>       > + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
>>>       > + bool require_content_len = false;
>>>       > + if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST"))
>>>      == 0 ||
>>>       > + mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
>>>       > + // POST and PUT should include an entity body. Therefore, they
>>>      should
>>>       > + // contain a Content-length header. Other requests can also
>>>      contain a
>>>       > + // body, but their content has no defined semantics (RFC 7231)
>>>       > + require_content_len = true;
>>>       > + } else if (is_response) {
>>>       > + // HTTP spec 7.2 Entity body: All other responses must include
>>>      a body
>>>       > + // or Content-Length header field defined with a value of 0.
>>>       > + int status = mg_http_status(&hm);
>>>       > + require_content_len = status >= 200 && status != 204 && status
>>>      != 304;
>>>       > + }
>>>       > + if (require_content_len) {
>>>       > + mg_http_reply(c, 411, "", "");
>>>       > + MG_ERROR(("%s", "Content length missing from request"));
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + if (is_chunked) {
>>>       > + // For chunked data, strip off prefixes and suffixes from chunks
>>>       > + // and relocate them right after the headers, then report a
>>>      message
>>>       > + char *s = (char *) c->recv.buf + ofs + n;
>>>       > + int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs -
>>>      (size_t) n);
>>>       > +
>>>       > + // Find zero-length chunk (the end of the body)
>>>       > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o
>>>      += cl;
>>>       > + if (cl == 0) break; // No zero-len chunk, buffer more data
>>>       > + if (cl < 0) {
>>>       > + mg_error(c, "Invalid chunk");
>>>       > + break;
>>>       > + }
>>>       > +
>>>       > + // Zero chunk found. Second pass: strip + relocate
>>>       > + o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
>>>       > + while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
>>>       > + memmove(s + hm.body.len, s + o + pl, (size_t) dl);
>>>       > + o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t)
>>>      dl;
>>>       > + if (dl == 0) break;
>>>       > + }
>>>       > + ofs += (size_t) (n + o);
>>>       > + } else { // Normal, non-chunked data
>>>       > + size_t len = c->recv.len - ofs - (size_t) n;
>>>       > + if (hm.body.len > len) break; // Buffer more data
>>>       > + ofs += (size_t) n + hm.body.len;
>>>       > + }
>>>       > +
>>>       > + if (c->is_accepted) c->is_resp = 1; // Start generating response
>>>       > + mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
>>>       > + }
>>>       > + if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete
>>>      processed data
>>>       > + }
>>>       > + (void) ev_data;
>>>       > +}
>>>       > +
>>>       > +static void mg_hfn(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + if (ev == MG_EV_HTTP_MSG) {
>>>       > + struct mg_http_message *hm = (struct mg_http_message *) ev_data;
>>>       > + if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
>>>       > + mg_http_reply(c, 200, "", "ok\n");
>>>       > + c->is_draining = 1;
>>>       > + c->data[0] = 'X';
>>>       > + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
>>>       > + int level = (int) mg_json_get_long(hm->body, "$.level",
>>>      MG_LL_DEBUG);
>>>       > + mg_log_set(level);
>>>       > + mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
>>>       > + } else {
>>>       > + mg_http_reply(c, 200, "", "hi\n");
>>>       > + }
>>>       > + } else if (ev == MG_EV_CLOSE) {
>>>       > + if (c->data[0] == 'X') *(bool *) c->fn_data = true;
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_hello(const char *url) {
>>>       > + struct mg_mgr mgr;
>>>       > + bool done = false;
>>>       > + mg_mgr_init(&mgr);
>>>       > + if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
>>>       > + while (done == false) mg_mgr_poll(&mgr, 100);
>>>       > + mg_mgr_free(&mgr);
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>>>       > + if (c != NULL) c->pfn = http_cb;
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>>>       > + if (c != NULL) c->pfn = http_cb;
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/iobuf.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +static size_t roundup(size_t size, size_t align) {
>>>       > + return align == 0 ? size : (size + align - 1) / align * align;
>>>       > +}
>>>       > +
>>>       > +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
>>>       > + int ok = 1;
>>>       > + new_size = roundup(new_size, io->align);
>>>       > + if (new_size == 0) {
>>>       > + mg_bzero(io->buf, io->size);
>>>       > + free(io->buf);
>>>       > + io->buf = NULL;
>>>       > + io->len = io->size = 0;
>>>       > + } else if (new_size != io->size) {
>>>       > + // NOTE(lsm): do not use realloc here. Use calloc/free only, to
>>>      ease the
>>>       > + // porting to some obscure platforms like FreeRTOS
>>>       > + void *p = calloc(1, new_size);
>>>       > + if (p != NULL) {
>>>       > + size_t len = new_size < io->len ? new_size : io->len;
>>>       > + if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
>>>       > + mg_bzero(io->buf, io->size);
>>>       > + free(io->buf);
>>>       > + io->buf = (unsigned char *) p;
>>>       > + io->size = new_size;
>>>       > + } else {
>>>       > + ok = 0;
>>>       > + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
>>>      new_size));
>>>       > + }
>>>       > + }
>>>       > + return ok;
>>>       > +}
>>>       > +
>>>       > +int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
>>>       > + io->buf = NULL;
>>>       > + io->align = align;
>>>       > + io->size = io->len = 0;
>>>       > + return mg_iobuf_resize(io, size);
>>>       > +}
>>>       > +
>>>       > +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
>>>      *buf,
>>>       > + size_t len) {
>>>       > + size_t new_size = roundup(io->len + len, io->align);
>>>       > + mg_iobuf_resize(io, new_size); // Attempt to resize
>>>       > + if (new_size != io->size) len = 0; // Resize failure, append
>>>      nothing
>>>       > + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
>>>      io->len - ofs);
>>>       > + if (buf != NULL) memmove(io->buf + ofs, buf, len);
>>>       > + if (ofs > io->len) io->len += ofs - io->len;
>>>       > + io->len += len;
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
>>>       > + if (ofs > io->len) ofs = io->len;
>>>       > + if (ofs + len > io->len) len = io->len - ofs;
>>>       > + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
>>>      io->len - ofs - len);
>>>       > + if (io->buf) mg_bzero(io->buf + io->len - len, len);
>>>       > + io->len -= len;
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +void mg_iobuf_free(struct mg_iobuf *io) {
>>>       > + mg_iobuf_resize(io, 0);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/json.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +static const char *escapeseq(int esc) {
>>>       > + return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
>>>       > +}
>>>       > +
>>>       > +static char json_esc(int c, int esc) {
>>>       > + const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
>>>       > + for (p = esc1; *p != '\0'; p++) {
>>>       > + if (*p == c) return esc2[p - esc1];
>>>       > + }
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +static int mg_pass_string(const char *s, int len) {
>>>       > + int i;
>>>       > + for (i = 0; i < len; i++) {
>>>       > + if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
>>>       > + i++;
>>>       > + } else if (s[i] == '\0') {
>>>       > + return MG_JSON_INVALID;
>>>       > + } else if (s[i] == '"') {
>>>       > + return i;
>>>       > + }
>>>       > + }
>>>       > + return MG_JSON_INVALID;
>>>       > +}
>>>       > +
>>>       > +static double mg_atod(const char *p, int len, int *numlen) {
>>>       > + double d = 0.0;
>>>       > + int i = 0, sign = 1;
>>>       > +
>>>       > + // Sign
>>>       > + if (i < len && *p == '-') {
>>>       > + sign = -1, i++;
>>>       > + } else if (i < len && *p == '+') {
>>>       > + i++;
>>>       > + }
>>>       > +
>>>       > + // Decimal
>>>       > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>>>       > + d *= 10.0;
>>>       > + d += p[i] - '0';
>>>       > + }
>>>       > + d *= sign;
>>>       > +
>>>       > + // Fractional
>>>       > + if (i < len && p[i] == '.') {
>>>       > + double frac = 0.0, base = 0.1;
>>>       > + i++;
>>>       > + for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>>>       > + frac += base * (p[i] - '0');
>>>       > + base /= 10.0;
>>>       > + }
>>>       > + d += frac * sign;
>>>       > + }
>>>       > +
>>>       > + // Exponential
>>>       > + if (i < len && (p[i] == 'e' || p[i] == 'E')) {
>>>       > + int j, exp = 0, minus = 0;
>>>       > + i++;
>>>       > + if (i < len && p[i] == '-') minus = 1, i++;
>>>       > + if (i < len && p[i] == '+') i++;
>>>       > + while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
>>>       > + exp = exp * 10 + (p[i++] - '0');
>>>       > + if (minus) exp = -exp;
>>>       > + for (j = 0; j < exp; j++) d *= 10.0;
>>>       > + for (j = 0; j < -exp; j++) d /= 10.0;
>>>       > + }
>>>       > +
>>>       > + if (numlen != NULL) *numlen = i;
>>>       > + return d;
>>>       > +}
>>>       > +
>>>       > +// Iterate over object or array elements
>>>       > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
>>>      *key,
>>>       > + struct mg_str *val) {
>>>       > + if (ofs >= obj.len) {
>>>       > + ofs = 0; // Out of boundaries, stop scanning
>>>       > + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
>>>       > + ofs = 0; // Not an array or object, stop
>>>       > + } else {
>>>       > + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
>>>       > + if (ofs == 0) ofs++, sub.buf++, sub.len--;
>>>       > + if (*obj.buf == '[') { // Iterate over an array
>>>       > + int n = 0, o = mg_json_get(sub, "$", &n);
>>>       > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>>>       > + ofs = 0; // Error parsing key, stop scanning
>>>       > + } else {
>>>       > + if (key) *key = mg_str_n(NULL, 0);
>>>       > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
>>>       > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
>>>       > + }
>>>       > + } else { // Iterate over an object
>>>       > + int n = 0, o = mg_json_get(sub, "$", &n);
>>>       > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>>>       > + ofs = 0; // Error parsing key, stop scanning
>>>       > + } else {
>>>       > + if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
>>>       > + sub.buf += o + n, sub.len -= (size_t) (o + n);
>>>       > + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
>>>       > + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
>>>       > + n = 0, o = mg_json_get(sub, "$", &n);
>>>       > + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
>>>       > + ofs = 0; // Error parsing value, stop scanning
>>>       > + } else {
>>>       > + if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
>>>       > + ofs = (size_t) (&sub.buf[o + n] - obj.buf);
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
>>>       > + while (ofs && ofs < obj.len &&
>>>       > + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
>>>       > + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
>>>       > + ofs++;
>>>       > + }
>>>       > + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
>>>       > + if (ofs > obj.len) ofs = 0;
>>>       > + }
>>>       > + return ofs;
>>>       > +}
>>>       > +
>>>       > +int mg_json_get(struct mg_str json, const char *path, int
>>>      *toklen) {
>>>       > + const char *s = json.buf;
>>>       > + int len = (int) json.len;
>>>       > + enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
>>>      S_VALUE;
>>>       > + unsigned char nesting[MG_JSON_MAX_DEPTH];
>>>       > + int i = 0; // Current offset in `s`
>>>       > + int j = 0; // Offset in `s` we're looking for (return value)
>>>       > + int depth = 0; // Current depth (nesting level)
>>>       > + int ed = 0; // Expected depth
>>>       > + int pos = 1; // Current position in `path`
>>>       > + int ci = -1, ei = -1; // Current and expected index in array
>>>       > +
>>>       > + if (toklen) *toklen = 0;
>>>       > + if (path[0] != '$') return MG_JSON_INVALID;
>>>       > +
>>>       > +#define MG_CHECKRET(x) \
>>>       > + do { \
>>>       > + if (depth == ed && path[pos] == '\0' && ci == ei) { \
>>>       > + if (toklen) *toklen = i - j + 1; \
>>>       > + return j; \
>>>       > + } \
>>>       > + } while (0)
>>>       > +
>>>       > +// In the ascii table, the distance between `[` and `]` is 2.
>>>       > +// Ditto for `{` and `}`. Hence +2 in the code below.
>>>       > +#define MG_EOO(x) \
>>>       > + do { \
>>>       > + if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
>>>       > + if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
>>>       > + depth--; \
>>>       > + MG_CHECKRET(x); \
>>>       > + } while (0)
>>>       > +
>>>       > + for (i = 0; i < len; i++) {
>>>       > + unsigned char c = ((unsigned char *) s)[i];
>>>       > + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
>>>       > + switch (expecting) {
>>>       > + case S_VALUE:
>>>       > + // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
>>>      ci, ei);
>>>       > + if (depth == ed) j = i;
>>>       > + if (c == '{') {
>>>       > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>>>       > + if (depth == ed && path[pos] == '.' && ci == ei) {
>>>       > + // If we start the object, reset array indices
>>>       > + ed++, pos++, ci = ei = -1;
>>>       > + }
>>>       > + nesting[depth++] = c;
>>>       > + expecting = S_KEY;
>>>       > + break;
>>>       > + } else if (c == '[') {
>>>       > + if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>>>       > + if (depth == ed && path[pos] == '[' && ei == ci) {
>>>       > + ed++, pos++, ci = 0;
>>>       > + for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
>>>       > + ei *= 10;
>>>       > + ei += path[pos] - '0';
>>>       > + }
>>>       > + if (path[pos] != 0) pos++;
>>>       > + }
>>>       > + nesting[depth++] = c;
>>>       > + break;
>>>       > + } else if (c == ']' && depth > 0) { // Empty array
>>>       > + MG_EOO(']');
>>>       > + } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
>>>      == 0) {
>>>       > + i += 3;
>>>       > + } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
>>>      == 0) {
>>>       > + i += 3;
>>>       > + } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
>>>      == 0) {
>>>       > + i += 4;
>>>       > + } else if (c == '-' || ((c >= '0' && c <= '9'))) {
>>>       > + int numlen = 0;
>>>       > + mg_atod(&s[i], len - i, &numlen);
>>>       > + i += numlen - 1;
>>>       > + } else if (c == '"') {
>>>       > + int n = mg_pass_string(&s[i + 1], len - i - 1);
>>>       > + if (n < 0) return n;
>>>       > + i += n + 1;
>>>       > + } else {
>>>       > + return MG_JSON_INVALID;
>>>       > + }
>>>       > + MG_CHECKRET('V');
>>>       > + if (depth == ed && ei >= 0) ci++;
>>>       > + expecting = S_COMMA_OR_EOO;
>>>       > + break;
>>>       > +
>>>       > + case S_KEY:
>>>       > + if (c == '"') {
>>>       > + int n = mg_pass_string(&s[i + 1], len - i - 1);
>>>       > + if (n < 0) return n;
>>>       > + if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
>>>       > + if (depth < ed) return MG_JSON_NOT_FOUND;
>>>       > + if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
>>>       > + // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos,
>>>      path, n,
>>>       > + // &s[i + 1], n, depth, ed, ci, ei);
>>>       > + // NOTE(cpq): in the check sequence below is important.
>>>       > + // strncmp() must go first: it fails fast if the remaining length
>>>       > + // of the path is smaller than `n`.
>>>       > + if (depth == ed && path[pos - 1] == '.' &&
>>>       > + strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
>>>       > + (path[pos + n] == '\0' || path[pos + n] == '.' ||
>>>       > + path[pos + n] == '[')) {
>>>       > + pos += n;
>>>       > + }
>>>       > + i += n + 1;
>>>       > + expecting = S_COLON;
>>>       > + } else if (c == '}') { // Empty object
>>>       > + MG_EOO('}');
>>>       > + expecting = S_COMMA_OR_EOO;
>>>       > + if (depth == ed && ei >= 0) ci++;
>>>       > + } else {
>>>       > + return MG_JSON_INVALID;
>>>       > + }
>>>       > + break;
>>>       > +
>>>       > + case S_COLON:
>>>       > + if (c == ':') {
>>>       > + expecting = S_VALUE;
>>>       > + } else {
>>>       > + return MG_JSON_INVALID;
>>>       > + }
>>>       > + break;
>>>       > +
>>>       > + case S_COMMA_OR_EOO:
>>>       > + if (depth <= 0) {
>>>       > + return MG_JSON_INVALID;
>>>       > + } else if (c == ',') {
>>>       > + expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
>>>       > + } else if (c == ']' || c == '}') {
>>>       > + if (depth == ed && c == '}' && path[pos - 1] == '.')
>>>       > + return MG_JSON_NOT_FOUND;
>>>       > + if (depth == ed && c == ']' && path[pos - 1] == ',')
>>>       > + return MG_JSON_NOT_FOUND;
>>>       > + MG_EOO('O');
>>>       > + if (depth == ed && ei >= 0) ci++;
>>>       > + } else {
>>>       > + return MG_JSON_INVALID;
>>>       > + }
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + return MG_JSON_NOT_FOUND;
>>>       > +}
>>>       > +
>>>       > +struct mg_str mg_json_get_tok(struct mg_str json, const char
>>>      *path) {
>>>       > + int len = 0, ofs = mg_json_get(json, path, &len);
>>>       > + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
>>>       > + (size_t) (len < 0 ? 0 : len));
>>>       > +}
>>>       > +
>>>       > +bool mg_json_get_num(struct mg_str json, const char *path,
>>>      double *v) {
>>>       > + int n, toklen, found = 0;
>>>       > + if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
>>>       > + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <=
>>>      '9'))) {
>>>       > + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
>>>       > + found = 1;
>>>       > + }
>>>       > + return found;
>>>       > +}
>>>       > +
>>>       > +bool mg_json_get_bool(struct mg_str json, const char *path, bool
>>>      *v) {
>>>       > + int found = 0, off = mg_json_get(json, path, NULL);
>>>       > + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
>>>       > + if (v != NULL) *v = json.buf[off] == 't';
>>>       > + found = 1;
>>>       > + }
>>>       > + return found;
>>>       > +}
>>>       > +
>>>       > +bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
>>>       > + size_t i, j;
>>>       > + for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
>>>       > + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
>>>       > + // \uXXXX escape. We process simple one-byte chars \u00xx
>>>      within ASCII
>>>       > + // range. More complex chars would require dragging in a UTF8
>>>      library,
>>>       > + // which is too much for us
>>>       > + if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
>>>       > + sizeof(uint8_t)) == false)
>>>       > + return false;
>>>       > + i += 5;
>>>       > + } else if (s.buf[i] == '\\' && i + 1 < s.len) {
>>>       > + char c = json_esc(s.buf[i + 1], 0);
>>>       > + if (c == 0) return false;
>>>       > + to[j] = c;
>>>       > + i++;
>>>       > + } else {
>>>       > + to[j] = s.buf[i];
>>>       > + }
>>>       > + }
>>>       > + if (j >= n) return false;
>>>       > + if (n > 0) to[j] = '\0';
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +char *mg_json_get_str(struct mg_str json, const char *path) {
>>>       > + char *result = NULL;
>>>       > + int len = 0, off = mg_json_get(json, path, &len);
>>>       > + if (off >= 0 && len > 1 && json.buf[off] == '"') {
>>>       > + if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
>>>       > + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len -
>>>      2)),
>>>       > + result, (size_t) len)) {
>>>       > + free(result);
>>>       > + result = NULL;
>>>       > + }
>>>       > + }
>>>       > + return result;
>>>       > +}
>>>       > +
>>>       > +char *mg_json_get_b64(struct mg_str json, const char *path, int
>>>      *slen) {
>>>       > + char *result = NULL;
>>>       > + int len = 0, off = mg_json_get(json, path, &len);
>>>       > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
>>>       > + (result = (char *) calloc(1, (size_t) len)) != NULL) {
>>>       > + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len -
>>>      2), result,
>>>       > + (size_t) len);
>>>       > + if (slen != NULL) *slen = (int) k;
>>>       > + }
>>>       > + return result;
>>>       > +}
>>>       > +
>>>       > +char *mg_json_get_hex(struct mg_str json, const char *path, int
>>>      *slen) {
>>>       > + char *result = NULL;
>>>       > + int len = 0, off = mg_json_get(json, path, &len);
>>>       > + if (off >= 0 && json.buf[off] == '"' && len > 1 &&
>>>       > + (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
>>>       > + int i;
>>>       > + for (i = 0; i < len - 2; i += 2) {
>>>       > + mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16,
>>>      &result[i >> 1],
>>>       > + sizeof(uint8_t));
>>>       > + }
>>>       > + result[len / 2 - 1] = '\0';
>>>       > + if (slen != NULL) *slen = len / 2 - 1;
>>>       > + }
>>>       > + return result;
>>>       > +}
>>>       > +
>>>       > +long mg_json_get_long(struct mg_str json, const char *path, long
>>>      dflt) {
>>>       > + double dv;
>>>       > + long result = dflt;
>>>       > + if (mg_json_get_num(json, path, &dv)) result = (long) dv;
>>>       > + return result;
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/log.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +int mg_log_level = MG_LL_INFO;
>>>       > +static mg_pfn_t s_log_func = mg_pfn_stdout;
>>>       > +static void *s_log_func_param = NULL;
>>>       > +
>>>       > +void mg_log_set_fn(mg_pfn_t fn, void *param) {
>>>       > + s_log_func = fn;
>>>       > + s_log_func_param = param;
>>>       > +}
>>>       > +
>>>       > +static void logc(unsigned char c) {
>>>       > + s_log_func((char) c, s_log_func_param);
>>>       > +}
>>>       > +
>>>       > +static void logs(const char *buf, size_t len) {
>>>       > + size_t i;
>>>       > + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
>>>       > +}
>>>       > +
>>>       > +#if MG_ENABLE_CUSTOM_LOG
>>>       > +// Let user define their own mg_log_prefix() and mg_log()
>>>       > +#else
>>>       > +void mg_log_prefix(int level, const char *file, int line, const
>>>      char *fname) {
>>>       > + const char *p = strrchr(file, '/');
>>>       > + char buf[41];
>>>       > + size_t n;
>>>       > + if (p == NULL) p = strrchr(file, '\\');
>>>       > + n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s",
>>>      mg_millis(), level,
>>>       > + p == NULL ? file : p + 1, line, fname);
>>>       > + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
>>>       > + while (n < sizeof(buf)) buf[n++] = ' ';
>>>       > + logs(buf, n - 1);
>>>       > +}
>>>       > +
>>>       > +void mg_log(const char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + logs("\r\n", 2);
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +static unsigned char nibble(unsigned c) {
>>>       > + return (unsigned char) (c < 10 ? c + '0' : c + 'W');
>>>       > +}
>>>       > +
>>>       > +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
>>>       > +void mg_hexdump(const void *buf, size_t len) {
>>>       > + const unsigned char *p = (const unsigned char *) buf;
>>>       > + unsigned char ascii[16], alen = 0;
>>>       > + size_t i;
>>>       > + for (i = 0; i < len; i++) {
>>>       > + if ((i % 16) == 0) {
>>>       > + // Print buffered ascii chars
>>>       > + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
>>>      alen = 0;
>>>       > + // Print hex address, then \t
>>>       > + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
>>>       > + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
>>>       > + }
>>>       > + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
>>>      nibbles, e.g. c5
>>>       > + logc(' '); // Space after hex number
>>>       > + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
>>>      buf
>>>       > + }
>>>       > + while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
>>>       > + logs(" ", 2), logs((char *) ascii, 16), logc('\n');
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/md5.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +// This code implements the MD5 message-digest algorithm.
>>>       > +// The algorithm is due to Ron Rivest. This code was
>>>       > +// written by Colin Plumb in 1993, no copyright is claimed.
>>>       > +// This code is in the public domain; do with it what you wish.
>>>       > +//
>>>       > +// Equivalent code is available from RSA Data Security, Inc.
>>>       > +// This code has been tested against that, and is equivalent,
>>>       > +// except that you don't need to include two pages of legalese
>>>       > +// with every copy.
>>>       > +//
>>>       > +// To compute the message digest of a chunk of bytes, declare an
>>>       > +// MD5Context structure, pass it to MD5Init, call MD5Update as
>>>       > +// needed on buffers full of bytes, and then call MD5Final, which
>>>       > +// will fill a supplied 16-byte array with the digest.
>>>       > +
>>>       > +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
>>>       > +
>>>       > +static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
>>>       > + if (MG_BIG_ENDIAN) {
>>>       > + do {
>>>       > + uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
>>>       > + ((unsigned) buf[1] << 8 | buf[0]);
>>>       > + *(uint32_t *) buf = t;
>>>       > + buf += 4;
>>>       > + } while (--longs);
>>>       > + } else {
>>>       > + (void) buf, (void) longs; // Little endian. Do nothing
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +#define F1(x, y, z) (z ^ (x & (y ^ z)))
>>>       > +#define F2(x, y, z) F1(z, x, y)
>>>       > +#define F3(x, y, z) (x ^ y ^ z)
>>>       > +#define F4(x, y, z) (y ^ (x | ~z))
>>>       > +
>>>       > +#define MD5STEP(f, w, x, y, z, data, s) \
>>>       > + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
>>>       > +
>>>       > +/*
>>>       > + * Start MD5 accumulation. Set bit count to 0 and buffer to
>>>      mysterious
>>>       > + * initialization constants.
>>>       > + */
>>>       > +void mg_md5_init(mg_md5_ctx *ctx) {
>>>       > + ctx->buf[0] = 0x67452301;
>>>       > + ctx->buf[1] = 0xefcdab89;
>>>       > + ctx->buf[2] = 0x98badcfe;
>>>       > + ctx->buf[3] = 0x10325476;
>>>       > +
>>>       > + ctx->bits[0] = 0;
>>>       > + ctx->bits[1] = 0;
>>>       > +}
>>>       > +
>>>       > +static void mg_md5_transform(uint32_t buf[4], uint32_t const
>>>      in[16]) {
>>>       > + uint32_t a, b, c, d;
>>>       > +
>>>       > + a = buf[0];
>>>       > + b = buf[1];
>>>       > + c = buf[2];
>>>       > + d = buf[3];
>>>       > +
>>>       > + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
>>>       > + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
>>>       > + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
>>>       > + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
>>>       > + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
>>>       > + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
>>>       > + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
>>>       > + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
>>>       > + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
>>>       > + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
>>>       > + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
>>>       > + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
>>>       > + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
>>>       > + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
>>>       > + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
>>>       > + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
>>>       > +
>>>       > + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
>>>       > + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
>>>       > + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
>>>       > + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
>>>       > + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
>>>       > + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
>>>       > + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
>>>       > + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
>>>       > + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
>>>       > + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
>>>       > + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
>>>       > + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
>>>       > + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
>>>       > + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
>>>       > + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
>>>       > + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
>>>       > +
>>>       > + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
>>>       > + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
>>>       > + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
>>>       > + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
>>>       > + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
>>>       > + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
>>>       > + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
>>>       > + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
>>>       > + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
>>>       > + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
>>>       > + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
>>>       > + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
>>>       > + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
>>>       > + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
>>>       > + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
>>>       > + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
>>>       > +
>>>       > + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
>>>       > + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
>>>       > + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
>>>       > + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
>>>       > + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
>>>       > + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
>>>       > + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
>>>       > + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
>>>       > + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
>>>       > + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
>>>       > + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
>>>       > + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
>>>       > + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
>>>       > + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
>>>       > + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
>>>       > + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
>>>       > +
>>>       > + buf[0] += a;
>>>       > + buf[1] += b;
>>>       > + buf[2] += c;
>>>       > + buf[3] += d;
>>>       > +}
>>>       > +
>>>       > +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
>>>      size_t len) {
>>>       > + uint32_t t;
>>>       > +
>>>       > + t = ctx->bits[0];
>>>       > + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
>>>      ctx->bits[1]++;
>>>       > + ctx->bits[1] += (uint32_t) len >> 29;
>>>       > +
>>>       > + t = (t >> 3) & 0x3f;
>>>       > +
>>>       > + if (t) {
>>>       > + unsigned char *p = (unsigned char *) ctx->in + t;
>>>       > +
>>>       > + t = 64 - t;
>>>       > + if (len < t) {
>>>       > + memcpy(p, buf, len);
>>>       > + return;
>>>       > + }
>>>       > + memcpy(p, buf, t);
>>>       > + mg_byte_reverse(ctx->in, 16);
>>>       > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > + buf += t;
>>>       > + len -= t;
>>>       > + }
>>>       > +
>>>       > + while (len >= 64) {
>>>       > + memcpy(ctx->in, buf, 64);
>>>       > + mg_byte_reverse(ctx->in, 16);
>>>       > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > + buf += 64;
>>>       > + len -= 64;
>>>       > + }
>>>       > +
>>>       > + memcpy(ctx->in, buf, len);
>>>       > +}
>>>       > +
>>>       > +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
>>>       > + unsigned count;
>>>       > + unsigned char *p;
>>>       > + uint32_t *a;
>>>       > +
>>>       > + count = (ctx->bits[0] >> 3) & 0x3F;
>>>       > +
>>>       > + p = ctx->in + count;
>>>       > + *p++ = 0x80;
>>>       > + count = 64 - 1 - count;
>>>       > + if (count < 8) {
>>>       > + memset(p, 0, count);
>>>       > + mg_byte_reverse(ctx->in, 16);
>>>       > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > + memset(ctx->in, 0, 56);
>>>       > + } else {
>>>       > + memset(p, 0, count - 8);
>>>       > + }
>>>       > + mg_byte_reverse(ctx->in, 14);
>>>       > +
>>>       > + a = (uint32_t *) ctx->in;
>>>       > + a[14] = ctx->bits[0];
>>>       > + a[15] = ctx->bits[1];
>>>       > +
>>>       > + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > + mg_byte_reverse((unsigned char *) ctx->buf, 4);
>>>       > + memcpy(digest, ctx->buf, 16);
>>>       > + memset((char *) ctx, 0, sizeof(*ctx));
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/mqtt.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define MQTT_CLEAN_SESSION 0x02
>>>       > +#define MQTT_HAS_WILL 0x04
>>>       > +#define MQTT_WILL_RETAIN 0x20
>>>       > +#define MQTT_HAS_PASSWORD 0x40
>>>       > +#define MQTT_HAS_USER_NAME 0x80
>>>       > +
>>>       > +struct mg_mqtt_pmap {
>>>       > + uint8_t id;
>>>       > + uint8_t type;
>>>       > +};
>>>       > +
>>>       > +static const struct mg_mqtt_pmap s_prop_map[] = {
>>>       > + {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
>>>       > + {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
>>>       > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
>>>       > + {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
>>>       > + {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
>>>       > + {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
>>>       > + {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
>>>       > + {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
>>>       > + {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
>>>       > + {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
>>>       > + {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
>>>       > + {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
>>>       > + {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
>>>       > + {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE,
>>>      MQTT_PROP_TYPE_BYTE},
>>>       > + {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
>>>       > +
>>>       > +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
>>>      uint8_t flags,
>>>       > + uint32_t len) {
>>>       > + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
>>>       > + buf[0] = (uint8_t) ((cmd << 4) | flags);
>>>       > + do {
>>>       > + *vlen = len % 0x80;
>>>       > + len /= 0x80;
>>>       > + if (len > 0) *vlen |= 0x80;
>>>       > + vlen++;
>>>       > + } while (len > 0 && vlen < &buf[sizeof(buf)]);
>>>       > + mg_send(c, buf, (size_t) (vlen - buf));
>>>       > +}
>>>       > +
>>>       > +static void mg_send_u16(struct mg_connection *c, uint16_t value) {
>>>       > + mg_send(c, &value, sizeof(value));
>>>       > +}
>>>       > +
>>>       > +static void mg_send_u32(struct mg_connection *c, uint32_t value) {
>>>       > + mg_send(c, &value, sizeof(value));
>>>       > +}
>>>       > +
>>>       > +static uint8_t varint_size(size_t length) {
>>>       > + uint8_t bytes_needed = 0;
>>>       > + do {
>>>       > + bytes_needed++;
>>>       > + length /= 0x80;
>>>       > + } while (length > 0);
>>>       > + return bytes_needed;
>>>       > +}
>>>       > +
>>>       > +static size_t encode_varint(uint8_t *buf, size_t value) {
>>>       > + size_t len = 0;
>>>       > +
>>>       > + do {
>>>       > + uint8_t b = (uint8_t) (value % 128);
>>>       > + value /= 128;
>>>       > + if (value > 0) b |= 0x80;
>>>       > + buf[len++] = b;
>>>       > + } while (value > 0);
>>>       > +
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static size_t decode_varint(const uint8_t *buf, size_t len,
>>>      size_t *value) {
>>>       > + size_t multiplier = 1, offset;
>>>       > + *value = 0;
>>>       > +
>>>       > + for (offset = 0; offset < 4 && offset < len; offset++) {
>>>       > + uint8_t encoded_byte = buf[offset];
>>>       > + *value += (encoded_byte & 0x7f) * multiplier;
>>>       > + multiplier *= 128;
>>>       > +
>>>       > + if ((encoded_byte & 0x80) == 0) return offset + 1;
>>>       > + }
>>>       > +
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +static int mqtt_prop_type_by_id(uint8_t prop_id) {
>>>       > + size_t i, num_properties = sizeof(s_prop_map) /
>>>      sizeof(s_prop_map[0]);
>>>       > + for (i = 0; i < num_properties; ++i) {
>>>       > + if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
>>>       > + }
>>>       > + return -1; // Property ID not found
>>>       > +}
>>>       > +
>>>       > +// Returns the size of the properties section, without the
>>>       > +// size of the content's length
>>>       > +static size_t get_properties_length(struct mg_mqtt_prop *props,
>>>      size_t count) {
>>>       > + size_t i, size = 0;
>>>       > + for (i = 0; i < count; i++) {
>>>       > + size++; // identifier
>>>       > + switch (mqtt_prop_type_by_id(props[i].id)) {
>>>       > + case MQTT_PROP_TYPE_STRING_PAIR:
>>>       > + size += (uint32_t) (props[i].val.len + props[i].key.len +
>>>       > + 2 * sizeof(uint16_t));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_STRING:
>>>       > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BINARY_DATA:
>>>       > + size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_VARIABLE_INT:
>>>       > + size += varint_size((uint32_t) props[i].iv);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_INT:
>>>       > + size += (uint32_t) sizeof(uint32_t);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_SHORT:
>>>       > + size += (uint32_t) sizeof(uint16_t);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BYTE:
>>>       > + size += (uint32_t) sizeof(uint8_t);
>>>       > + break;
>>>       > + default:
>>>       > + return size; // cannot parse further down
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + return size;
>>>       > +}
>>>       > +
>>>       > +// returns the entire size of the properties section, including the
>>>       > +// size of the variable length of the content
>>>       > +static size_t get_props_size(struct mg_mqtt_prop *props, size_t
>>>      count) {
>>>       > + size_t size = get_properties_length(props, count);
>>>       > + size += varint_size(size);
>>>       > + return size;
>>>       > +}
>>>       > +
>>>       > +static void mg_send_mqtt_properties(struct mg_connection *c,
>>>       > + struct mg_mqtt_prop *props, size_t nprops) {
>>>       > + size_t total_size = get_properties_length(props, nprops);
>>>       > + uint8_t buf_v[4] = {0, 0, 0, 0};
>>>       > + uint8_t buf[4] = {0, 0, 0, 0};
>>>       > + size_t i, len = encode_varint(buf, total_size);
>>>       > +
>>>       > + mg_send(c, buf, (size_t) len);
>>>       > + for (i = 0; i < nprops; i++) {
>>>       > + mg_send(c, &props[i].id, sizeof(props[i].id));
>>>       > + switch (mqtt_prop_type_by_id(props[i].id)) {
>>>       > + case MQTT_PROP_TYPE_STRING_PAIR:
>>>       > + mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
>>>       > + mg_send(c, props[i].key.buf, props[i].key.len);
>>>       > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>>>       > + mg_send(c, props[i].val.buf, props[i].val.len);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BYTE:
>>>       > + mg_send(c, &props[i].iv, sizeof(uint8_t));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_SHORT:
>>>       > + mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_INT:
>>>       > + mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_STRING:
>>>       > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>>>       > + mg_send(c, props[i].val.buf, props[i].val.len);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BINARY_DATA:
>>>       > + mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
>>>       > + mg_send(c, props[i].val.buf, props[i].val.len);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_VARIABLE_INT:
>>>       > + len = encode_varint(buf_v, props[i].iv);
>>>       > + mg_send(c, buf_v, (size_t) len);
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct
>>>      mg_mqtt_prop *prop,
>>>       > + size_t ofs) {
>>>       > + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
>>>       > + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
>>>       > + size_t new_pos = ofs, len;
>>>       > + prop->id = i[0];
>>>       > +
>>>       > + if (ofs >= msg->dgram.len || ofs >= msg->props_start +
>>>      msg->props_size)
>>>       > + return 0;
>>>       > + i++, new_pos++;
>>>       > +
>>>       > + switch (mqtt_prop_type_by_id(prop->id)) {
>>>       > + case MQTT_PROP_TYPE_STRING_PAIR:
>>>       > + prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>>>       > + prop->key.buf = (char *) i + 2;
>>>       > + i += 2 + prop->key.len;
>>>       > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>>>       > + prop->val.buf = (char *) i + 2;
>>>       > + new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BYTE:
>>>       > + prop->iv = (uint8_t) i[0];
>>>       > + new_pos++;
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_SHORT:
>>>       > + prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>>>       > + new_pos += sizeof(uint16_t);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_INT:
>>>       > + prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
>>>       > + ((uint32_t) i[2] << 8) | i[3];
>>>       > + new_pos += sizeof(uint32_t);
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_STRING:
>>>       > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>>>       > + prop->val.buf = (char *) i + 2;
>>>       > + new_pos += 2 + prop->val.len;
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_BINARY_DATA:
>>>       > + prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
>>>       > + prop->val.buf = (char *) i + 2;
>>>       > + new_pos += 2 + prop->val.len;
>>>       > + break;
>>>       > + case MQTT_PROP_TYPE_VARIABLE_INT:
>>>       > + len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
>>>       > + new_pos = (!len) ? 0 : new_pos + len;
>>>       > + break;
>>>       > + default:
>>>       > + new_pos = 0;
>>>       > + }
>>>       > +
>>>       > + return new_pos;
>>>       > +}
>>>       > +
>>>       > +void mg_mqtt_login(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts) {
>>>       > + char client_id[21];
>>>       > + struct mg_str cid = opts->client_id;
>>>       > + size_t total_len = 7 + 1 + 2 + 2;
>>>       > + uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
>>>       > +
>>>       > + if (cid.len == 0) {
>>>       > + mg_random_str(client_id, sizeof(client_id) - 1);
>>>       > + client_id[sizeof(client_id) - 1] = '\0';
>>>       > + cid = mg_str(client_id);
>>>       > + }
>>>       > +
>>>       > + if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
>>>      (3.1.1)
>>>       > + c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
>>>       > + hdr[7] = (uint8_t) ((opts->qos & 3) << 3); // Connection flags
>>>       > + if (opts->user.len > 0) {
>>>       > + total_len += 2 + (uint32_t) opts->user.len;
>>>       > + hdr[7] |= MQTT_HAS_USER_NAME;
>>>       > + }
>>>       > + if (opts->pass.len > 0) {
>>>       > + total_len += 2 + (uint32_t) opts->pass.len;
>>>       > + hdr[7] |= MQTT_HAS_PASSWORD;
>>>       > + }
>>>       > + if (opts->topic.len > 0) { // allow zero-length msgs,
>>>      message.len is size_t
>>>       > + total_len += 4 + (uint32_t) opts->topic.len + (uint32_t)
>>>      opts->message.len;
>>>       > + hdr[7] |= MQTT_HAS_WILL;
>>>       > + }
>>>       > + if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
>>>       > + if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
>>>       > + total_len += (uint32_t) cid.len;
>>>       > + if (c->is_mqtt5) {
>>>       > + total_len += get_props_size(opts->props, opts->num_props);
>>>       > + if (hdr[7] & MQTT_HAS_WILL)
>>>       > + total_len += get_props_size(opts->will_props,
>>>      opts->num_will_props);
>>>       > + }
>>>       > +
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
>>>       > + mg_send(c, hdr, sizeof(hdr));
>>>       > + // keepalive == 0 means "do not disconnect us!"
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
>>>       > +
>>>       > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>>>      opts->num_props);
>>>       > +
>>>       > + mg_send_u16(c, mg_htons((uint16_t) cid.len));
>>>       > + mg_send(c, cid.buf, cid.len);
>>>       > +
>>>       > + if (hdr[7] & MQTT_HAS_WILL) {
>>>       > + if (c->is_mqtt5)
>>>       > + mg_send_mqtt_properties(c, opts->will_props,
>>>      opts->num_will_props);
>>>       > +
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>>>       > + mg_send(c, opts->topic.buf, opts->topic.len);
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
>>>       > + mg_send(c, opts->message.buf, opts->message.len);
>>>       > + }
>>>       > + if (opts->user.len > 0) {
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
>>>       > + mg_send(c, opts->user.buf, opts->user.len);
>>>       > + }
>>>       > + if (opts->pass.len > 0) {
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
>>>       > + mg_send(c, opts->pass.buf, opts->pass.len);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts) {
>>>       > + uint16_t id = opts->retransmit_id;
>>>       > + uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) |
>>>      (opts->retain ? 1 : 0));
>>>       > + size_t len = 2 + opts->topic.len + opts->message.len;
>>>       > + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
>>>       > + (char *) opts->topic.buf, (int) opts->message.len,
>>>       > + (char *) opts->message.buf));
>>>       > + if (opts->qos > 0) len += 2;
>>>       > + if (c->is_mqtt5) len += get_props_size(opts->props,
>>>      opts->num_props);
>>>       > +
>>>       > + if (opts->qos > 0 && id != 0) flags |= 1 << 3;
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>>>       > + mg_send(c, opts->topic.buf, opts->topic.len);
>>>       > + if (opts->qos > 0) { // need to send 'id' field
>>>       > + if (id == 0) { // generate new one if not resending
>>>       > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>>>       > + id = c->mgr->mqtt_id;
>>>       > + }
>>>       > + mg_send_u16(c, mg_htons(id));
>>>       > + }
>>>       > +
>>>       > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>>>      opts->num_props);
>>>       > +
>>>       > + if (opts->message.len > 0) mg_send(c, opts->message.buf,
>>>      opts->message.len);
>>>       > + return id;
>>>       > +}
>>>       > +
>>>       > +void mg_mqtt_sub(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts) {
>>>       > + uint8_t qos_ = opts->qos & 3;
>>>       > + size_t plen = c->is_mqtt5 ? get_props_size(opts->props,
>>>      opts->num_props) : 0;
>>>       > + size_t len = 2 + opts->topic.len + 2 + 1 + plen;
>>>       > +
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
>>>       > + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>>>       > + mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>>>       > + if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props,
>>>      opts->num_props);
>>>       > +
>>>       > + mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
>>>       > + mg_send(c, opts->topic.buf, opts->topic.len);
>>>       > + mg_send(c, &qos_, sizeof(qos_));
>>>       > +}
>>>       > +
>>>       > +int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
>>>       > + struct mg_mqtt_message *m) {
>>>       > + uint8_t lc = 0, *p, *end;
>>>       > + uint32_t n = 0, len_len = 0;
>>>       > +
>>>       > + memset(m, 0, sizeof(*m));
>>>       > + m->dgram.buf = (char *) buf;
>>>       > + if (len < 2) return MQTT_INCOMPLETE;
>>>       > + m->cmd = (uint8_t) (buf[0] >> 4);
>>>       > + m->qos = (buf[0] >> 1) & 3;
>>>       > +
>>>       > + n = len_len = 0;
>>>       > + p = (uint8_t *) buf + 1;
>>>       > + while ((size_t) (p - buf) < len) {
>>>       > + lc = *((uint8_t *) p++);
>>>       > + n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
>>>       > + len_len++;
>>>       > + if (!(lc & 0x80)) break;
>>>       > + if (len_len >= 4) return MQTT_MALFORMED;
>>>       > + }
>>>       > + end = p + n;
>>>       > + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
>>>       > + m->dgram.len = (size_t) (end - buf);
>>>       > +
>>>       > + switch (m->cmd) {
>>>       > + case MQTT_CMD_CONNACK:
>>>       > + if (end - p < 2) return MQTT_MALFORMED;
>>>       > + m->ack = p[1];
>>>       > + break;
>>>       > + case MQTT_CMD_PUBACK:
>>>       > + case MQTT_CMD_PUBREC:
>>>       > + case MQTT_CMD_PUBREL:
>>>       > + case MQTT_CMD_PUBCOMP:
>>>       > + case MQTT_CMD_SUBSCRIBE:
>>>       > + case MQTT_CMD_SUBACK:
>>>       > + case MQTT_CMD_UNSUBSCRIBE:
>>>       > + case MQTT_CMD_UNSUBACK:
>>>       > + if (p + 2 > end) return MQTT_MALFORMED;
>>>       > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > + p += 2;
>>>       > + break;
>>>       > + case MQTT_CMD_PUBLISH: {
>>>       > + if (p + 2 > end) return MQTT_MALFORMED;
>>>       > + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > + m->topic.buf = (char *) p + 2;
>>>       > + p += 2 + m->topic.len;
>>>       > + if (p > end) return MQTT_MALFORMED;
>>>       > + if (m->qos > 0) {
>>>       > + if (p + 2 > end) return MQTT_MALFORMED;
>>>       > + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > + p += 2;
>>>       > + }
>>>       > + if (p > end) return MQTT_MALFORMED;
>>>       > + if (version == 5 && p + 2 < end) {
>>>       > + len_len =
>>>       > + (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
>>>       > + if (!len_len) return MQTT_MALFORMED;
>>>       > + m->props_start = (size_t) (p + len_len - buf);
>>>       > + p += len_len + m->props_size;
>>>       > + }
>>>       > + if (p > end) return MQTT_MALFORMED;
>>>       > + m->data.buf = (char *) p;
>>>       > + m->data.len = (size_t) (end - p);
>>>       > + break;
>>>       > + }
>>>       > + default:
>>>       > + break;
>>>       > + }
>>>       > + return MQTT_OK;
>>>       > +}
>>>       > +
>>>       > +static void mqtt_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + if (ev == MG_EV_READ) {
>>>       > + for (;;) {
>>>       > + uint8_t version = c->is_mqtt5 ? 5 : 4;
>>>       > + struct mg_mqtt_message mm;
>>>       > + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
>>>       > + if (rc == MQTT_MALFORMED) {
>>>       > + MG_ERROR(("%lu MQTT malformed message", c->id));
>>>       > + c->is_closing = 1;
>>>       > + break;
>>>       > + } else if (rc == MQTT_OK) {
>>>       > + MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
>>>       > + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
>>>       > + switch (mm.cmd) {
>>>       > + case MQTT_CMD_CONNACK:
>>>       > + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
>>>       > + if (mm.ack == 0) {
>>>       > + MG_DEBUG(("%lu Connected", c->id));
>>>       > + } else {
>>>       > + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
>>>       > + c->is_closing = 1;
>>>       > + }
>>>       > + break;
>>>       > + case MQTT_CMD_PUBLISH: {
>>>       > + /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
>>>       > + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
>>>       > + if (mm.qos > 0) {
>>>       > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>>>       > + uint32_t remaining_len = sizeof(id);
>>>       > + if (c->is_mqtt5) remaining_len += 2; // 3.4.2
>>>       > +
>>>       > + mg_mqtt_send_header(
>>>       > + c,
>>>       > + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
>>>       > + 0, remaining_len);
>>>       > + mg_send(c, &id, sizeof(id));
>>>       > +
>>>       > + if (c->is_mqtt5) {
>>>       > + uint16_t zero = 0;
>>>       > + mg_send(c, &zero, sizeof(zero));
>>>       > + }
>>>       > + }
>>>       > + mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff
>>>       > + break;
>>>       > + }
>>>       > + case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable
>>>      header rc
>>>       > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>>>       > + uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
>>>       > + mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2
>>>       > + break;
>>>       > + }
>>>       > + case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable
>>>      header rc
>>>       > + uint16_t id = mg_ntohs(mm.id <http://mm.id>);
>>>       > + uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
>>>       > + mg_send(c, &id, sizeof(id));
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + mg_call(c, MG_EV_MQTT_CMD, &mm);
>>>       > + mg_iobuf_del(&c->recv, 0, mm.dgram.len);
>>>       > + } else {
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > + (void) ev_data;
>>>       > +}
>>>       > +
>>>       > +void mg_mqtt_ping(struct mg_connection *nc) {
>>>       > + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
>>>       > +}
>>>       > +
>>>       > +void mg_mqtt_pong(struct mg_connection *nc) {
>>>       > + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
>>>       > +}
>>>       > +
>>>       > +void mg_mqtt_disconnect(struct mg_connection *c,
>>>       > + const struct mg_mqtt_opts *opts) {
>>>       > + size_t len = 0;
>>>       > + if (c->is_mqtt5) len = 1 + get_props_size(opts->props,
>>>      opts->num_props);
>>>       > + mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
>>>       > +
>>>       > + if (c->is_mqtt5) {
>>>       > + uint8_t zero = 0;
>>>       > + mg_send(c, &zero, sizeof(zero)); // reason code
>>>       > + mg_send_mqtt_properties(c, opts->props, opts->num_props);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > + const struct mg_mqtt_opts *opts,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>>>       > + if (c != NULL) {
>>>       > + struct mg_mqtt_opts empty;
>>>       > + memset(&empty, 0, sizeof(empty));
>>>       > + mg_mqtt_login(c, opts == NULL ? &empty : opts);
>>>       > + c->pfn = mqtt_cb;
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>>>       > + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/net.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +size_t mg_vprintf(struct mg_connection *c, const char *fmt,
>>>      va_list *ap) {
>>>       > + size_t old = c->send.len;
>>>       > + mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>>>       > + return c->send.len - old;
>>>       > +}
>>>       > +
>>>       > +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
>>>       > + size_t len = 0;
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + len = mg_vprintf(c, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
>>>       > + uint32_t localhost = mg_htonl(0x7f000001);
>>>       > + if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
>>>       > + memcpy(addr->ip, &localhost, sizeof(uint32_t));
>>>       > + addr->is_ip6 = false;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
>>>       > + if (str.len > 0) return false;
>>>       > + memset(addr->ip, 0, sizeof(addr->ip));
>>>       > + addr->is_ip6 = false;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
>>>       > + uint8_t data[4] = {0, 0, 0, 0};
>>>       > + size_t i, num_dots = 0;
>>>       > + for (i = 0; i < str.len; i++) {
>>>       > + if (str.buf[i] >= '0' && str.buf[i] <= '9') {
>>>       > + int octet = data[num_dots] * 10 + (str.buf[i] - '0');
>>>       > + if (octet > 255) return false;
>>>       > + data[num_dots] = (uint8_t) octet;
>>>       > + } else if (str.buf[i] == '.') {
>>>       > + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return
>>>      false;
>>>       > + num_dots++;
>>>       > + } else {
>>>       > + return false;
>>>       > + }
>>>       > + }
>>>       > + if (num_dots != 3 || str.buf[i - 1] == '.') return false;
>>>       > + memcpy(&addr->ip, data, sizeof(data));
>>>       > + addr->is_ip6 = false;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
>>>       > + int i;
>>>       > + uint32_t ipv4;
>>>       > + if (str.len < 14) return false;
>>>       > + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] !=
>>>      ':') return false;
>>>       > + for (i = 2; i < 6; i++) {
>>>       > + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
>>>       > + }
>>>       > + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
>>>       > + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return
>>>      false;
>>>       > + memcpy(&ipv4, addr->ip, sizeof(ipv4));
>>>       > + memset(addr->ip, 0, sizeof(addr->ip));
>>>       > + addr->ip[10] = addr->ip[11] = 255;
>>>       > + memcpy(&addr->ip[12], &ipv4, 4);
>>>       > + addr->is_ip6 = true;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
>>>       > + size_t i, j = 0, n = 0, dc = 42;
>>>       > + addr->scope_id = 0;
>>>       > + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
>>>       > + if (mg_v4mapped(str, addr)) return true;
>>>       > + for (i = 0; i < str.len; i++) {
>>>       > + if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
>>>       > + (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
>>>       > + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
>>>       > + unsigned long val; // TODO(): This loops on chars, refactor
>>>       > + if (i > j + 3) return false;
>>>       > + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1),
>>>      &str.buf[j]));
>>>       > + mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val,
>>>      sizeof(val));
>>>       > + addr->ip[n] = (uint8_t) ((val >> 8) & 255);
>>>       > + addr->ip[n + 1] = (uint8_t) (val & 255);
>>>       > + } else if (str.buf[i] == ':') {
>>>       > + j = i + 1;
>>>       > + if (i > 0 && str.buf[i - 1] == ':') {
>>>       > + dc = n; // Double colon
>>>       > + if (i > 1 && str.buf[i - 2] == ':') return false;
>>>       > + } else if (i > 0) {
>>>       > + n += 2;
>>>       > + }
>>>       > + if (n > 14) return false;
>>>       > + addr->ip[n] = addr->ip[n + 1] = 0; // For trailing ::
>>>       > + } else if (str.buf[i] == '%') { // Scope ID, last in string
>>>       > + return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i -
>>>      1), 10,
>>>       > + &addr->scope_id, sizeof(uint8_t));
>>>       > + } else {
>>>       > + return false;
>>>       > + }
>>>       > + }
>>>       > + if (n < 14 && dc == 42) return false;
>>>       > + if (n < 14) {
>>>       > + memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
>>>       > + memset(&addr->ip[dc], 0, 14 - n);
>>>       > + }
>>>       > +
>>>       > + addr->is_ip6 = true;
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +bool mg_aton(struct mg_str str, struct mg_addr *addr) {
>>>       > + // MG_INFO(("[%.*s]", (int) str.len, str.buf));
>>>       > + return mg_atone(str, addr) || mg_atonl(str, addr) ||
>>>      mg_aton4(str, addr) ||
>>>       > + mg_aton6(str, addr);
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
>>>       > + struct mg_connection *c =
>>>       > + (struct mg_connection *) calloc(1, sizeof(*c) +
>>>      mgr->extraconnsize);
>>>       > + if (c != NULL) {
>>>       > + c->mgr = mgr;
>>>       > + c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
>>>       > + c->id = ++mgr->nextid;
>>>       > + MG_PROF_INIT(c);
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +void mg_close_conn(struct mg_connection *c) {
>>>       > + mg_resolve_cancel(c); // Close any pending DNS query
>>>       > + LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
>>>       > + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
>>>       > + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
>>>       > + // Order of operations is important. `MG_EV_CLOSE` event must
>>>      be fired
>>>       > + // before we deallocate received data, see #1331
>>>       > + mg_call(c, MG_EV_CLOSE, NULL);
>>>       > + MG_DEBUG(("%lu %ld closed", c->id, c->fd));
>>>       > + MG_PROF_DUMP(c);
>>>       > + MG_PROF_FREE(c);
>>>       > +
>>>       > + mg_tls_free(c);
>>>       > + mg_iobuf_free(&c->recv);
>>>       > + mg_iobuf_free(&c->send);
>>>       > + mg_iobuf_free(&c->rtls);
>>>       > + mg_bzero((unsigned char *) c, sizeof(*c));
>>>       > + free(c);
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
>>>      *url,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = NULL;
>>>       > + if (url == NULL || url[0] == '\0') {
>>>       > + MG_ERROR(("null url"));
>>>       > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > + MG_ERROR(("OOM"));
>>>       > + } else {
>>>       > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > + c->is_udp = (strncmp(url, "udp:", 4) == 0);
>>>       > + c->fd = (void *) (size_t) MG_INVALID_SOCKET;
>>>       > + c->fn = fn;
>>>       > + c->is_client = true;
>>>       > + c->fn_data = fn_data;
>>>       > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
>>>       > + mg_call(c, MG_EV_OPEN, (void *) url);
>>>       > + mg_resolve(c, url);
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
>>>      *url,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = NULL;
>>>       > + if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > + MG_ERROR(("OOM %s", url));
>>>       > + } else if (!mg_open_listener(c, url)) {
>>>       > + MG_ERROR(("Failed: %s, errno %d", url, errno));
>>>       > + MG_PROF_FREE(c);
>>>       > + free(c);
>>>       > + c = NULL;
>>>       > + } else {
>>>       > + c->is_listening = 1;
>>>       > + c->is_udp = strncmp(url, "udp:", 4) == 0;
>>>       > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > + c->fn = fn;
>>>       > + c->fn_data = fn_data;
>>>       > + mg_call(c, MG_EV_OPEN, NULL);
>>>       > + if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must
>>>       > + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
>>>       > + mg_event_handler_t fn, void *fn_data) {
>>>       > + struct mg_connection *c = mg_alloc_conn(mgr);
>>>       > + if (c != NULL) {
>>>       > + c->fd = (void *) (size_t) fd;
>>>       > + c->fn = fn;
>>>       > + c->fn_data = fn_data;
>>>       > + MG_EPOLL_ADD(c);
>>>       > + mg_call(c, MG_EV_OPEN, NULL);
>>>       > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>>>      milliseconds,
>>>       > + unsigned flags, void (*fn)(void *), void *arg) {
>>>       > + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
>>>       > + if (t != NULL) {
>>>       > + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
>>>       > + t->id = mgr->timerid++;
>>>       > + }
>>>       > + return t;
>>>       > +}
>>>       > +
>>>       > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > + if (c->rtls.len == 0) return MG_IO_WAIT;
>>>       > + if (len > c->rtls.len) len = c->rtls.len;
>>>       > + memcpy(buf, c->rtls.buf, len);
>>>       > + mg_iobuf_del(&c->rtls, 0, len);
>>>       > + return (long) len;
>>>       > +}
>>>       > +
>>>       > +void mg_mgr_free(struct mg_mgr *mgr) {
>>>       > + struct mg_connection *c;
>>>       > + struct mg_timer *tmp, *t = mgr->timers;
>>>       > + while (t != NULL) tmp = t->next, free(t), t = tmp;
>>>       > + mgr->timers = NULL; // Important. Next call to poll won't touch
>>>      timers
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
>>>       > + mg_mgr_poll(mgr, 0);
>>>       > +#if MG_ENABLE_FREERTOS_TCP
>>>       > + FreeRTOS_DeleteSocketSet(mgr->ss);
>>>       > +#endif
>>>       > + MG_DEBUG(("All connections closed"));
>>>       > +#if MG_ENABLE_EPOLL
>>>       > + if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
>>>       > +#endif
>>>       > + mg_tls_ctx_free(mgr);
>>>       > +}
>>>       > +
>>>       > +void mg_mgr_init(struct mg_mgr *mgr) {
>>>       > + memset(mgr, 0, sizeof(*mgr));
>>>       > +#if MG_ENABLE_EPOLL
>>>       > + if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
>>>       > + MG_ERROR(("epoll_create1 errno %d", errno));
>>>       > +#else
>>>       > + mgr->epoll_fd = -1;
>>>       > +#endif
>>>       > +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > + // clang-format off
>>>       > + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
>>>       > + // clang-format on
>>>       > +#elif MG_ENABLE_FREERTOS_TCP
>>>       > + mgr->ss = FreeRTOS_CreateSocketSet();
>>>       > +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
>>>       > + // Ignore SIGPIPE signal, so if client cancels the request, it
>>>       > + // won't kill the whole process.
>>>       > + signal(SIGPIPE, SIG_IGN);
>>>       > +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
>>>       > + MG_TCPIP_DRIVER_INIT(mgr);
>>>       > +#endif
>>>       > + mgr->pipe = MG_INVALID_SOCKET;
>>>       > + mgr->dnstimeout = 3000;
>>>       > + mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
>>>       > + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
>>>       > + mg_tls_ctx_init(mgr);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/net_builtin.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
>>>       > +#define MG_EPHEMERAL_PORT_BASE 32768
>>>       > +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
>>>       > +
>>>       > +#ifndef MIP_TCP_KEEPALIVE_MS
>>>       > +#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
>>>       > +#endif
>>>       > +
>>>       > +#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
>>>       > +#define MIP_TCP_ARP_MS 100 // Timeout for ARP response
>>>       > +#define MIP_TCP_SYN_MS 15000 // Timeout for connection
>>>      establishment
>>>       > +#define MIP_TCP_FIN_MS 1000 // Timeout for closing connection
>>>       > +#define MIP_TCP_WIN 6000 // TCP window size
>>>       > +
>>>       > +struct connstate {
>>>       > + uint32_t seq, ack; // TCP seq/ack counters
>>>       > + uint64_t timer; // TCP keep-alive / ACK timer
>>>       > + uint32_t acked; // Last ACK-ed number
>>>       > + size_t unacked; // Not acked bytes
>>>       > + uint8_t mac[6]; // Peer MAC address
>>>       > + uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
>>>       > +#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
>>>      send keepalive
>>>       > +#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
>>>      soon
>>>       > +#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response
>>>       > +#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
>>>       > +#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating
>>>      the connection
>>>       > + uint8_t tmiss; // Number of keep-alive misses
>>>       > + struct mg_iobuf raw; // For TLS only. Incoming raw data
>>>       > +};
>>>       > +
>>>       > +#pragma pack(push, 1)
>>>       > +
>>>       > +struct lcp {
>>>       > + uint8_t addr, ctrl, proto[2], code, id, len[2];
>>>       > +};
>>>       > +
>>>       > +struct eth {
>>>       > + uint8_t dst[6]; // Destination MAC address
>>>       > + uint8_t src[6]; // Source MAC address
>>>       > + uint16_t type; // Ethernet type
>>>       > +};
>>>       > +
>>>       > +struct ip {
>>>       > + uint8_t ver; // Version
>>>       > + uint8_t tos; // Unused
>>>       > + uint16_t len; // Length
>>>       > + uint16_t id; // Unused
>>>       > + uint16_t frag; // Fragmentation
>>>       > +#define IP_FRAG_OFFSET_MSK 0xFF1F
>>>       > +#define IP_MORE_FRAGS_MSK 0x20
>>>       > + uint8_t ttl; // Time to live
>>>       > + uint8_t proto; // Upper level protocol
>>>       > + uint16_t csum; // Checksum
>>>       > + uint32_t src; // Source IP
>>>       > + uint32_t dst; // Destination IP
>>>       > +};
>>>       > +
>>>       > +struct ip6 {
>>>       > + uint8_t ver; // Version
>>>       > + uint8_t opts[3]; // Options
>>>       > + uint16_t len; // Length
>>>       > + uint8_t proto; // Upper level protocol
>>>       > + uint8_t ttl; // Time to live
>>>       > + uint8_t src[16]; // Source IP
>>>       > + uint8_t dst[16]; // Destination IP
>>>       > +};
>>>       > +
>>>       > +struct icmp {
>>>       > + uint8_t type;
>>>       > + uint8_t code;
>>>       > + uint16_t csum;
>>>       > +};
>>>       > +
>>>       > +struct arp {
>>>       > + uint16_t fmt; // Format of hardware address
>>>       > + uint16_t pro; // Format of protocol address
>>>       > + uint8_t hlen; // Length of hardware address
>>>       > + uint8_t plen; // Length of protocol address
>>>       > + uint16_t op; // Operation
>>>       > + uint8_t sha[6]; // Sender hardware address
>>>       > + uint32_t spa; // Sender protocol address
>>>       > + uint8_t tha[6]; // Target hardware address
>>>       > + uint32_t tpa; // Target protocol address
>>>       > +};
>>>       > +
>>>       > +struct tcp {
>>>       > + uint16_t sport; // Source port
>>>       > + uint16_t dport; // Destination port
>>>       > + uint32_t seq; // Sequence number
>>>       > + uint32_t ack; // Acknowledgement number
>>>       > + uint8_t off; // Data offset
>>>       > + uint8_t flags; // TCP flags
>>>       > +#define TH_FIN 0x01
>>>       > +#define TH_SYN 0x02
>>>       > +#define TH_RST 0x04
>>>       > +#define TH_PUSH 0x08
>>>       > +#define TH_ACK 0x10
>>>       > +#define TH_URG 0x20
>>>       > +#define TH_ECE 0x40
>>>       > +#define TH_CWR 0x80
>>>       > + uint16_t win; // Window
>>>       > + uint16_t csum; // Checksum
>>>       > + uint16_t urp; // Urgent pointer
>>>       > +};
>>>       > +
>>>       > +struct udp {
>>>       > + uint16_t sport; // Source port
>>>       > + uint16_t dport; // Destination port
>>>       > + uint16_t len; // UDP length
>>>       > + uint16_t csum; // UDP checksum
>>>       > +};
>>>       > +
>>>       > +struct dhcp {
>>>       > + uint8_t op, htype, hlen, hops;
>>>       > + uint32_t xid;
>>>       > + uint16_t secs, flags;
>>>       > + uint32_t ciaddr, yiaddr, siaddr, giaddr;
>>>       > + uint8_t hwaddr[208];
>>>       > + uint32_t magic;
>>>       > + uint8_t options[32];
>>>       > +};
>>>       > +
>>>       > +#pragma pack(pop)
>>>       > +
>>>       > +struct pkt {
>>>       > + struct mg_str raw; // Raw packet data
>>>       > + struct mg_str pay; // Payload data
>>>       > + struct eth *eth;
>>>       > + struct llc *llc;
>>>       > + struct arp *arp;
>>>       > + struct ip *ip;
>>>       > + struct ip6 *ip6;
>>>       > + struct icmp *icmp;
>>>       > + struct tcp *tcp;
>>>       > + struct udp *udp;
>>>       > + struct dhcp *dhcp;
>>>       > +};
>>>       > +
>>>       > +static void send_syn(struct mg_connection *c);
>>>       > +
>>>       > +static void mkpay(struct pkt *pkt, void *p) {
>>>       > + pkt->pay =
>>>       > + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] -
>>>      (char *) p));
>>>       > +}
>>>       > +
>>>       > +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
>>>       > + size_t i;
>>>       > + const uint8_t *p = (const uint8_t *) buf;
>>>       > + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t)
>>>      (p[i] << 8);
>>>       > + return sum;
>>>       > +}
>>>       > +
>>>       > +static uint16_t csumfin(uint32_t sum) {
>>>       > + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
>>>       > + return mg_htons(~sum & 0xffff);
>>>       > +}
>>>       > +
>>>       > +static uint16_t ipcsum(const void *buf, size_t len) {
>>>       > + uint32_t sum = csumup(0, buf, len);
>>>       > + return csumfin(sum);
>>>       > +}
>>>       > +
>>>       > +static void settmout(struct mg_connection *c, uint8_t type) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS
>>>       > + : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
>>>       > + : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
>>>       > + : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
>>>       > + : MIP_TCP_KEEPALIVE_MS;
>>>       > + s->timer = ifp->now + n;
>>>       > + s->ttype = type;
>>>       > + MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
>>>       > +}
>>>       > +
>>>       > +static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
>>>       > + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
>>>       > + if (n == len) ifp->nsent++;
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
>>>       > + struct eth *eth = (struct eth *) ifp->tx.buf;
>>>       > + struct arp *arp = (struct arp *) (eth + 1);
>>>       > + memset(eth->dst, 255, sizeof(eth->dst));
>>>       > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
>>>       > + eth->type = mg_htons(0x806);
>>>       > + memset(arp, 0, sizeof(*arp));
>>>       > + arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
>>>       > + arp->plen = 4;
>>>       > + arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
>>>       > + memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
>>>       > + ether_output(ifp, PDIFF(eth, arp + 1));
>>>       > +}
>>>       > +
>>>       > +static void onstatechange(struct mg_tcpip_if *ifp) {
>>>       > + if (ifp->state == MG_TCPIP_STATE_READY) {
>>>       > + MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
>>>       > + MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
>>>       > + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
>>>       > + arp_ask(ifp, ifp->gw);
>>>       > + } else if (ifp->state == MG_TCPIP_STATE_UP) {
>>>       > + MG_ERROR(("Link up"));
>>>       > + srand((unsigned int) mg_millis());
>>>       > + } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
>>>       > + MG_ERROR(("Link down"));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>>>       > + uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
>>>       > + size_t plen) {
>>>       > + struct eth *eth = (struct eth *) ifp->tx.buf;
>>>       > + struct ip *ip = (struct ip *) (eth + 1);
>>>       > + memcpy(eth->dst, mac_dst, sizeof(eth->dst));
>>>       > + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC
>>>       > + eth->type = mg_htons(0x800);
>>>       > + memset(ip, 0, sizeof(*ip));
>>>       > + ip->ver = 0x45; // Version 4, header length 5 words
>>>       > + ip->frag = 0x40; // Don't fragment
>>>       > + ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
>>>       > + ip->ttl = 64;
>>>       > + ip->proto = proto;
>>>       > + ip->src = ip_src;
>>>       > + ip->dst = ip_dst;
>>>       > + ip->csum = ipcsum(ip, sizeof(*ip));
>>>       > + return ip;
>>>       > +}
>>>       > +
>>>       > +static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>>>      uint32_t ip_src,
>>>       > + uint16_t sport, uint32_t ip_dst, uint16_t dport,
>>>       > + const void *buf, size_t len) {
>>>       > + struct ip *ip =
>>>       > + tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
>>>       > + struct udp *udp = (struct udp *) (ip + 1);
>>>       > + // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
>>>       > + udp->sport = sport;
>>>       > + udp->dport = dport;
>>>       > + udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
>>>       > + udp->csum = 0;
>>>       > + uint32_t cs = csumup(0, udp, sizeof(*udp));
>>>       > + cs = csumup(cs, buf, len);
>>>       > + cs = csumup(cs, &ip->src, sizeof(ip->src));
>>>       > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>>>       > + cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
>>>       > + udp->csum = csumfin(cs);
>>>       > + memmove(udp + 1, buf, len);
>>>       > + // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
>>>       > + ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
>>>      sizeof(*udp) + len);
>>>       > +}
>>>       > +
>>>       > +static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
>>>      uint32_t ip_src,
>>>       > + uint32_t ip_dst, uint8_t *opts, size_t optslen,
>>>       > + bool ciaddr) {
>>>       > + // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
>>>      <https://datatracker.ietf.org/doc/html/rfc2132#section-9.6>
>>>       > + struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>>>       > + dhcp.magic = mg_htonl(0x63825363);
>>>       > + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
>>>       > + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
>>>       > + memcpy(&dhcp.options, opts, optslen);
>>>       > + if (ciaddr) dhcp.ciaddr = ip_src;
>>>       > + tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst,
>>>      mg_htons(67), &dhcp,
>>>       > + sizeof(dhcp));
>>>       > +}
>>>       > +
>>>       > +static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
>>>       > +
>>>       > +// RFC-2131 #4.3.6, #4.4.1
>>>       > +static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp,
>>>      uint32_t ip_req,
>>>       > + uint32_t ip_srv) {
>>>       > + uint8_t opts[] = {
>>>       > + 53, 1, 3, // Type: DHCP request
>>>       > + 55, 2, 1, 3, // GW and mask
>>>       > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
>>>       > + 54, 4, 0, 0, 0, 0, // DHCP server ID
>>>       > + 50, 4, 0, 0, 0, 0, // Requested IP
>>>       > + 255 // End of options
>>>       > + };
>>>       > + memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
>>>       > + memcpy(opts + 20, &ip_req, sizeof(ip_req));
>>>       > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
>>>      sizeof(opts), false);
>>>       > + MG_DEBUG(("DHCP req sent"));
>>>       > +}
>>>       > +
>>>       > +// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
>>>       > +static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t
>>>      *mac_dst,
>>>       > + uint32_t ip_src, uint32_t ip_dst) {
>>>       > + uint8_t opts[] = {
>>>       > + 53, 1, 3, // Type: DHCP request
>>>       > + 255 // End of options
>>>       > + };
>>>       > + tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
>>>       > + MG_DEBUG(("DHCP req sent"));
>>>       > +}
>>>       > +
>>>       > +static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
>>>       > + uint8_t opts[] = {
>>>       > + 53, 1, 1, // Type: DHCP discover
>>>       > + 55, 2, 1, 3, // Parameters: ip, mask
>>>       > + 255 // End of options
>>>       > + };
>>>       > + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts,
>>>      sizeof(opts), false);
>>>       > + MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac,
>>>      ifp->mac));
>>>       > +}
>>>       > +
>>>       > +static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
>>>      pkt *pkt,
>>>       > + bool lsn) {
>>>       > + struct mg_connection *c = NULL;
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > + if (c->is_arplooking && pkt->arp &&
>>>       > + memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
>>>       > + break;
>>>       > + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
>>>      break;
>>>       > + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
>>>       > + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
>>>       > + break;
>>>       > + }
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
>>>       > + // ARP request. Make a response, then send
>>>       > + // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op),
>>>      mg_print_ip4,
>>>       > + // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
>>>       > + struct eth *eth = (struct eth *) ifp->tx.buf;
>>>       > + struct arp *arp = (struct arp *) (eth + 1);
>>>       > + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
>>>       > + memcpy(eth->src, ifp->mac, sizeof(eth->src));
>>>       > + eth->type = mg_htons(0x806);
>>>       > + *arp = *pkt->arp;
>>>       > + arp->op = mg_htons(2);
>>>       > + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
>>>       > + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
>>>       > + arp->tpa = pkt->arp->spa;
>>>       > + arp->spa = ifp->ip;
>>>       > + MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa,
>>>      mg_print_mac,
>>>       > + &ifp->mac));
>>>       > + ether_output(ifp, PDIFF(eth, arp + 1));
>>>       > + } else if (pkt->arp->op == mg_htons(2)) {
>>>       > + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
>>>      0) return;
>>>       > + if (pkt->arp->spa == ifp->gw) {
>>>       > + // Got response for the GW ARP request. Set ifp->gwmac
>>>       > + memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
>>>       > + } else {
>>>       > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>>>       > + if (c != NULL && c->is_arplooking) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
>>>       > + MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4,
>>>      c->rem.ip,
>>>       > + mg_print_mac, s->mac));
>>>       > + c->is_arplooking = 0;
>>>       > + send_syn(c);
>>>       > + settmout(c, MIP_TTYPE_SYN);
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + // MG_DEBUG(("ICMP %d", (int) len));
>>>       > + if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
>>>      ifp->ip) {
>>>       > + size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
>>>      sizeof(struct icmp);
>>>       > + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
>>>       > + if (plen > space) plen = space;
>>>       > + struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip,
>>>      pkt->ip->src,
>>>       > + sizeof(struct icmp) + plen);
>>>       > + struct icmp *icmp = (struct icmp *) (ip + 1);
>>>       > + memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
>>>       > + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX
>>>       > + icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
>>>       > + ether_output(ifp, hlen + plen);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt
>>>      *pkt) {
>>>       > + uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
>>>       > + uint8_t msgtype = 0, state = ifp->state;
>>>       > + // perform size check first, then access fields
>>>       > + uint8_t *p = pkt->dhcp->options,
>>>       > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
>>>       > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>>>       > + if (memcmp(&pkt->dhcp->xid, ifp->mac + 2,
>>>      sizeof(pkt->dhcp->xid))) return;
>>>       > + while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9
>>>       > + if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
>>>      Mask
>>>       > + memcpy(&mask, p + 2, sizeof(mask));
>>>       > + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
>>>      { // GW
>>>       > + memcpy(&gw, p + 2, sizeof(gw));
>>>       > + ip = pkt->dhcp->yiaddr;
>>>       > + } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
>>>       > + memcpy(&lease, p + 2, sizeof(lease));
>>>       > + lease = mg_ntohl(lease);
>>>       > + } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) { // Msg Type
>>>       > + msgtype = p[2];
>>>       > + }
>>>       > + p += p[1] + 2;
>>>       > + }
>>>       > + // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
>>>       > + if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP
>>>       > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
>>>       > + } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP &&
>>>      ip && gw &&
>>>       > + lease) { // DHCPOFFER
>>>       > + // select IP, (4.4.1) (fallback to IP source addr on foul play)
>>>       > + tx_dhcp_request_sel(ifp, ip,
>>>       > + pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
>>>       > + ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
>>>       > + } else if (msgtype == 5) { // DHCPACK
>>>       > + if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { //
>>>      got an IP
>>>       > + ifp->lease_expire = ifp->now + lease * 1000;
>>>       > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
>>>      1000));
>>>       > + // assume DHCP server = router until ARP resolves
>>>       > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
>>>       > + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
>>>       > + ifp->state = MG_TCPIP_STATE_READY; // BOUND state
>>>       > + uint64_t rand;
>>>       > + mg_random(&rand, sizeof(rand));
>>>       > + srand((unsigned int) (rand + mg_millis()));
>>>       > + } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip)
>>>      { // renew
>>>       > + ifp->lease_expire = ifp->now + lease * 1000;
>>>       > + MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire /
>>>      1000));
>>>       > + } // TODO(): accept provided T1/T2 and store server IP for
>>>      renewal (4.4)
>>>       > + }
>>>       > + if (ifp->state != state) onstatechange(ifp);
>>>       > +}
>>>       > +
>>>       > +// Simple DHCP server that assigns a next IP address: ifp->ip + 1
>>>       > +static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt
>>>      *pkt) {
>>>       > + uint8_t op = 0, *p = pkt->dhcp->options,
>>>       > + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
>>>       > + if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>>>       > + // struct dhcp *req = pkt->dhcp;
>>>       > + struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>>>       > + res.yiaddr = ifp->ip;
>>>       > + ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
>>>       > + while (p + 1 < end && p[0] != 255) { // Parse options
>>>       > + if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
>>>       > + op = p[2];
>>>       > + }
>>>       > + p += p[1] + 2;
>>>       > + }
>>>       > + if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
>>>       > + uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
>>>      DHCP ACK
>>>       > + uint8_t opts[] = {
>>>       > + 53, 1, msg, // Message type
>>>       > + 1, 4, 0, 0, 0, 0, // Subnet mask
>>>       > + 54, 4, 0, 0, 0, 0, // Server ID
>>>       > + 12, 3, 'm', 'i', 'p', // Host name: "mip"
>>>       > + 51, 4, 255, 255, 255, 255, // Lease time
>>>       > + 255 // End of options
>>>       > + };
>>>       > + memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
>>>       > + memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
>>>       > + memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
>>>       > + memcpy(&res.options, opts, sizeof(opts));
>>>       > + res.magic = pkt->dhcp->magic;
>>>       > + res.xid = pkt->dhcp->xid;
>>>       > + if (ifp->enable_get_gateway) {
>>>       > + ifp->gw = res.yiaddr;
>>>       > + memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
>>>       > + }
>>>       > + tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
>>>       > + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
>>>       > + if (c == NULL) {
>>>       > + // No UDP listener on this port. Should send ICMP, but keep
>>>      silent.
>>>       > + } else {
>>>       > + c->rem.port = pkt->udp->sport;
>>>       > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
>>>       > + if (c->recv.len >= MG_MAX_RECV_SIZE) {
>>>       > + mg_error(c, "max_recv_buf_size reached");
>>>       > + } else if (c->recv.size - c->recv.len < pkt->pay.len &&
>>>       > + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
>>>       > + mg_error(c, "oom");
>>>       > + } else {
>>>       > + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
>>>       > + c->recv.len += pkt->pay.len;
>>>       > + mg_call(c, MG_EV_READ, &pkt->pay.len);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac,
>>>      uint32_t dst_ip,
>>>       > + uint8_t flags, uint16_t sport, uint16_t dport,
>>>       > + uint32_t seq, uint32_t ack, const void *buf, size_t len) {
>>>       > +#if 0
>>>       > + uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460,
>>>      SACK permitted
>>>       > + if (flags & TH_SYN) {
>>>       > + // Handshake? Set MSS
>>>       > + buf = opts;
>>>       > + len = sizeof(opts);
>>>       > + }
>>>       > +#endif
>>>       > + struct ip *ip =
>>>       > + tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
>>>       > + struct tcp *tcp = (struct tcp *) (ip + 1);
>>>       > + memset(tcp, 0, sizeof(*tcp));
>>>       > + if (buf != NULL && len) memmove(tcp + 1, buf, len);
>>>       > + tcp->sport = sport;
>>>       > + tcp->dport = dport;
>>>       > + tcp->seq = seq;
>>>       > + tcp->ack = ack;
>>>       > + tcp->flags = flags;
>>>       > + tcp->win = mg_htons(MIP_TCP_WIN);
>>>       > + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
>>>       > + // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header
>>>      size 28 bytes
>>>       > +
>>>       > + uint32_t cs = 0;
>>>       > + uint16_t n = (uint16_t) (sizeof(*tcp) + len);
>>>       > + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
>>>      (n & 255)};
>>>       > + cs = csumup(cs, tcp, n);
>>>       > + cs = csumup(cs, &ip->src, sizeof(ip->src));
>>>       > + cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>>>       > + cs = csumup(cs, pseudo, sizeof(pseudo));
>>>       > + tcp->csum = csumfin(cs);
>>>       > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4,
>>>      &ip->src,
>>>       > + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
>>>       > + mg_ntohs(tcp->dport), tcp->flags, len));
>>>       > + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
>>>       > + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
>>>       > +}
>>>       > +
>>>       > +static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
>>>       > + uint8_t flags, uint32_t seq, const void *buf,
>>>       > + size_t len) {
>>>       > + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
>>>       > + return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags,
>>>      pkt->tcp->dport,
>>>       > + pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
>>>       > + buf, len);
>>>       > +}
>>>       > +
>>>       > +static struct mg_connection *accept_conn(struct mg_connection *lsn,
>>>       > + struct pkt *pkt) {
>>>       > + struct mg_connection *c = mg_alloc_conn(lsn->mgr);
>>>       > + if (c == NULL) {
>>>       > + MG_ERROR(("OOM"));
>>>       > + return NULL;
>>>       > + }
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>>>      mg_ntohl(pkt->tcp->seq);
>>>       > + memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
>>>       > + settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > + memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
>>>       > + c->rem.port = pkt->tcp->sport;
>>>       > + MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
>>>       > + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
>>>       > + c->is_accepted = 1;
>>>       > + c->is_hexdumping = lsn->is_hexdumping;
>>>       > + c->pfn = lsn->pfn;
>>>       > + c->loc = lsn->loc;
>>>       > + c->pfn_data = lsn->pfn_data;
>>>       > + c->fn = lsn->fn;
>>>       > + c->fn_data = lsn->fn_data;
>>>       > + mg_call(c, MG_EV_OPEN, NULL);
>>>       > + mg_call(c, MG_EV_ACCEPT, NULL);
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +static size_t trim_len(struct mg_connection *c, size_t len) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60,
>>>      udp_h_len = 8;
>>>       > + size_t max_headers_len =
>>>       > + eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len :
>>>      tcp_max_h_len);
>>>       > + size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len
>>>      - eth_h_len;
>>>       > +
>>>       > + // If the frame exceeds the available buffer, trim the length
>>>       > + if (len + max_headers_len > ifp->tx.len) {
>>>       > + len = ifp->tx.len - max_headers_len;
>>>       > + }
>>>       > + // Ensure the MTU isn't lower than the minimum allowed value
>>>       > + if (ifp->mtu < min_mtu) {
>>>       > + MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
>>>       > + ifp->mtu = (uint16_t) min_mtu;
>>>       > + }
>>>       > + // If the total packet size exceeds the MTU, trim the length
>>>       > + if (len + max_headers_len - eth_h_len > ifp->mtu) {
>>>       > + len = ifp->mtu - max_headers_len + eth_h_len;
>>>       > + if (c->is_udp) {
>>>       > + MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + uint32_t dst_ip = *(uint32_t *) c->rem.ip;
>>>       > + len = trim_len(c, len);
>>>       > + if (c->is_udp) {
>>>       > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port,
>>>      buf, len);
>>>       > + } else {
>>>       > + size_t sent =
>>>       > + tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port,
>>>      c->rem.port,
>>>       > + mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
>>>       > + if (sent == 0) {
>>>       > + return MG_IO_WAIT;
>>>       > + } else if (sent == (size_t) -1) {
>>>       > + return MG_IO_ERR;
>>>       > + } else {
>>>       > + s->seq += (uint32_t) len;
>>>       > + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > + }
>>>       > + }
>>>       > + return (long) len;
>>>       > +}
>>>       > +
>>>       > +static void handle_tls_recv(struct mg_connection *c, struct
>>>      mg_iobuf *io) {
>>>       > + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
>>>       > + if (n == MG_IO_ERR) {
>>>       > + mg_error(c, "TLS recv error");
>>>       > + } else if (n > 0) {
>>>       > + // Decrypted successfully - trigger MG_EV_READ
>>>       > + io->len += (size_t) n;
>>>       > + mg_call(c, MG_EV_READ, &n);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void read_conn(struct mg_connection *c, struct pkt *pkt) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
>>>       > + uint32_t seq = mg_ntohl(pkt->tcp->seq);
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + if (pkt->tcp->flags & TH_FIN) {
>>>       > + // If we initiated the closure, we reply with ACK upon
>>>      receiving FIN
>>>       > + // If we didn't initiate it, we reply with FIN as part of the
>>>      normal TCP
>>>       > + // closure process
>>>       > + uint8_t flags = TH_ACK;
>>>       > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
>>>       > + if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
>>>       > + if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
>>>       > + s->seq++; // Yes. Increment our SEQ
>>>       > + } else { // Otherwise,
>>>       > + s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
>>>       > + }
>>>       > + } else {
>>>       > + flags |= TH_FIN;
>>>       > + c->is_draining = 1;
>>>       > + settmout(c, MIP_TTYPE_FIN);
>>>       > + }
>>>       > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
>>>       > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
>>>      "", 0);
>>>       > + } else if (pkt->pay.len == 0) {
>>>       > + // TODO(cpq): handle this peer's ACK
>>>       > + } else if (seq != s->ack) {
>>>       > + uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) +
>>>      pkt->pay.len);
>>>       > + if (s->ack == ack) {
>>>       > + MG_VERBOSE(("ignoring duplicate pkt"));
>>>       > + } else {
>>>       > + MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
>>>       > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
>>>      TH_ACK,
>>>       > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
>>>       > + 0);
>>>       > + }
>>>       > + } else if (io->size - io->len < pkt->pay.len &&
>>>       > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>>>       > + mg_error(c, "oom");
>>>       > + } else {
>>>       > + // Copy TCP payload into the IO buffer. If the connection is
>>>      plain text,
>>>       > + // we copy to c->recv. If the connection is TLS, this data is
>>>      encrypted,
>>>       > + // therefore we copy that encrypted data to the c->rtls
>>>      iobuffer instead,
>>>       > + // and then call mg_tls_recv() to decrypt it. NOTE:
>>>      mg_tls_recv() will
>>>       > + // call back mg_io_recv() which grabs raw data from c->rtls
>>>       > + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
>>>       > + io->len += pkt->pay.len;
>>>       > +
>>>       > + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
>>>      s->ack));
>>>       > + // Advance ACK counter
>>>       > + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
>>>       > + s->unacked += pkt->pay.len;
>>>       > + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
>>>       > + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
>>>       > + // Send ACK immediately
>>>       > + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
>>>       > + tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip,
>>>      TH_ACK,
>>>       > + c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack),
>>>      NULL,
>>>       > + 0);
>>>       > + s->unacked = 0;
>>>       > + s->acked = s->ack;
>>>       > + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c,
>>>      MIP_TTYPE_KEEPALIVE);
>>>       > + } else {
>>>       > + // if not already running, setup a timer to send an ACK later
>>>       > + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
>>>       > + }
>>>       > +
>>>       > + if (c->is_tls && c->is_tls_hs) {
>>>       > + mg_tls_handshake(c);
>>>       > + } else if (c->is_tls) {
>>>       > + // TLS connection. Make room for decrypted data in c->recv
>>>       > + io = &c->recv;
>>>       > + if (io->size - io->len < pkt->pay.len &&
>>>       > + !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>>>       > + mg_error(c, "oom");
>>>       > + } else {
>>>       > + // Decrypt data directly into c->recv
>>>       > + handle_tls_recv(c, io);
>>>       > + }
>>>       > + } else {
>>>       > + // Plain text connection, data is already in c->recv, trigger
>>>       > + // MG_EV_READ
>>>       > + mg_call(c, MG_EV_READ, &pkt->pay.len);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>>>       > + struct connstate *s = c == NULL ? NULL : (struct connstate *)
>>>      (c + 1);
>>>       > +#if 0
>>>       > + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
>>>      pkt->pay.len));
>>>       > +#endif
>>>       > + if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN
>>>      | TH_ACK)) {
>>>       > + s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>>>      mg_ntohl(pkt->tcp->seq) + 1;
>>>       > + tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > + c->is_connecting = 0; // Client connected
>>>       > + settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > + mg_call(c, MG_EV_CONNECT, NULL); // Let user know
>>>       > + } else if (c != NULL && c->is_connecting && pkt->tcp->flags !=
>>>      TH_ACK) {
>>>       > + // mg_hexdump(pkt->raw.buf, pkt->raw.len);
>>>       > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > + } else if (c != NULL && pkt->tcp->flags & TH_RST) {
>>>       > + mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>>>       > + } else if (c != NULL) {
>>>       > +#if 0
>>>       > + MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
>>>       > + mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
>>>       > + mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
>>>       > + mg_hexdump(pkt->pay.buf, pkt->pay.len);
>>>       > +#endif
>>>       > + s->tmiss = 0; // Reset missed keep-alive counter
>>>       > + if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
>>>       > + settmout(c,
>>>       > + MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
>>>       > + read_conn(c, pkt); // Override timer with ACK timeout if needed
>>>       > + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
>>>       > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > + } else if (pkt->tcp->flags & TH_RST) {
>>>       > + if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>>>       > + // ignore RST if not connected
>>>       > + } else if (pkt->tcp->flags & TH_SYN) {
>>>       > + // Use peer's source port as ISN, in order to recognise the
>>>      handshake
>>>       > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
>>>       > + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
>>>       > + } else if (pkt->tcp->flags & TH_FIN) {
>>>       > + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
>>>      + 1U) {
>>>       > + accept_conn(c, pkt);
>>>       > + } else if (!c->is_accepted) { // no peer
>>>       > + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > + } else {
>>>       > + // MG_VERBOSE(("dropped silently.."));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag &
>>>      IP_FRAG_OFFSET_MSK) {
>>>       > + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
>>>       > + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
>>>       > + struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>>>       > + if (c) mg_error(c, "Received fragmented packet");
>>>       > + } else if (pkt->ip->proto == 1) {
>>>       > + pkt->icmp = (struct icmp *) (pkt->ip + 1);
>>>       > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>>>       > + mkpay(pkt, pkt->icmp + 1);
>>>       > + rx_icmp(ifp, pkt);
>>>       > + } else if (pkt->ip->proto == 17) {
>>>       > + pkt->udp = (struct udp *) (pkt->ip + 1);
>>>       > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
>>>       > + mkpay(pkt, pkt->udp + 1);
>>>       > + MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4,
>>>      &pkt->ip->src,
>>>       > + mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
>>>       > + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
>>>       > + if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
>>>       > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>>>       > + mkpay(pkt, pkt->dhcp + 1);
>>>       > + rx_dhcp_client(ifp, pkt);
>>>       > + } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
>>>      mg_htons(67)) {
>>>       > + pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>>>       > + mkpay(pkt, pkt->dhcp + 1);
>>>       > + rx_dhcp_server(ifp, pkt);
>>>       > + } else {
>>>       > + rx_udp(ifp, pkt);
>>>       > + }
>>>       > + } else if (pkt->ip->proto == 6) {
>>>       > + pkt->tcp = (struct tcp *) (pkt->ip + 1);
>>>       > + if (pkt->pay.len < sizeof(*pkt->tcp)) return;
>>>       > + mkpay(pkt, pkt->tcp + 1);
>>>       > + uint16_t iplen = mg_ntohs(pkt->ip->len);
>>>       > + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
>>>       >> 4) * 4U));
>>>       > + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
>>>       > + MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4,
>>>      &pkt->ip->src,
>>>       > + mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
>>>       > + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
>>>       > + rx_tcp(ifp, pkt);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
>>>       > + // MG_DEBUG(("IP %d", (int) len));
>>>       > + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
>>>       > + pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
>>>       > + if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>>>       > + mkpay(pkt, pkt->icmp + 1);
>>>       > + rx_icmp(ifp, pkt);
>>>       > + } else if (pkt->ip6->proto == 17) {
>>>       > + pkt->udp = (struct udp *) (pkt->ip6 + 1);
>>>       > + if (pkt->pay.len < sizeof(*pkt->udp)) return;
>>>       > + // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
>>>       > + // mg_htons(udp->dport)));
>>>       > + mkpay(pkt, pkt->udp + 1);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf,
>>>      size_t len) {
>>>       > + struct pkt pkt;
>>>       > + memset(&pkt, 0, sizeof(pkt));
>>>       > + pkt.raw.buf = (char *) buf;
>>>       > + pkt.raw.len = len;
>>>       > + pkt.eth = (struct eth *) buf;
>>>       > + // mg_hexdump(buf, len > 16 ? 16: len);
>>>       > + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
>>>       > + if (ifp->enable_mac_check &&
>>>       > + memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
>>>       > + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
>>>       > + return;
>>>       > + if (ifp->enable_crc32_check && len > 4) {
>>>       > + len -= 4; // TODO(scaprile): check on bigendian
>>>       > + uint32_t crc = mg_crc32(0, (const char *) buf, len);
>>>       > + if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc)))
>>>      return;
>>>       > + }
>>>       > + if (pkt.eth->type == mg_htons(0x806)) {
>>>       > + pkt.arp = (struct arp *) (pkt.eth + 1);
>>>       > + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
>>>      // Truncated
>>>       > + rx_arp(ifp, &pkt);
>>>       > + } else if (pkt.eth->type == mg_htons(0x86dd)) {
>>>       > + pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
>>>       > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
>>>      // Truncated
>>>       > + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
>>>       > + mkpay(&pkt, pkt.ip6 + 1);
>>>       > + rx_ip6(ifp, &pkt);
>>>       > + } else if (pkt.eth->type == mg_htons(0x800)) {
>>>       > + pkt.ip = (struct ip *) (pkt.eth + 1);
>>>       > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>>>      // Truncated
>>>       > + // Truncate frame to what IP header tells us
>>>       > + if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
>>>      pkt.raw.len) {
>>>       > + pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
>>>       > + }
>>>       > + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>>>      // Truncated
>>>       > + if ((pkt.ip->ver >> 4) != 4) return; // Not IP
>>>       > + mkpay(&pkt, pkt.ip + 1);
>>>       > + rx_ip(ifp, &pkt);
>>>       > + } else {
>>>       > + MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
>>>       > + if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ?
>>>      32 : len);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
>>>       > + struct mg_connection *c;
>>>       > + bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
>>>      1000, now);
>>>       > + ifp->now = now;
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
>>>       > + if (expired_1000ms) {
>>>       > + const char *names[] = {"down", "up", "req", "ready"};
>>>       > + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
>>>       > + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
>>>       > + ifp->ndrop, ifp->nerr));
>>>       > + }
>>>       > +#endif
>>>       > + // Handle physical interface up/down status
>>>       > + if (expired_1000ms && ifp->driver->up) {
>>>       > + bool up = ifp->driver->up(ifp);
>>>       > + bool current = ifp->state != MG_TCPIP_STATE_DOWN;
>>>       > + if (up != current) {
>>>       > + ifp->state = up == false ? MG_TCPIP_STATE_DOWN
>>>       > + : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
>>>       > + : MG_TCPIP_STATE_READY;
>>>       > + if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
>>>       > + onstatechange(ifp);
>>>       > + }
>>>       > + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is
>>>      down"));
>>>       > + }
>>>       > + if (ifp->state == MG_TCPIP_STATE_DOWN) return;
>>>       > +
>>>       > + // DHCP RFC-2131 (4.4)
>>>       > + if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
>>>       > + tx_dhcp_discover(ifp); // INIT (4.4.1)
>>>       > + } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
>>>       > + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
>>>       > + if (ifp->now >= ifp->lease_expire) {
>>>       > + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired,
>>>      release IP
>>>       > + onstatechange(ifp);
>>>       > + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
>>>       > + ((ifp->now / 1000) % 60) == 0) {
>>>       > + // hack: 30 min before deadline, try to rebind (4.3.6) every min
>>>       > + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip,
>>>      0xffffffff);
>>>       > + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
>>>       > + }
>>>       > +
>>>       > + // Read data from the network
>>>       > + if (ifp->driver->rx != NULL) { // Polling driver. We must call it
>>>       > + size_t len =
>>>       > + ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
>>>       > + if (len > 0) {
>>>       > + ifp->nrecv++;
>>>       > + mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
>>>       > + }
>>>       > + } else { // Interrupt-based driver. Fills recv queue itself
>>>       > + char *buf;
>>>       > + size_t len = mg_queue_next(&ifp->recv_queue, &buf);
>>>       > + if (len > 0) {
>>>       > + mg_tcpip_rx(ifp, buf, len);
>>>       > + mg_queue_del(&ifp->recv_queue, len);
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + // Process timeouts
>>>       > + for (c = ifp->mgr->conns; c != NULL; c = c->next) {
>>>       > + if (c->is_udp || c->is_listening || c->is_resolving) continue;
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + if (now > s->timer) {
>>>       > + if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
>>>       > + MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
>>>       > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
>>>       > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>>>       > + s->acked = s->ack;
>>>       > + } else if (s->ttype == MIP_TTYPE_ARP) {
>>>       > + mg_error(c, "ARP timeout");
>>>       > + } else if (s->ttype == MIP_TTYPE_SYN) {
>>>       > + mg_error(c, "Connection timeout");
>>>       > + } else if (s->ttype == MIP_TTYPE_FIN) {
>>>       > + c->is_closing = 1;
>>>       > + continue;
>>>       > + } else {
>>>       > + if (s->tmiss++ > 2) {
>>>       > + mg_error(c, "keepalive");
>>>       > + } else {
>>>       > + MG_VERBOSE(("%lu keepalive", c->id));
>>>       > + tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
>>>       > + mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +// This function executes in interrupt context, thus it should
>>>      copy data
>>>       > +// somewhere fast. Note that newlib's malloc is not thread safe,
>>>      thus use
>>>       > +// our lock-free queue with preallocated buffer to copy data and
>>>      return asap
>>>       > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
>>>      *ifp) {
>>>       > + char *p;
>>>       > + if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
>>>       > + memcpy(p, buf, len);
>>>       > + mg_queue_add(&ifp->recv_queue, len);
>>>       > + ifp->nrecv++;
>>>       > + } else {
>>>       > + ifp->ndrop++;
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
>>>       > + // If MAC address is not set, make a random one
>>>       > + if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
>>>       > + ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
>>>       > + ifp->mac[0] = 0x02; // Locally administered, unicast
>>>       > + mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
>>>       > + MG_INFO(("MAC not set. Generated random: %M", mg_print_mac,
>>>      ifp->mac));
>>>       > + }
>>>       > +
>>>       > + if (ifp->driver->init && !ifp->driver->init(ifp)) {
>>>       > + MG_ERROR(("driver init failed"));
>>>       > + } else {
>>>       > + size_t framesize = 1540;
>>>       > + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len =
>>>      framesize;
>>>       > + if (ifp->recv_queue.size == 0)
>>>       > + ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
>>>       > + ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
>>>       > + ifp->timer_1000ms = mg_millis();
>>>       > + mgr->priv = ifp;
>>>       > + ifp->mgr = mgr;
>>>       > + ifp->mtu = MG_TCPIP_MTU_DEFAULT;
>>>       > + mgr->extraconnsize = sizeof(struct connstate);
>>>       > + if (ifp->ip == 0) ifp->enable_dhcp_client = true;
>>>       > + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast
>>>       > + mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to
>>>      65535
>>>       > + ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from
>>>       > + // MG_EPHEMERAL_PORT_BASE to 65535
>>>       > + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL)
>>>      MG_ERROR(("OOM"));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_tcpip_free(struct mg_tcpip_if *ifp) {
>>>       > + free(ifp->recv_queue.buf);
>>>       > + free(ifp->tx.buf);
>>>       > +}
>>>       > +
>>>       > +static void send_syn(struct mg_connection *c) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port,
>>>      isn, 0, NULL,
>>>       > + 0);
>>>       > +}
>>>       > +
>>>       > +void mg_connect_resolved(struct mg_connection *c) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + c->is_resolving = 0;
>>>       > + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport =
>>>      MG_EPHEMERAL_PORT_BASE;
>>>       > + memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
>>>       > + c->loc.port = mg_htons(ifp->eport++);
>>>       > + MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc,
>>>      mg_print_ip_port,
>>>       > + &c->rem));
>>>       > + mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > + if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip |
>>>      ~ifp->mask))) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast
>>>       > + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip &
>>>      ifp->mask))) {
>>>       > + // If we're in the same LAN, fire an ARP lookup.
>>>       > + MG_DEBUG(("%lu ARP lookup...", c->id));
>>>       > + arp_ask(ifp, rem_ip);
>>>       > + settmout(c, MIP_TTYPE_ARP);
>>>       > + c->is_arplooking = 1;
>>>       > + c->is_connecting = 1;
>>>       > + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1); // 224 to
>>>      239, E0 to EF
>>>       > + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group
>>>       > + memcpy(s->mac, mcastp, 3);
>>>       > + memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb
>>>       > + s->mac[3] &= 0x7F;
>>>       > + } else {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
>>>       > + if (c->is_udp) {
>>>       > + mg_call(c, MG_EV_CONNECT, NULL);
>>>       > + } else {
>>>       > + send_syn(c);
>>>       > + settmout(c, MIP_TTYPE_SYN);
>>>       > + c->is_connecting = 1;
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +bool mg_open_listener(struct mg_connection *c, const char *url) {
>>>       > + c->loc.port = mg_htons(mg_url_port(url));
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static void write_conn(struct mg_connection *c) {
>>>       > + long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
>>>       > + : mg_io_send(c, c->send.buf, c->send.len);
>>>       > + if (len == MG_IO_ERR) {
>>>       > + mg_error(c, "tx err");
>>>       > + } else if (len > 0) {
>>>       > + mg_iobuf_del(&c->send, 0, (size_t) len);
>>>       > + mg_call(c, MG_EV_WRITE, &len);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void init_closure(struct mg_connection *c) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + if (c->is_udp == false && c->is_listening == false &&
>>>       > + c->is_connecting == false) { // For TCP conns,
>>>       > + struct mg_tcpip_if *ifp =
>>>       > + (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port,
>>>      c->rem.port,
>>>       > + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>>>       > + settmout(c, MIP_TTYPE_FIN);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void close_conn(struct mg_connection *c) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + mg_iobuf_free(&s->raw); // For TLS connections, release raw data
>>>       > + mg_close_conn(c);
>>>       > +}
>>>       > +
>>>       > +static bool can_write(struct mg_connection *c) {
>>>       > + return c->is_connecting == 0 && c->is_resolving == 0 &&
>>>      c->send.len > 0 &&
>>>       > + c->is_tls_hs == 0 && c->is_arplooking == 0;
>>>       > +}
>>>       > +
>>>       > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
>>>       > + struct mg_connection *c, *tmp;
>>>       > + uint64_t now = mg_millis();
>>>       > + mg_timer_poll(&mgr->timers, now);
>>>       > + if (ifp == NULL || ifp->driver == NULL) return;
>>>       > + mg_tcpip_poll(ifp, now);
>>>       > + for (c = mgr->conns; c != NULL; c = tmp) {
>>>       > + tmp = c->next;
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + mg_call(c, MG_EV_POLL, &now);
>>>       > + MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
>>>       > + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>>>       > + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>>>       > + if (c->is_tls && mg_tls_pending(c) > 0)
>>>       > + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
>>>       > + if (can_write(c)) write_conn(c);
>>>       > + if (c->is_draining && c->send.len == 0 && s->ttype !=
>>>      MIP_TTYPE_FIN)
>>>       > + init_closure(c);
>>>       > + if (c->is_closing) close_conn(c);
>>>       > + }
>>>       > + (void) ms;
>>>       > +}
>>>       > +
>>>       > +bool mg_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > + struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
>>>       > + bool res = false;
>>>       > + uint32_t rem_ip;
>>>       > + memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
>>>       > + if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
>>>       > + mg_error(c, "net down");
>>>       > + } else if (c->is_udp) {
>>>       > + struct connstate *s = (struct connstate *) (c + 1);
>>>       > + len = trim_len(c, len); // Trimming length if necessary
>>>       > + tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port,
>>>      buf, len);
>>>       > + res = true;
>>>       > + } else {
>>>       > + res = mg_iobuf_add(&c->send, c->send.len, buf, len);
>>>       > + }
>>>       > + return res;
>>>       > +}
>>>       > +#endif // MG_ENABLE_TCPIP
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/ota_dummy.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_OTA == MG_OTA_NONE
>>>       > +bool mg_ota_begin(size_t new_firmware_size) {
>>>       > + (void) new_firmware_size;
>>>       > + return true;
>>>       > +}
>>>       > +bool mg_ota_write(const void *buf, size_t len) {
>>>       > + (void) buf, (void) len;
>>>       > + return true;
>>>       > +}
>>>       > +bool mg_ota_end(void) {
>>>       > + return true;
>>>       > +}
>>>       > +bool mg_ota_commit(void) {
>>>       > + return true;
>>>       > +}
>>>       > +bool mg_ota_rollback(void) {
>>>       > + return true;
>>>       > +}
>>>       > +int mg_ota_status(int fw) {
>>>       > + (void) fw;
>>>       > + return 0;
>>>       > +}
>>>       > +uint32_t mg_ota_crc32(int fw) {
>>>       > + (void) fw;
>>>       > + return 0;
>>>       > +}
>>>       > +uint32_t mg_ota_timestamp(int fw) {
>>>       > + (void) fw;
>>>       > + return 0;
>>>       > +}
>>>       > +size_t mg_ota_size(int fw) {
>>>       > + (void) fw;
>>>       > + return 0;
>>>       > +}
>>>       > +MG_IRAM void mg_ota_boot(void) {
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/ota_esp32.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
>>>       > +
>>>       > +static const esp_partition_t *s_ota_update_partition;
>>>       > +static esp_ota_handle_t s_ota_update_handle;
>>>       > +static bool s_ota_success;
>>>       > +
>>>       > +// Those empty macros do nothing, but mark places in the code
>>>      which could
>>>       > +// potentially trigger a watchdog reboot due to the log flash
>>>      erase operation
>>>       > +#define disable_wdt()
>>>       > +#define enable_wdt()
>>>       > +
>>>       > +bool mg_ota_begin(size_t new_firmware_size) {
>>>       > + if (s_ota_update_partition != NULL) {
>>>       > + MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
>>>       > + return false;
>>>       > + } else {
>>>       > + s_ota_success = false;
>>>       > + disable_wdt();
>>>       > + s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
>>>       > + esp_err_t err = esp_ota_begin(s_ota_update_partition,
>>>      new_firmware_size,
>>>       > + &s_ota_update_handle);
>>>       > + enable_wdt();
>>>       > + MG_DEBUG(("esp_ota_begin(): %d", err));
>>>       > + s_ota_success = (err == ESP_OK);
>>>       > + }
>>>       > + return s_ota_success;
>>>       > +}
>>>       > +
>>>       > +bool mg_ota_write(const void *buf, size_t len) {
>>>       > + disable_wdt();
>>>       > + esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
>>>       > + enable_wdt();
>>>       > + MG_INFO(("esp_ota_write(): %d", err));
>>>       > + s_ota_success = err == ESP_OK;
>>>       > + return s_ota_success;
>>>       > +}
>>>       > +
>>>       > +bool mg_ota_end(void) {
>>>       > + esp_err_t err = esp_ota_end(s_ota_update_handle);
>>>       > + MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
>>>       > + if (s_ota_success && err == ESP_OK) {
>>>       > + err = esp_ota_set_boot_partition(s_ota_update_partition);
>>>       > + s_ota_success = (err == ESP_OK);
>>>       > + }
>>>       > + MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
>>>       > + s_ota_update_partition = NULL;
>>>       > + return s_ota_success;
>>>       > +}
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/ota_flash.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +// This OTA implementation uses the internal flash API outlined
>>>      in device.h
>>>       > +// It splits flash into 2 equal partitions, and stores OTA
>>>      status in the
>>>       > +// last sector of the partition.
>>>       > +
>>>       > +#if MG_OTA == MG_OTA_FLASH
>>>       > +
>>>       > +#define MG_OTADATA_KEY 0xb07afed0
>>>       > +
>>>       > +static char *s_addr; // Current address to write to
>>>       > +static size_t s_size; // Firmware size to flash. In-progress
>>>      indicator
>>>       > +static uint32_t s_crc32; // Firmware checksum
>>>       > +
>>>       > +struct mg_otadata {
>>>       > + uint32_t crc32, size, timestamp, status;
>>>       > +};
>>>       > +
>>>       > +bool mg_ota_begin(size_t new_firmware_size) {
>>>       > + bool ok = false;
>>>       > + if (s_size) {
>>>       > + MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
>>>       > + } else {
>>>       > + size_t half = mg_flash_size() / 2, max = half -
>>>      mg_flash_sector_size();
>>>       > + s_crc32 = 0;
>>>       > + s_addr = (char *) mg_flash_start() + half;
>>>       > + MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
>>>       > + if (new_firmware_size < max) {
>>>       > + ok = true;
>>>       > + s_size = new_firmware_size;
>>>       > + MG_INFO(("Starting OTA, firmware size %lu", s_size));
>>>       > + } else {
>>>       > + MG_ERROR(("Firmware %lu is too big to fit %lu",
>>>      new_firmware_size, max));
>>>       > + }
>>>       > + }
>>>       > + return ok;
>>>       > +}
>>>       > +
>>>       > +bool mg_ota_write(const void *buf, size_t len) {
>>>       > + bool ok = false;
>>>       > + if (s_size == 0) {
>>>       > + MG_ERROR(("OTA is not started, call mg_ota_begin()"));
>>>       > + } else {
>>>       > + size_t align = mg_flash_write_align();
>>>       > + size_t len_aligned_down = MG_ROUND_DOWN(len, align);
>>>       > + if (len_aligned_down) ok = mg_flash_write(s_addr, buf,
>>>      len_aligned_down);
>>>       > + if (len_aligned_down < len) {
>>>       > + size_t left = len - len_aligned_down;
>>>       > + char tmp[align];
>>>       > + memset(tmp, 0xff, sizeof(tmp));
>>>       > + memcpy(tmp, (char *) buf + len_aligned_down, left);
>>>       > + ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
>>>       > + }
>>>       > + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC
>>>       > + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
>>>       > + s_addr += len;
>>>       > + }
>>>       > + return ok;
>>>       > +}
>>>       > +
>>>       > +MG_IRAM static uint32_t mg_fwkey(int fw) {
>>>       > + uint32_t key = MG_OTADATA_KEY + fw;
>>>       > + int bank = mg_flash_bank();
>>>       > + if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
>>>       > + if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
>>>       > + return key;
>>>       > +}
>>>       > +
>>>       > +bool mg_ota_end(void) {
>>>       > + char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
>>>       > + bool ok = false;
>>>       > + if (s_size) {
>>>       > + size_t size = s_addr - base;
>>>       > + uint32_t crc32 = mg_crc32(0, base, s_size);
>>>       > + if (size == s_size && crc32 == s_crc32) {
>>>       > + uint32_t now = (uint32_t) (mg_now() / 1000);
>>>       > + struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
>>>       > + uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
>>>       > + ok = mg_flash_save(NULL, key, &od, sizeof(od));
>>>       > + }
>>>       > + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32,
>>>      crc32, s_size,
>>>       > + size, ok ? "ok" : "fail"));
>>>       > + s_size = 0;
>>>       > + if (ok) ok = mg_flash_swap_bank();
>>>       > + }
>>>       > + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
>>>       > + return ok;
>>>       > +}
>>>       > +
>>>       > +MG_IRAM static struct mg_otadata mg_otadata(int fw) {
>>>       > + uint32_t key = mg_fwkey(fw);
>>>       > + struct mg_otadata od = {};
>>>       > + MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ?
>>>      "curr" : "prev"));
>>>       > + mg_flash_load(NULL, key, &od, sizeof(od));
>>>       > + // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw,
>>>      bank, key));
>>>       > + // mg_hexdump(&od, sizeof(od));
>>>       > + return od;
>>>       > +}
>>>       > +
>>>       > +int mg_ota_status(int fw) {
>>>       > + struct mg_otadata od = mg_otadata(fw);
>>>       > + return od.status;
>>>       > +}
>>>       > +uint32_t mg_ota_crc32(int fw) {
>>>       > + struct mg_otadata od = mg_otadata(fw);
>>>       > + return od.crc32;
>>>       > +}
>>>       > +uint32_t mg_ota_timestamp(int fw) {
>>>       > + struct mg_otadata od = mg_otadata(fw);
>>>       > + return od.timestamp;
>>>       > +}
>>>       > +size_t mg_ota_size(int fw) {
>>>       > + struct mg_otadata od = mg_otadata(fw);
>>>       > + return od.size;
>>>       > +}
>>>       > +
>>>       > +MG_IRAM bool mg_ota_commit(void) {
>>>       > + bool ok = true;
>>>       > + struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
>>>       > + if (od.status != MG_OTA_COMMITTED) {
>>>       > + od.status = MG_OTA_COMMITTED;
>>>       > + MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
>>>       > + ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od,
>>>      sizeof(od));
>>>       > + }
>>>       > + return ok;
>>>       > +}
>>>       > +
>>>       > +bool mg_ota_rollback(void) {
>>>       > + MG_DEBUG(("Rolling firmware back"));
>>>       > + if (mg_flash_bank() == 0) {
>>>       > + // No dual bank support. Mark previous firmware as FIRST_BOOT
>>>       > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
>>>       > + prev.status = MG_OTA_FIRST_BOOT;
>>>       > + return mg_flash_save(NULL, MG_OTADATA_KEY +
>>>      MG_FIRMWARE_PREVIOUS, &prev,
>>>       > + sizeof(prev));
>>>       > + } else {
>>>       > + return mg_flash_swap_bank();
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +MG_IRAM void mg_ota_boot(void) {
>>>       > + MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
>>>       > + struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
>>>       > + struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
>>>       > +
>>>       > + if (curr.status == MG_OTA_FIRST_BOOT) {
>>>       > + if (prev.status == MG_OTA_UNAVAILABLE) {
>>>       > + MG_INFO(("Setting previous firmware state to committed"));
>>>       > + prev.status = MG_OTA_COMMITTED;
>>>       > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev,
>>>      sizeof(prev));
>>>       > + }
>>>       > + curr.status = MG_OTA_UNCOMMITTED;
>>>       > + MG_INFO(("First boot, setting status to UNCOMMITTED"));
>>>       > + mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr,
>>>      sizeof(curr));
>>>       > + } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank()
>>>      == 0) {
>>>       > + // Swap paritions. Pray power does not disappear
>>>       > + size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
>>>       > + char *partition1 = mg_flash_start();
>>>       > + char *partition2 = mg_flash_start() + fs / 2;
>>>       > + size_t ofs, max = fs / 2 - ss; // Set swap size to the whole
>>>      partition
>>>       > +
>>>       > + if (curr.status != MG_OTA_UNAVAILABLE &&
>>>       > + prev.status != MG_OTA_UNAVAILABLE) {
>>>       > + // We know exact sizes of both firmwares.
>>>       > + // Shrink swap size to the MAX(firmware1, firmware2)
>>>       > + size_t sz = curr.size > prev.size ? curr.size : prev.size;
>>>       > + if (sz > 0 && sz < max) max = sz;
>>>       > + }
>>>       > +
>>>       > + // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
>>>       > + prev.status = MG_OTA_UNCOMMITTED;
>>>       > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
>>>       > + sizeof(prev));
>>>       > + mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
>>>       > + sizeof(curr));
>>>       > +
>>>       > + MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max
>>>      / ss));
>>>       > + MG_INFO(("Do NOT power off..."));
>>>       > + mg_log_level = MG_LL_NONE;
>>>       > +
>>>       > + // We use the last sector of partition2 for OTA data/config
>>>      storage
>>>       > + // Therefore we can use last sector of partition1 for swapping
>>>       > + char *tmpsector = partition1 + fs / 2 - ss; // Last sector of
>>>      partition1
>>>       > + (void) tmpsector;
>>>       > + for (ofs = 0; ofs < max; ofs += ss) {
>>>       > + // mg_flash_erase(tmpsector);
>>>       > + mg_flash_write(tmpsector, partition1 + ofs, ss);
>>>       > + // mg_flash_erase(partition1 + ofs);
>>>       > + mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
>>>       > + // mg_flash_erase(partition2 + ofs);
>>>       > + mg_flash_write(partition2 + ofs, tmpsector, ss);
>>>       > + }
>>>       > + mg_device_reset();
>>>       > + }
>>>       > +}
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/printf.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt,
>>>      va_list *ap) {
>>>       > + size_t len = mg_snprintf(NULL, 0, fmt, ap);
>>>       > + char *buf;
>>>       > + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
>>>       > + len = 0; // Nah. Not enough space
>>>       > + } else {
>>>       > + len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
>>>       > + mg_queue_add(q, len);
>>>       > + }
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + size_t len;
>>>       > + va_start(ap, fmt);
>>>       > + len = mg_queue_vprintf(q, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static void mg_pfn_iobuf_private(char ch, void *param, bool
>>>      expand) {
>>>       > + struct mg_iobuf *io = (struct mg_iobuf *) param;
>>>       > + if (expand && io->len + 2 > io->size) mg_iobuf_resize(io,
>>>      io->len + 2);
>>>       > + if (io->len + 2 <= io->size) {
>>>       > + io->buf[io->len++] = (uint8_t) ch;
>>>       > + io->buf[io->len] = 0;
>>>       > + } else if (io->len < io->size) {
>>>       > + io->buf[io->len++] = 0; // Guarantee to 0-terminate
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void mg_putchar_iobuf_static(char ch, void *param) {
>>>       > + mg_pfn_iobuf_private(ch, param, false);
>>>       > +}
>>>       > +
>>>       > +void mg_pfn_iobuf(char ch, void *param) {
>>>       > + mg_pfn_iobuf_private(ch, param, true);
>>>       > +}
>>>       > +
>>>       > +size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>>>      va_list *ap) {
>>>       > + struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
>>>       > + size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
>>>       > + if (n < len) buf[n] = '\0';
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + size_t n;
>>>       > + va_start(ap, fmt);
>>>       > + n = mg_vsnprintf(buf, len, fmt, &ap);
>>>       > + va_end(ap);
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +char *mg_vmprintf(const char *fmt, va_list *ap) {
>>>       > + struct mg_iobuf io = {0, 0, 0, 256};
>>>       > + mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
>>>       > + return (char *) io.buf;
>>>       > +}
>>>       > +
>>>       > +char *mg_mprintf(const char *fmt, ...) {
>>>       > + char *s;
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + s = mg_vmprintf(fmt, &ap);
>>>       > + va_end(ap);
>>>       > + return s;
>>>       > +}
>>>       > +
>>>       > +void mg_pfn_stdout(char c, void *param) {
>>>       > + putchar(c);
>>>       > + (void) param;
>>>       > +}
>>>       > +
>>>       > +static size_t print_ip4(void (*out)(char, void *), void *arg,
>>>      uint8_t *p) {
>>>       > + return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2],
>>>      p[3]);
>>>       > +}
>>>       > +
>>>       > +static size_t print_ip6(void (*out)(char, void *), void *arg,
>>>      uint16_t *p) {
>>>       > + return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]",
>>>      mg_ntohs(p[0]),
>>>       > + mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
>>>       > + mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
>>>       > + mg_ntohs(p[7]));
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + uint8_t *p = va_arg(*ap, uint8_t *);
>>>       > + return print_ip4(out, arg, p);
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + uint16_t *p = va_arg(*ap, uint16_t *);
>>>       > + return print_ip6(out, arg, p);
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
>>>      *ap) {
>>>       > + struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
>>>       > + if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *)
>>>      addr->ip);
>>>       > + return print_ip4(out, arg, (uint8_t *) &addr->ip);
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + struct mg_addr *a = va_arg(*ap, struct mg_addr *);
>>>       > + return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a,
>>>      mg_ntohs(a->port));
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + uint8_t *p = va_arg(*ap, uint8_t *);
>>>       > + return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x",
>>>      p[0], p[1], p[2],
>>>       > + p[3], p[4], p[5]);
>>>       > +}
>>>       > +
>>>       > +static char mg_esc(int c, bool esc) {
>>>       > + const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
>>>       > + for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
>>>       > + if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
>>>       > + }
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +static char mg_escape(int c) {
>>>       > + return mg_esc(c, true);
>>>       > +}
>>>       > +
>>>       > +static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
>>>       > + size_t len) {
>>>       > + size_t i = 0, extra = 0;
>>>       > + for (i = 0; i < len && buf[i] != '\0'; i++) {
>>>       > + char c = mg_escape(buf[i]);
>>>       > + if (c) {
>>>       > + out('\\', ptr), out(c, ptr), extra++;
>>>       > + } else {
>>>       > + out(buf[i], ptr);
>>>       > + }
>>>       > + }
>>>       > + return i + extra;
>>>       > +}
>>>       > +
>>>       > +static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t
>>>      *buf,
>>>       > + size_t len) {
>>>       > + size_t i, j, n = 0;
>>>       > + const char *t =
>>>       > +
>>>      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
>>>       > + for (i = 0; i < len; i += 3) {
>>>       > + uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
>>>       > + c3 = i + 2 < len ? buf[i + 2] : 0;
>>>       > + char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=',
>>>      '='};
>>>       > + if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
>>>       > + if (i + 2 < len) tmp[3] = t[c3 & 63];
>>>       > + for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j],
>>>      arg);
>>>       > + n += j;
>>>       > + }
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + size_t bl = (size_t) va_arg(*ap, int);
>>>       > + uint8_t *p = va_arg(*ap, uint8_t *);
>>>       > + const char *hex = "0123456789abcdef";
>>>       > + size_t j;
>>>       > + for (j = 0; j < bl; j++) {
>>>       > + out(hex[(p[j] >> 4) & 0x0F], arg);
>>>       > + out(hex[p[j] & 0x0F], arg);
>>>       > + }
>>>       > + return 2 * bl;
>>>       > +}
>>>       > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + size_t len = (size_t) va_arg(*ap, int);
>>>       > + uint8_t *buf = va_arg(*ap, uint8_t *);
>>>       > + return bcpy(out, arg, buf, len);
>>>       > +}
>>>       > +
>>>       > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
>>>      va_list *ap) {
>>>       > + size_t len = (size_t) va_arg(*ap, int);
>>>       > + char *p = va_arg(*ap, char *);
>>>       > + if (len == 0) len = p == NULL ? 0 : strlen(p);
>>>       > + return qcpy(out, arg, p, len);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/queue.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if (defined(__GNUC__) && (__GNUC__ > 4) || \
>>>       > + (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >=
>>>      1)) || \
>>>       > + defined(__clang__)
>>>       > +#define MG_MEMORY_BARRIER() __sync_synchronize()
>>>       > +#elif defined(_MSC_VER) && _MSC_VER >= 1700
>>>       > +#define MG_MEMORY_BARRIER() MemoryBarrier()
>>>       > +#elif !defined(MG_MEMORY_BARRIER)
>>>       > +#define MG_MEMORY_BARRIER()
>>>       > +#endif
>>>       > +
>>>       > +// Every message in a queue is prepended by a 32-bit message
>>>      length (ML).
>>>       > +// If ML is 0, then it is the end, and reader must wrap to the
>>>      beginning.
>>>       > +//
>>>       > +// Queue when q->tail <= q->head:
>>>       > +// |----- free -----| ML | message1 | ML | message2 | ----- free
>>>      ------|
>>>       > +// ^ ^ ^ ^
>>>       > +// buf tail head len
>>>       > +//
>>>       > +// Queue when q->tail > q->head:
>>>       > +// | ML | message2 |----- free ------| ML | message1 | 0 |----
>>>      free ----|
>>>       > +// ^ ^ ^ ^
>>>       > +// buf head tail len
>>>       > +
>>>       > +void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
>>>       > + q->size = size;
>>>       > + q->buf = buf;
>>>       > + q->head = q->tail = 0;
>>>       > +}
>>>       > +
>>>       > +static size_t mg_queue_read_len(struct mg_queue *q) {
>>>       > + uint32_t n = 0;
>>>       > + MG_MEMORY_BARRIER();
>>>       > + memcpy(&n, q->buf + q->tail, sizeof(n));
>>>       > + assert(q->tail + n + sizeof(n) <= q->size);
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +static void mg_queue_write_len(struct mg_queue *q, size_t len) {
>>>       > + uint32_t n = (uint32_t) len;
>>>       > + memcpy(q->buf + q->head, &n, sizeof(n));
>>>       > + MG_MEMORY_BARRIER();
>>>       > +}
>>>       > +
>>>       > +size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
>>>       > + size_t space = 0, hs = sizeof(uint32_t) * 2; // *2 is for the 0
>>>      marker
>>>       > + if (q->head >= q->tail && q->head + len + hs <= q->size) {
>>>       > + space = q->size - q->head - hs; // There is enough space
>>>       > + } else if (q->head >= q->tail && q->tail > hs) {
>>>       > + mg_queue_write_len(q, 0); // Not enough space ahead
>>>       > + q->head = 0; // Wrap head to the beginning
>>>       > + }
>>>       > + if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
>>>       > + if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
>>>       > + return space;
>>>       > +}
>>>       > +
>>>       > +size_t mg_queue_next(struct mg_queue *q, char **buf) {
>>>       > + size_t len = 0;
>>>       > + if (q->tail != q->head) {
>>>       > + len = mg_queue_read_len(q);
>>>       > + if (len == 0) { // Zero (head wrapped) ?
>>>       > + q->tail = 0; // Reset tail to the start
>>>       > + if (q->head > q->tail) len = mg_queue_read_len(q); // Read again
>>>       > + }
>>>       > + }
>>>       > + if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
>>>       > + assert(q->tail + len <= q->size);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +void mg_queue_add(struct mg_queue *q, size_t len) {
>>>       > + assert(len > 0);
>>>       > + mg_queue_write_len(q, len);
>>>       > + assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
>>>       > + q->head += len + sizeof(uint32_t);
>>>       > +}
>>>       > +
>>>       > +void mg_queue_del(struct mg_queue *q, size_t len) {
>>>       > + q->tail += len + sizeof(uint32_t);
>>>       > + assert(q->tail + sizeof(uint32_t) <= q->size);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/rpc.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
>>>       > + void (*fn)(struct mg_rpc_req *), void *fn_data) {
>>>       > + struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
>>>       > + if (rpc != NULL) {
>>>       > + rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
>>>       > + rpc->method.len = method.len;
>>>       > + rpc->fn = fn;
>>>       > + rpc->fn_data = fn_data;
>>>       > + rpc->next = *head, *head = rpc;
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
>>>      mg_rpc_req *)) {
>>>       > + struct mg_rpc *r;
>>>       > + while ((r = *head) != NULL) {
>>>       > + if (r->fn == fn || fn == NULL) {
>>>       > + *head = r->next;
>>>       > + free((void *) r->method.buf);
>>>       > + free(r);
>>>       > + } else {
>>>       > + head = &(*head)->next;
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
>>>      method) {
>>>       > + struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
>>>       > + while (h != NULL && !mg_match(method, h->method, NULL)) h =
>>>      h->next;
>>>       > + if (h != NULL) {
>>>       > + r->rpc = h;
>>>       > + h->fn(r);
>>>       > + } else {
>>>       > + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
>>>      method.buf);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_process(struct mg_rpc_req *r) {
>>>       > + int len, off = mg_json_get(r->frame, "$.method", &len);
>>>       > + if (off > 0 && r->frame.buf[off] == '"') {
>>>       > + struct mg_str method = mg_str_n(&r->frame.buf[off + 1],
>>>      (size_t) len - 2);
>>>       > + mg_rpc_call(r, method);
>>>       > + } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
>>>       > + (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
>>>       > + mg_rpc_call(r, mg_str("")); // JSON response! call "" method
>>>      handler
>>>       > + } else {
>>>       > + mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
>>>       > + r->frame.buf); // Invalid
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
>>>      *ap) {
>>>       > + int len, off = mg_json_get(r->frame, "$.id", &len);
>>>       > + if (off > 0) {
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc,
>>>      0, "id", len,
>>>       > + &r->frame.buf[off], mg_print_esc, 0, "result");
>>>       > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "}");
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + mg_rpc_vok(r, fmt, &ap);
>>>       > + va_end(ap);
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
>>>      *fmt, va_list *ap) {
>>>       > + int len, off = mg_json_get(r->frame, "$.id", &len);
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "{");
>>>       > + if (off > 0) {
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0,
>>>      "id", len,
>>>       > + &r->frame.buf[off]);
>>>       > + }
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc,
>>>      0, "error",
>>>       > + mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
>>>       > + mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>>>       > + mg_xprintf(r->pfn, r->pfn_data, "}}");
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
>>>      ...) {
>>>       > + va_list ap;
>>>       > + va_start(ap, fmt);
>>>       > + mg_rpc_verr(r, code, fmt, &ap);
>>>       > + va_end(ap);
>>>       > +}
>>>       > +
>>>       > +static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
>>>      va_list *ap) {
>>>       > + struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
>>>      **);
>>>       > + size_t len = 0;
>>>       > + for (h = *head; h != NULL; h = h->next) {
>>>       > + if (h->method.len == 0) continue; // Ignore response handler
>>>       > + len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
>>>       > + mg_print_esc, (int) h->method.len, h->method.buf);
>>>       > + }
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +void mg_rpc_list(struct mg_rpc_req *r) {
>>>       > + mg_rpc_ok(r, "[%M]", print_methods, r->head);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/sha1.c"
>>>       > +#endif
>>>       > +/* Copyright(c) By Steve Reid <st...@edmweb.com> */
>>>       > +/* 100% Public Domain */
>>>       > +
>>>       > +
>>>       > +
>>>       > +union char64long16 {
>>>       > + unsigned char c[64];
>>>       > + uint32_t l[16];
>>>       > +};
>>>       > +
>>>       > +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
>>>      - (bits))))
>>>       > +
>>>       > +static uint32_t blk0(union char64long16 *block, int i) {
>>>       > + if (MG_BIG_ENDIAN) {
>>>       > + } else {
>>>       > + block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
>>>       > + (rol(block->l[i], 8) & 0x00FF00FF);
>>>       > + }
>>>       > + return block->l[i];
>>>       > +}
>>>       > +
>>>       > +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
>>>      define R0~R4) */
>>>       > +#undef blk
>>>       > +#undef R0
>>>       > +#undef R1
>>>       > +#undef R2
>>>       > +#undef R3
>>>       > +#undef R4
>>>       > +
>>>       > +#define blk(i) \
>>>       > + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
>>>      8) & 15] ^ \
>>>       > + block->l[(i + 2) & 15] ^ block->l[i & 15], \
>>>       > + 1))
>>>       > +#define R0(v, w, x, y, z, i) \
>>>       > + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
>>>      5); \
>>>       > + w = rol(w, 30);
>>>       > +#define R1(v, w, x, y, z, i) \
>>>       > + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
>>>       > + w = rol(w, 30);
>>>       > +#define R2(v, w, x, y, z, i) \
>>>       > + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
>>>       > + w = rol(w, 30);
>>>       > +#define R3(v, w, x, y, z, i) \
>>>       > + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
>>>      5); \
>>>       > + w = rol(w, 30);
>>>       > +#define R4(v, w, x, y, z, i) \
>>>       > + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
>>>       > + w = rol(w, 30);
>>>       > +
>>>       > +static void mg_sha1_transform(uint32_t state[5],
>>>       > + const unsigned char *buffer) {
>>>       > + uint32_t a, b, c, d, e;
>>>       > + union char64long16 block[1];
>>>       > +
>>>       > + memcpy(block, buffer, 64);
>>>       > + a = state[0];
>>>       > + b = state[1];
>>>       > + c = state[2];
>>>       > + d = state[3];
>>>       > + e = state[4];
>>>       > + R0(a, b, c, d, e, 0);
>>>       > + R0(e, a, b, c, d, 1);
>>>       > + R0(d, e, a, b, c, 2);
>>>       > + R0(c, d, e, a, b, 3);
>>>       > + R0(b, c, d, e, a, 4);
>>>       > + R0(a, b, c, d, e, 5);
>>>       > + R0(e, a, b, c, d, 6);
>>>       > + R0(d, e, a, b, c, 7);
>>>       > + R0(c, d, e, a, b, 8);
>>>       > + R0(b, c, d, e, a, 9);
>>>       > + R0(a, b, c, d, e, 10);
>>>       > + R0(e, a, b, c, d, 11);
>>>       > + R0(d, e, a, b, c, 12);
>>>       > + R0(c, d, e, a, b, 13);
>>>       > + R0(b, c, d, e, a, 14);
>>>       > + R0(a, b, c, d, e, 15);
>>>       > + R1(e, a, b, c, d, 16);
>>>       > + R1(d, e, a, b, c, 17);
>>>       > + R1(c, d, e, a, b, 18);
>>>       > + R1(b, c, d, e, a, 19);
>>>       > + R2(a, b, c, d, e, 20);
>>>       > + R2(e, a, b, c, d, 21);
>>>       > + R2(d, e, a, b, c, 22);
>>>       > + R2(c, d, e, a, b, 23);
>>>       > + R2(b, c, d, e, a, 24);
>>>       > + R2(a, b, c, d, e, 25);
>>>       > + R2(e, a, b, c, d, 26);
>>>       > + R2(d, e, a, b, c, 27);
>>>       > + R2(c, d, e, a, b, 28);
>>>       > + R2(b, c, d, e, a, 29);
>>>       > + R2(a, b, c, d, e, 30);
>>>       > + R2(e, a, b, c, d, 31);
>>>       > + R2(d, e, a, b, c, 32);
>>>       > + R2(c, d, e, a, b, 33);
>>>       > + R2(b, c, d, e, a, 34);
>>>       > + R2(a, b, c, d, e, 35);
>>>       > + R2(e, a, b, c, d, 36);
>>>       > + R2(d, e, a, b, c, 37);
>>>       > + R2(c, d, e, a, b, 38);
>>>       > + R2(b, c, d, e, a, 39);
>>>       > + R3(a, b, c, d, e, 40);
>>>       > + R3(e, a, b, c, d, 41);
>>>       > + R3(d, e, a, b, c, 42);
>>>       > + R3(c, d, e, a, b, 43);
>>>       > + R3(b, c, d, e, a, 44);
>>>       > + R3(a, b, c, d, e, 45);
>>>       > + R3(e, a, b, c, d, 46);
>>>       > + R3(d, e, a, b, c, 47);
>>>       > + R3(c, d, e, a, b, 48);
>>>       > + R3(b, c, d, e, a, 49);
>>>       > + R3(a, b, c, d, e, 50);
>>>       > + R3(e, a, b, c, d, 51);
>>>       > + R3(d, e, a, b, c, 52);
>>>       > + R3(c, d, e, a, b, 53);
>>>       > + R3(b, c, d, e, a, 54);
>>>       > + R3(a, b, c, d, e, 55);
>>>       > + R3(e, a, b, c, d, 56);
>>>       > + R3(d, e, a, b, c, 57);
>>>       > + R3(c, d, e, a, b, 58);
>>>       > + R3(b, c, d, e, a, 59);
>>>       > + R4(a, b, c, d, e, 60);
>>>       > + R4(e, a, b, c, d, 61);
>>>       > + R4(d, e, a, b, c, 62);
>>>       > + R4(c, d, e, a, b, 63);
>>>       > + R4(b, c, d, e, a, 64);
>>>       > + R4(a, b, c, d, e, 65);
>>>       > + R4(e, a, b, c, d, 66);
>>>       > + R4(d, e, a, b, c, 67);
>>>       > + R4(c, d, e, a, b, 68);
>>>       > + R4(b, c, d, e, a, 69);
>>>       > + R4(a, b, c, d, e, 70);
>>>       > + R4(e, a, b, c, d, 71);
>>>       > + R4(d, e, a, b, c, 72);
>>>       > + R4(c, d, e, a, b, 73);
>>>       > + R4(b, c, d, e, a, 74);
>>>       > + R4(a, b, c, d, e, 75);
>>>       > + R4(e, a, b, c, d, 76);
>>>       > + R4(d, e, a, b, c, 77);
>>>       > + R4(c, d, e, a, b, 78);
>>>       > + R4(b, c, d, e, a, 79);
>>>       > + state[0] += a;
>>>       > + state[1] += b;
>>>       > + state[2] += c;
>>>       > + state[3] += d;
>>>       > + state[4] += e;
>>>       > + /* Erase working structures. The order of operations is important,
>>>       > + * used to ensure that compiler doesn't optimize those out. */
>>>       > + memset(block, 0, sizeof(block));
>>>       > + a = b = c = d = e = 0;
>>>       > + (void) a;
>>>       > + (void) b;
>>>       > + (void) c;
>>>       > + (void) d;
>>>       > + (void) e;
>>>       > +}
>>>       > +
>>>       > +void mg_sha1_init(mg_sha1_ctx *context) {
>>>       > + context->state[0] = 0x67452301;
>>>       > + context->state[1] = 0xEFCDAB89;
>>>       > + context->state[2] = 0x98BADCFE;
>>>       > + context->state[3] = 0x10325476;
>>>       > + context->state[4] = 0xC3D2E1F0;
>>>       > + context->count[0] = context->count[1] = 0;
>>>       > +}
>>>       > +
>>>       > +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
>>>      *data,
>>>       > + size_t len) {
>>>       > + size_t i, j;
>>>       > +
>>>       > + j = context->count[0];
>>>       > + if ((context->count[0] += (uint32_t) len << 3) < j)
>>>      context->count[1]++;
>>>       > + context->count[1] += (uint32_t) (len >> 29);
>>>       > + j = (j >> 3) & 63;
>>>       > + if ((j + len) > 63) {
>>>       > + memcpy(&context->buffer[j], data, (i = 64 - j));
>>>       > + mg_sha1_transform(context->state, context->buffer);
>>>       > + for (; i + 63 < len; i += 64) {
>>>       > + mg_sha1_transform(context->state, &data[i]);
>>>       > + }
>>>       > + j = 0;
>>>       > + } else
>>>       > + i = 0;
>>>       > + memcpy(&context->buffer[j], &data[i], len - i);
>>>       > +}
>>>       > +
>>>       > +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
>>>      *context) {
>>>       > + unsigned i;
>>>       > + unsigned char finalcount[8], c;
>>>       > +
>>>       > + for (i = 0; i < 8; i++) {
>>>       > + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
>>>      1)] >>
>>>       > + ((3 - (i & 3)) * 8)) &
>>>       > + 255);
>>>       > + }
>>>       > + c = 0200;
>>>       > + mg_sha1_update(context, &c, 1);
>>>       > + while ((context->count[0] & 504) != 448) {
>>>       > + c = 0000;
>>>       > + mg_sha1_update(context, &c, 1);
>>>       > + }
>>>       > + mg_sha1_update(context, finalcount, 8);
>>>       > + for (i = 0; i < 20; i++) {
>>>       > + digest[i] =
>>>       > + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
>>>      8)) & 255);
>>>       > + }
>>>       > + memset(context, '\0', sizeof(*context));
>>>       > + memset(&finalcount, '\0', sizeof(finalcount));
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/sha256.c"
>>>       > +#endif
>>>       > +// https://github.com/B-Con/crypto-algorithms
>>>      <https://github.com/B-Con/crypto-algorithms>
>>>       > +// Author: Brad Conte (brad AT bradconte.com
>>>      <http://bradconte.com>)
>>>       > +// Disclaimer: This code is presented "as is" without any
>>>      guarantees.
>>>       > +// Details: Defines the API for the corresponding SHA1
>>>      implementation.
>>>       > +// Copyright: public domain
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
>>>       > +#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
>>>       > +#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
>>>       > +#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
>>>       > +#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
>>>       > +#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
>>>       > +#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
>>>       > +
>>>       > +static const uint32_t mg_sha256_k[64] = {
>>>       > + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
>>>      0x59f111f1,
>>>       > + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
>>>      0x550c7dc3,
>>>       > + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1,
>>>      0xefbe4786,
>>>       > + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc,
>>>      0x76f988da,
>>>       > + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3,
>>>      0xd5a79147,
>>>       > + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
>>>      0x53380d13,
>>>       > + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1,
>>>      0xa81a664b,
>>>       > + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585,
>>>      0x106aa070,
>>>       > + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3,
>>>      0x4ed8aa4a,
>>>       > + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814,
>>>      0x8cc70208,
>>>       > + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
>>>       > +
>>>       > +void mg_sha256_init(mg_sha256_ctx *ctx) {
>>>       > + ctx->len = 0;
>>>       > + ctx->bits = 0;
>>>       > + ctx->state[0] = 0x6a09e667;
>>>       > + ctx->state[1] = 0xbb67ae85;
>>>       > + ctx->state[2] = 0x3c6ef372;
>>>       > + ctx->state[3] = 0xa54ff53a;
>>>       > + ctx->state[4] = 0x510e527f;
>>>       > + ctx->state[5] = 0x9b05688c;
>>>       > + ctx->state[6] = 0x1f83d9ab;
>>>       > + ctx->state[7] = 0x5be0cd19;
>>>       > +}
>>>       > +
>>>       > +static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
>>>       > + int i, j;
>>>       > + uint32_t a, b, c, d, e, f, g, h;
>>>       > + uint32_t m[64];
>>>       > + for (i = 0, j = 0; i < 16; ++i, j += 4)
>>>       > + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
>>>       > + ((uint32_t) ctx->buffer[j + 1] << 16) |
>>>       > + ((uint32_t) ctx->buffer[j + 2] << 8) |
>>>       > + ((uint32_t) ctx->buffer[j + 3]));
>>>       > + for (; i < 64; ++i)
>>>       > + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
>>>       > +
>>>       > + a = ctx->state[0];
>>>       > + b = ctx->state[1];
>>>       > + c = ctx->state[2];
>>>       > + d = ctx->state[3];
>>>       > + e = ctx->state[4];
>>>       > + f = ctx->state[5];
>>>       > + g = ctx->state[6];
>>>       > + h = ctx->state[7];
>>>       > +
>>>       > + for (i = 0; i < 64; ++i) {
>>>       > + uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
>>>       > + uint32_t t2 = ep0(a) + maj(a, b, c);
>>>       > + h = g;
>>>       > + g = f;
>>>       > + f = e;
>>>       > + e = d + t1;
>>>       > + d = c;
>>>       > + c = b;
>>>       > + b = a;
>>>       > + a = t1 + t2;
>>>       > + }
>>>       > +
>>>       > + ctx->state[0] += a;
>>>       > + ctx->state[1] += b;
>>>       > + ctx->state[2] += c;
>>>       > + ctx->state[3] += d;
>>>       > + ctx->state[4] += e;
>>>       > + ctx->state[5] += f;
>>>       > + ctx->state[6] += g;
>>>       > + ctx->state[7] += h;
>>>       > +}
>>>       > +
>>>       > +void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char
>>>      *data,
>>>       > + size_t len) {
>>>       > + size_t i;
>>>       > + for (i = 0; i < len; i++) {
>>>       > + ctx->buffer[ctx->len] = data[i];
>>>       > + if ((++ctx->len) == 64) {
>>>       > + mg_sha256_chunk(ctx);
>>>       > + ctx->bits += 512;
>>>       > + ctx->len = 0;
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +// TODO: make final reusable (remove side effects)
>>>       > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx
>>>      *ctx) {
>>>       > + uint32_t i = ctx->len;
>>>       > + if (i < 56) {
>>>       > + ctx->buffer[i++] = 0x80;
>>>       > + while (i < 56) {
>>>       > + ctx->buffer[i++] = 0x00;
>>>       > + }
>>>       > + } else {
>>>       > + ctx->buffer[i++] = 0x80;
>>>       > + while (i < 64) {
>>>       > + ctx->buffer[i++] = 0x00;
>>>       > + }
>>>       > + mg_sha256_chunk(ctx);
>>>       > + memset(ctx->buffer, 0, 56);
>>>       > + }
>>>       > +
>>>       > + ctx->bits += ctx->len * 8;
>>>       > + ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
>>>       > + ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
>>>       > + ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
>>>       > + ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
>>>       > + ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
>>>       > + ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
>>>       > + ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
>>>       > + ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
>>>       > + mg_sha256_chunk(ctx);
>>>       > +
>>>       > + for (i = 0; i < 4; ++i) {
>>>       > + digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
>>>       > + digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) &
>>>      0xff);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
>>>      uint8_t *data,
>>>       > + size_t datasz) {
>>>       > + mg_sha256_ctx ctx;
>>>       > + uint8_t k[64] = {0};
>>>       > + uint8_t o_pad[64], i_pad[64];
>>>       > + unsigned int i;
>>>       > + memset(i_pad, 0x36, sizeof(i_pad));
>>>       > + memset(o_pad, 0x5c, sizeof(o_pad));
>>>       > + if (keysz < 64) {
>>>       > + if (keysz > 0) memmove(k, key, keysz);
>>>       > + } else {
>>>       > + mg_sha256_init(&ctx);
>>>       > + mg_sha256_update(&ctx, key, keysz);
>>>       > + mg_sha256_final(k, &ctx);
>>>       > + }
>>>       > + for (i = 0; i < sizeof(k); i++) {
>>>       > + i_pad[i] ^= k[i];
>>>       > + o_pad[i] ^= k[i];
>>>       > + }
>>>       > + mg_sha256_init(&ctx);
>>>       > + mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
>>>       > + mg_sha256_update(&ctx, data, datasz);
>>>       > + mg_sha256_final(dst, &ctx);
>>>       > + mg_sha256_init(&ctx);
>>>       > + mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
>>>       > + mg_sha256_update(&ctx, dst, 32);
>>>       > + mg_sha256_final(dst, &ctx);
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/sntp.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
>>>       > +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
>>>       > +
>>>       > +static int64_t gettimestamp(const uint32_t *data) {
>>>       > + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
>>>       > + if (sec) sec -= SNTP_TIME_OFFSET;
>>>       > + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
>>>      * 1000.0);
>>>       > +}
>>>       > +
>>>       > +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
>>>       > + int64_t res = -1;
>>>       > + int mode = len > 0 ? buf[0] & 7 : 0;
>>>       > + int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
>>>       > + if (len < 48) {
>>>       > + MG_ERROR(("%s", "corrupt packet"));
>>>       > + } else if (mode != 4 && mode != 5) {
>>>       > + MG_ERROR(("%s", "not a server reply"));
>>>       > + } else if (buf[1] == 0) {
>>>       > + MG_ERROR(("%s", "server sent a kiss of death"));
>>>       > + } else if (version == 4 || version == 3) {
>>>       > + // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
>>>       > + int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
>>>       > + int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
>>>       > + int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
>>>       > + int64_t t3 = (int64_t) mg_millis();
>>>       > + int64_t delta = (t3 - t0) - (t2 - t1);
>>>       > + MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
>>>      delta));
>>>       > + res = t2 + delta / 2;
>>>       > + } else {
>>>       > + MG_ERROR(("unexpected version: %d", version));
>>>       > + }
>>>       > + return res;
>>>       > +}
>>>       > +
>>>       > +static void sntp_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > + if (ev == MG_EV_READ) {
>>>       > + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
>>>       > + if (milliseconds > 0) {
>>>       > + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id,
>>>      milliseconds));
>>>       > + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
>>>       > + MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
>>>       > + (unsigned) (milliseconds % 1000)));
>>>       > + }
>>>       > + mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
>>>       > + } else if (ev == MG_EV_CONNECT) {
>>>       > + mg_sntp_request(c);
>>>       > + } else if (ev == MG_EV_CLOSE) {
>>>       > + }
>>>       > + (void) ev_data;
>>>       > +}
>>>       > +
>>>       > +void mg_sntp_request(struct mg_connection *c) {
>>>       > + if (c->is_resolving) {
>>>       > + MG_ERROR(("%lu wait until resolved", c->id));
>>>       > + } else {
>>>       > + int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
>>>       > + uint8_t buf[48] = {0};
>>>       > + uint32_t *t = (uint32_t *) &buf[40];
>>>       > + double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
>>>       > + buf[0] = (0 << 6) | (4 << 3) | 3;
>>>       > + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
>>>       > + t[1] = mg_htonl((uint32_t) frac);
>>>       > + mg_send(c, buf, sizeof(buf));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > + mg_event_handler_t fn, void *fnd) {
>>>       > + struct mg_connection *c = NULL;
>>>       > + if (url == NULL) url = "udp://time.google.com:123
>>>      <http://time.google.com:123>";
>>>       > + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
>>>       > + return c;
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/sock.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_SOCKET
>>>       > +
>>>       > +#ifndef closesocket
>>>       > +#define closesocket(x) close(x)
>>>       > +#endif
>>>       > +
>>>       > +#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
>>>       > +#define S2PTR(s_) ((void *) (size_t) (s_))
>>>       > +
>>>       > +#ifndef MSG_NONBLOCKING
>>>       > +#define MSG_NONBLOCKING 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef AF_INET6
>>>       > +#define AF_INET6 10
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_SOCK_ERR
>>>       > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_SOCK_INTR
>>>       > +#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET &&
>>>      MG_SOCK_ERR(-1) == EINTR)
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_SOCK_PENDING
>>>       > +#define MG_SOCK_PENDING(errcode) \
>>>       > + (((errcode) < 0) && (errno == EINPROGRESS || errno ==
>>>      EWOULDBLOCK))
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_SOCK_RESET
>>>       > +#define MG_SOCK_RESET(errcode) \
>>>       > + (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
>>>       > +#endif
>>>       > +
>>>       > +union usa {
>>>       > + struct sockaddr sa;
>>>       > + struct sockaddr_in sin;
>>>       > +#if MG_ENABLE_IPV6
>>>       > + struct sockaddr_in6 sin6;
>>>       > +#endif
>>>       > +};
>>>       > +
>>>       > +static socklen_t tousa(struct mg_addr *a, union usa *usa) {
>>>       > + socklen_t len = sizeof(usa->sin);
>>>       > + memset(usa, 0, sizeof(*usa));
>>>       > + usa->sin.sin_family = AF_INET;
>>>       > + usa->sin.sin_port = a->port;
>>>       > + memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
>>>       > +#if MG_ENABLE_IPV6
>>>       > + if (a->is_ip6) {
>>>       > + usa->sin.sin_family = AF_INET6;
>>>       > + usa->sin6.sin6_port = a->port;
>>>       > + usa->sin6.sin6_scope_id = a->scope_id;
>>>       > + memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
>>>       > + len = sizeof(usa->sin6);
>>>       > + }
>>>       > +#endif
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static void tomgaddr(union usa *usa, struct mg_addr *a, bool
>>>      is_ip6) {
>>>       > + a->is_ip6 = is_ip6;
>>>       > + a->port = usa->sin.sin_port;
>>>       > + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
>>>       > +#if MG_ENABLE_IPV6
>>>       > + if (is_ip6) {
>>>       > + memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
>>>       > + a->port = usa->sin6.sin6_port;
>>>       > + a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
>>>       > + }
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
>>>       > + union usa usa;
>>>       > + socklen_t n = sizeof(usa);
>>>       > + if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
>>>       > + tomgaddr(&usa, addr, n != sizeof(usa.sin));
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void iolog(struct mg_connection *c, char *buf, long n,
>>>      bool r) {
>>>       > + if (n == MG_IO_WAIT) {
>>>       > + // Do nothing
>>>       > + } else if (n <= 0) {
>>>       > + c->is_closing = 1; // Termination. Don't call mg_error(): #1529
>>>       > + } else if (n > 0) {
>>>       > + if (c->is_hexdumping) {
>>>       > + MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port,
>>>      &c->loc,
>>>       > + r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
>>>       > + mg_hexdump(buf, (size_t) n);
>>>       > + }
>>>       > + if (r) {
>>>       > + c->recv.len += (size_t) n;
>>>       > + mg_call(c, MG_EV_READ, &n);
>>>       > + } else {
>>>       > + mg_iobuf_del(&c->send, 0, (size_t) n);
>>>       > + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
>>>       > + if (c->send.len == 0) {
>>>       > + MG_EPOLL_MOD(c, 0);
>>>       > + }
>>>       > + mg_call(c, MG_EV_WRITE, &n);
>>>       > }
>>>       > }
>>>       > - return flags;
>>>       > }
>>>       >
>>>       > -static int uri_to_path(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - const struct mg_http_serve_opts *opts, char *path,
>>>       > - size_t path_size) {
>>>       > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > - struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p =
>>>      {0, 0};
>>>       > - while (mg_commalist(&s, &k, &v)) {
>>>       > - if (v.len == 0) v = k, k = mg_str("/");
>>>       > - if (hm->uri.len < k.len) continue;
>>>       > - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
>>>       > - u = k, p = v;
>>>       > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > + long n;
>>>       > + if (c->is_udp) {
>>>       > + union usa usa;
>>>       > + socklen_t slen = tousa(&c->rem, &usa);
>>>       > + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
>>>      <http://usa.sa>, slen);
>>>       > + if (n > 0) setlocaddr(FD(c), &c->loc);
>>>       > + } else {
>>>       > + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>>>       > }
>>>       > - return uri_to_path2(c, hm, fs, u, p, path, path_size);
>>>       > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
>>>       > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
>>>       > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > }
>>>       >
>>>       > -void mg_http_serve_dir(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - const struct mg_http_serve_opts *opts) {
>>>       > - char path[MG_PATH_MAX];
>>>       > - const char *sp = opts->ssi_pattern;
>>>       > - int flags = uri_to_path(c, hm, opts, path, sizeof(path));
>>>       > - if (flags < 0) {
>>>       > - // Do nothing: the response has already been sent by uri_to_path()
>>>       > - } else if (flags & MG_FS_DIR) {
>>>       > - listdir(c, hm, opts, path);
>>>       > - } else if (flags && sp != NULL &&
>>>       > - mg_globmatch(sp, strlen(sp), path, strlen(path))) {
>>>       > - mg_http_serve_ssi(c, opts->root_dir, path);
>>>       > +bool mg_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > + if (c->is_udp) {
>>>       > + long n = mg_io_send(c, buf, len);
>>>       > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
>>>      c->send.len,
>>>       > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
>>>       > + iolog(c, (char *) buf, n, false);
>>>       > + return n > 0;
>>>       > } else {
>>>       > - mg_http_serve_file(c, hm, path, opts);
>>>       > + return mg_iobuf_add(&c->send, c->send.len, buf, len);
>>>       > }
>>>       > }
>>>       >
>>>       > -static bool mg_is_url_safe(int c) {
>>>       > - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
>>>       > - (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c
>>>      == '~';
>>>       > +static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
>>>       > +#if defined(MG_CUSTOM_NONBLOCK)
>>>       > + MG_CUSTOM_NONBLOCK(fd);
>>>       > +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > + unsigned long on = 1;
>>>       > + ioctlsocket(fd, FIONBIO, &on);
>>>       > +#elif MG_ENABLE_RL
>>>       > + unsigned long on = 1;
>>>       > + ioctlsocket(fd, FIONBIO, &on);
>>>       > +#elif MG_ENABLE_FREERTOS_TCP
>>>       > + const BaseType_t off = 0;
>>>       > + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
>>>      != 0) (void) 0;
>>>       > + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
>>>      != 0) (void) 0;
>>>       > +#elif MG_ENABLE_LWIP
>>>       > + lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
>>>       > +#elif MG_ARCH == MG_ARCH_AZURERTOS
>>>       > + fcntl(fd, F_SETFL, O_NONBLOCK);
>>>       > +#elif MG_ARCH == MG_ARCH_TIRTOS
>>>       > + int val = 0;
>>>       > + setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
>>>       > + // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
>>>       > + int sz = sizeof(val);
>>>       > + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
>>>       > + val /= 2; // set send low-water mark at half send buffer size
>>>       > + setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
>>>       > +#else
>>>       > + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
>>>      Non-blocking mode
>>>       > + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
>>>       > +#endif
>>>       > }
>>>       >
>>>       > -size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t
>>>      len) {
>>>       > - size_t i, n = 0;
>>>       > - for (i = 0; i < sl; i++) {
>>>       > - int c = *(unsigned char *) &s[i];
>>>       > - if (n + 4 >= len) return 0;
>>>       > - if (mg_is_url_safe(c)) {
>>>       > - buf[n++] = s[i];
>>>       > +bool mg_open_listener(struct mg_connection *c, const char *url) {
>>>       > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>>>       > + bool success = false;
>>>       > + c->loc.port = mg_htons(mg_url_port(url));
>>>       > + if (!mg_aton(mg_url_host(url), &c->loc)) {
>>>       > + MG_ERROR(("invalid listening URL: %s", url));
>>>       > + } else {
>>>       > + union usa usa;
>>>       > + socklen_t slen = tousa(&c->loc, &usa);
>>>       > + int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
>>>       > + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
>>>      SOCK_STREAM;
>>>       > + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
>>>       > + (void) on;
>>>       > +
>>>       > + if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
>>>       > + MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
>>>       > +#if defined(SO_EXCLUSIVEADDRUSE)
>>>       > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
>>>       > + (char *) &on, sizeof(on))) != 0) {
>>>       > + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
>>>       > + MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on,
>>>      MG_SOCK_ERR(rc)));
>>>       > +#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
>>>       > + } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char
>>>      *) &on,
>>>       > + sizeof(on))) != 0) {
>>>       > + // 1. SO_REUSEADDR semantics on UNIX and Windows is different. On
>>>       > + // Windows, SO_REUSEADDR allows to bind a socket to a port
>>>      without error
>>>       > + // even if the port is already open by another program. This is
>>>      not the
>>>       > + // behavior SO_REUSEADDR was designed for, and leads to
>>>      hard-to-track
>>>       > + // failure scenarios.
>>>       > + //
>>>       > + // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by
>>>      defining
>>>       > + // SO_REUSE = 1 in lwipopts.h, otherwise the code below will
>>>      compile but
>>>       > + // won't work! (setsockopt will return EINVAL)
>>>       > + MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
>>>       > +#endif
>>>       > +#if MG_IPV6_V6ONLY
>>>       > + // Bind only to the V6 address, not V4 address on this port
>>>       > + } else if (c->loc.is_ip6 &&
>>>       > + (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
>>>       > + sizeof(on))) != 0) {
>>>       > + // See #2089. Allow to bind v4 and v6 sockets on the same port
>>>       > + MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
>>>       > +#endif
>>>       > + } else if ((rc = bind(fd, &usa.sa <http://usa.sa>, slen)) != 0) {
>>>       > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
>>>       > + } else if ((type == SOCK_STREAM &&
>>>       > + (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
>>>       > + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
>>>       > + // In case port was set to 0, get the real port number
>>>       > + MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
>>>       > } else {
>>>       > - buf[n++] = '%';
>>>       > - mg_hex(&s[i], 1, &buf[n]);
>>>       > - n += 2;
>>>       > + setlocaddr(fd, &c->loc);
>>>       > + mg_set_non_blocking_mode(fd);
>>>       > + c->fd = S2PTR(fd);
>>>       > + MG_EPOLL_ADD(c);
>>>       > + success = true;
>>>       > }
>>>       > }
>>>       > - if (len > 0 && n < len - 1) buf[n] = '\0'; // Null-terminate
>>>      the destination
>>>       > - if (len > 0) buf[len - 1] = '\0'; // Always.
>>>       > - return n;
>>>       > + if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
>>>       > + return success;
>>>       > }
>>>       >
>>>       > -void mg_http_creds(struct mg_http_message *hm, char *user,
>>>      size_t userlen,
>>>       > - char *pass, size_t passlen) {
>>>       > - struct mg_str *v = mg_http_get_header(hm, "Authorization");
>>>       > - user[0] = pass[0] = '\0';
>>>       > - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
>>>       > - char buf[256];
>>>       > - int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
>>>       > - const char *p = (const char *) memchr(buf, ':', n > 0 ?
>>>      (size_t) n : 0);
>>>       > - if (p != NULL) {
>>>       > - mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
>>>       > - mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p +
>>>      1);
>>>       > - }
>>>       > - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ",
>>>      7) == 0) {
>>>       > - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
>>>       > - } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
>>>       > - struct mg_str t = mg_http_get_header_var(*v,
>>>      mg_str_n("access_token", 12));
>>>       > - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len,
>>>      t.ptr);
>>>       > +static long recv_raw(struct mg_connection *c, void *buf, size_t
>>>      len) {
>>>       > + long n = 0;
>>>       > + if (c->is_udp) {
>>>       > + union usa usa;
>>>       > + socklen_t slen = tousa(&c->rem, &usa);
>>>       > + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
>>>      <http://usa.sa>, &slen);
>>>       > + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
>>>       > } else {
>>>       > - mg_http_get_var(&hm->query, "access_token", pass, passlen);
>>>       > + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>>>       > }
>>>       > + MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
>>>       > + if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
>>>       > + if (MG_SOCK_RESET(n)) return MG_IO_RESET;
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > }
>>>       >
>>>       > -static struct mg_str stripquotes(struct mg_str s) {
>>>       > - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
>>>       > - ? mg_str_n(s.ptr + 1, s.len - 2)
>>>       > - : s;
>>>       > +static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
>>>       > + bool res = false;
>>>       > + if (io->len >= MG_MAX_RECV_SIZE) {
>>>       > + mg_error(c, "MG_MAX_RECV_SIZE");
>>>       > + } else if (io->size <= io->len &&
>>>       > + !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
>>>       > + mg_error(c, "OOM");
>>>       > + } else {
>>>       > + res = true;
>>>       > + }
>>>       > + return res;
>>>       > }
>>>       >
>>>       > -struct mg_str mg_http_get_header_var(struct mg_str s, struct
>>>      mg_str v) {
>>>       > - size_t i;
>>>       > - for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
>>>       > - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len)
>>>      == 0) {
>>>       > - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
>>>       > - int q = p < x && *p == '"' ? 1 : 0;
>>>       > - while (p < x &&
>>>       > - (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
>>>       > - p++;
>>>       > - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int)
>>>      v.len,
>>>       > - // v.ptr, (int) (p - b), b));
>>>       > - return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
>>>       > +// NOTE(lsm): do only one iteration of reads, cause some systems
>>>       > +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
>>>      no data
>>>       > +static void read_conn(struct mg_connection *c) {
>>>       > + if (ioalloc(c, &c->recv)) {
>>>       > + char *buf = (char *) &c->recv.buf[c->recv.len];
>>>       > + size_t len = c->recv.size - c->recv.len;
>>>       > + long n = -1;
>>>       > + if (c->is_tls) {
>>>       > + if (!ioalloc(c, &c->rtls)) return;
>>>       > + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
>>>       > + c->rtls.size - c->rtls.len);
>>>       > + if (n == MG_IO_ERR && c->rtls.len == 0) {
>>>       > + // Close only if we have fully drained both raw (rtls) and TLS
>>>      buffers
>>>       > + c->is_closing = 1;
>>>       > + } else {
>>>       > + if (n > 0) c->rtls.len += (size_t) n;
>>>       > + if (c->is_tls_hs) mg_tls_handshake(c);
>>>       > + n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
>>>       > + }
>>>       > + } else {
>>>       > + n = recv_raw(c, buf, len);
>>>       > }
>>>       > + MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd,
>>>      c->send.len,
>>>       > + c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
>>>       > + iolog(c, buf, n, true);
>>>       > }
>>>       > - return mg_str_n(NULL, 0);
>>>       > }
>>>       >
>>>       > -bool mg_http_match_uri(const struct mg_http_message *hm, const
>>>      char *glob) {
>>>       > - return mg_match(hm->uri, mg_str(glob), NULL);
>>>       > +static void write_conn(struct mg_connection *c) {
>>>       > + char *buf = (char *) c->send.buf;
>>>       > + size_t len = c->send.len;
>>>       > + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
>>>      buf, len);
>>>       > + MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d",
>>>      c->id, c->fd,
>>>       > + (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>>>       > + (long) c->recv.size, n, MG_SOCK_ERR(n)));
>>>       > + iolog(c, buf, n, false);
>>>       > }
>>>       >
>>>       > -long mg_http_upload(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - struct mg_fs *fs, const char *path, size_t max_size) {
>>>       > - char buf[20] = "0";
>>>       > - long res = 0, offset;
>>>       > - mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
>>>       > - offset = strtol(buf, NULL, 0);
>>>       > - if (hm->body.len == 0) {
>>>       > - mg_http_reply(c, 200, "", "%ld", res); // Nothing to write
>>>       > +static void close_conn(struct mg_connection *c) {
>>>       > + if (FD(c) != MG_INVALID_SOCKET) {
>>>       > +#if MG_ENABLE_EPOLL
>>>       > + epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
>>>       > +#endif
>>>       > + closesocket(FD(c));
>>>       > +#if MG_ENABLE_FREERTOS_TCP
>>>       > + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
>>>       > +#endif
>>>       > + }
>>>       > + mg_close_conn(c);
>>>       > +}
>>>       > +
>>>       > +static void connect_conn(struct mg_connection *c) {
>>>       > + union usa usa;
>>>       > + socklen_t n = sizeof(usa);
>>>       > + // Use getpeername() to test whether we have connected
>>>       > + if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
>>>       > + c->is_connecting = 0;
>>>       > + setlocaddr(FD(c), &c->loc);
>>>       > + mg_call(c, MG_EV_CONNECT, NULL);
>>>       > + MG_EPOLL_MOD(c, 0);
>>>       > + if (c->is_tls_hs) mg_tls_handshake(c);
>>>       > } else {
>>>       > - struct mg_fd *fd;
>>>       > - size_t current_size = 0;
>>>       > - MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len,
>>>      offset));
>>>       > - if (offset == 0) fs->rm(path); // If offset if 0, truncate file
>>>       > - fs->st(path, &current_size, NULL);
>>>       > - if (offset < 0) {
>>>       > - mg_http_reply(c, 400, "", "offset required");
>>>       > - res = -1;
>>>       > - } else if (offset > 0 && current_size != (size_t) offset) {
>>>       > - mg_http_reply(c, 400, "", "%s: offset mismatch", path);
>>>       > - res = -2;
>>>       > - } else if ((size_t) offset + hm->body.len > max_size) {
>>>       > - mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
>>>       > - (unsigned long) max_size);
>>>       > - res = -3;
>>>       > - } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
>>>       > - mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
>>>       > - res = -4;
>>>       > + mg_error(c, "socket error");
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void setsockopts(struct mg_connection *c) {
>>>       > +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
>>>       > + MG_ARCH == MG_ARCH_TIRTOS
>>>       > + (void) c;
>>>       > +#else
>>>       > + int on = 1;
>>>       > +#if !defined(SOL_TCP)
>>>       > +#define SOL_TCP IPPROTO_TCP
>>>       > +#endif
>>>       > + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
>>>      sizeof(on)) != 0)
>>>       > + (void) 0;
>>>       > + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
>>>      sizeof(on)) !=
>>>       > + 0)
>>>       > + (void) 0;
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +void mg_connect_resolved(struct mg_connection *c) {
>>>       > + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
>>>       > + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
>>>      resolved IP
>>>       > + c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
>>>       > + c->is_resolving = 0; // Clear resolving flag
>>>       > + if (FD(c) == MG_INVALID_SOCKET) {
>>>       > + mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
>>>       > + } else if (c->is_udp) {
>>>       > + MG_EPOLL_ADD(c);
>>>       > +#if MG_ARCH == MG_ARCH_TIRTOS
>>>       > + union usa usa; // TI-RTOS NDK requires binding to receive on
>>>      UDP sockets
>>>       > + socklen_t slen = tousa(&c->loc, &usa);
>>>       > + if ((rc = bind(c->fd, &usa.sa <http://usa.sa>, slen)) != 0)
>>>       > + MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
>>>       > +#endif
>>>       > + setlocaddr(FD(c), &c->loc);
>>>       > + mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > + mg_call(c, MG_EV_CONNECT, NULL);
>>>       > + } else {
>>>       > + union usa usa;
>>>       > + socklen_t slen = tousa(&c->rem, &usa);
>>>       > + mg_set_non_blocking_mode(FD(c));
>>>       > + setsockopts(c);
>>>       > + MG_EPOLL_ADD(c);
>>>       > + mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > + rc = connect(FD(c), &usa.sa <http://usa.sa>, slen); // Attempt
>>>      to connect
>>>       > + if (rc == 0) { // Success
>>>       > + setlocaddr(FD(c), &c->loc);
>>>       > + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user
>>>       > + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP
>>>      handshake
>>>       > + MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port,
>>>      &c->rem));
>>>       > + c->is_connecting = 1;
>>>       > } else {
>>>       > - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
>>>       > - mg_fs_close(fd);
>>>       > - mg_http_reply(c, 200, "", "%ld", res);
>>>       > + mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
>>>       > }
>>>       > }
>>>       > - return res;
>>>       > -}
>>>       > -
>>>       > -int mg_http_status(const struct mg_http_message *hm) {
>>>       > - return atoi(hm->uri.ptr);
>>>       > -}
>>>       > -
>>>       > -// If a server sends data to the client using chunked encoding,
>>>      Mongoose strips
>>>       > -// off the chunking prefix (hex length and \r\n) and suffix
>>>      (\r\n), appends the
>>>       > -// stripped data to the body, and fires the MG_EV_HTTP_CHUNK
>>>      event. When zero
>>>       > -// chunk is received, we fire MG_EV_HTTP_MSG, and the body
>>>      already has all
>>>       > -// chunking prefixes/suffixes stripped.
>>>       > -//
>>>       > -// If a server sends data without chunked encoding, we also fire
>>>      a series of
>>>       > -// MG_EV_HTTP_CHUNK events for every received piece of data, and
>>>      then we fire
>>>       > -// MG_EV_HTTP_MSG event in the end.
>>>       > -//
>>>       > -// We track total processed length in the c->pfn_data, which is
>>>      a void *
>>>       > -// pointer: we store a size_t value there.
>>>       > -static bool getchunk(struct mg_str s, size_t *prefixlen, size_t
>>>      *datalen) {
>>>       > - size_t i = 0, n;
>>>       > - while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
>>>       > - n = mg_unhexn(s.ptr, i);
>>>       > - // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
>>>       > - if (s.len < i + n + 4) return false; // Chunk not yet fully
>>>      buffered
>>>       > - if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
>>>       > - if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n')
>>>      return false;
>>>       > - *prefixlen = i + 2;
>>>       > - *datalen = n;
>>>       > - return true;
>>>       > }
>>>       >
>>>       > -static bool mg_is_chunked(struct mg_http_message *hm) {
>>>       > - const char *needle = "chunked";
>>>       > - struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
>>>       > - return te != NULL && mg_vcasecmp(te, needle) == 0;
>>>       > -}
>>>       > -
>>>       > -void mg_http_delete_chunk(struct mg_connection *c, struct
>>>      mg_http_message *hm) {
>>>       > - size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
>>>       > - mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
>>>       > - c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
>>>       > -}
>>>       > -
>>>       > -static void deliver_chunked_chunks(struct mg_connection *c,
>>>      size_t hlen,
>>>       > - struct mg_http_message *hm, bool *next) {
>>>       > - // | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
>>>       > - // +------------------+--------------------------+----
>>>       > - // | hlen | chunk1 | ......
>>>       > - char *buf = (char *) &c->recv.buf[hlen], *p = buf;
>>>       > - size_t len = c->recv.len - hlen;
>>>       > - size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
>>>       > - size_t mark, pl, dl, del = 0, ofs = 0;
>>>       > - bool last = false;
>>>       > - if (processed <= len) len -= processed, buf += processed;
>>>       > - while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl,
>>>      &dl)) {
>>>       > - size_t saved = c->recv.len;
>>>       > - memmove(p + processed, buf + ofs + pl, dl);
>>>       > - // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
>>>       > - hm->chunk = mg_str_n(p + processed, dl);
>>>       > - mg_call(c, MG_EV_HTTP_CHUNK, hm);
>>>       > - ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
>>>       > - processed += dl;
>>>       > - if (c->recv.len != saved) processed -= dl, buf -= dl;
>>>       > - // mg_hexdump(c->recv.buf, hlen + processed);
>>>       > - last = (dl == 0);
>>>       > - }
>>>       > - mg_iobuf_del(&c->recv, hlen + processed, del);
>>>       > - mark = ((size_t) c->pfn_data) & MG_DMARK;
>>>       > - c->pfn_data = (void *) (processed | mark);
>>>       > - if (last) {
>>>       > - hm->body.len = processed;
>>>       > - hm->message.len = hlen + processed;
>>>       > - c->pfn_data = NULL;
>>>       > - if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
>>>       > - // MG_INFO(("LAST, mark: %lx", mark));
>>>       > - // mg_hexdump(c->recv.buf, c->recv.len);
>>>       > - }
>>>       > +static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
>>>       > + socklen_t *len) {
>>>       > + MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>>>       > + do {
>>>       > + memset(usa, 0, sizeof(*usa));
>>>       > + fd = accept(sock, &usa->sa, len);
>>>       > + } while (MG_SOCK_INTR(fd));
>>>       > + return fd;
>>>       > }
>>>       >
>>>       > -static void deliver_normal_chunks(struct mg_connection *c,
>>>      size_t hlen,
>>>       > - struct mg_http_message *hm, bool *next) {
>>>       > - size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
>>>       > - size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
>>>       > - hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len -
>>>      hlen);
>>>       > - if (processed <= hm->chunk.len && !deleted) {
>>>       > - hm->chunk.len -= processed;
>>>       > - hm->chunk.ptr += processed;
>>>       > - }
>>>       > - left = hm->body.len < processed ? 0 : hm->body.len - processed;
>>>       > - if (hm->chunk.len > left) hm->chunk.len = left;
>>>       > - if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
>>>       > - processed += hm->chunk.len;
>>>       > - deleted = ((size_t) c->pfn_data) & MG_DMARK; // Re-evaluate
>>>      after user call
>>>       > - if (processed >= hm->body.len) { // Last, 0-len chunk
>>>       > - hm->chunk.len = 0; // Reset length
>>>       > - mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
>>>       > - c->pfn_data = NULL; // Reset processed counter
>>>       > - if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen),
>>>      *next = true;
>>>       > +static void accept_conn(struct mg_mgr *mgr, struct mg_connection
>>>      *lsn) {
>>>       > + struct mg_connection *c = NULL;
>>>       > + union usa usa;
>>>       > + socklen_t sa_len = sizeof(usa);
>>>       > + MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
>>>       > + if (fd == MG_INVALID_SOCKET) {
>>>       > +#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
>>>       > + // AzureRTOS, in non-block socket mode can mark listening
>>>      socket readable
>>>       > + // even it is not. See comment for 'select' func implementation in
>>>       > + // nx_bsd.c That's not an error, just should try later
>>>       > + if (errno != EAGAIN)
>>>       > +#endif
>>>       > + MG_ERROR(("%lu accept failed, errno %d", lsn->id,
>>>      MG_SOCK_ERR(-1)));
>>>       > +#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
>>>       > + (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
>>>       > + } else if ((long) fd >= FD_SETSIZE) {
>>>       > + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
>>>       > + closesocket(fd);
>>>       > +#endif
>>>       > + } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > + MG_ERROR(("%lu OOM", lsn->id));
>>>       > + closesocket(fd);
>>>       > } else {
>>>       > - c->pfn_data = (void *) (processed | deleted); // if it is set
>>>       > + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
>>>       > + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > + c->fd = S2PTR(fd);
>>>       > + MG_EPOLL_ADD(c);
>>>       > + mg_set_non_blocking_mode(FD(c));
>>>       > + setsockopts(c);
>>>       > + c->is_accepted = 1;
>>>       > + c->is_hexdumping = lsn->is_hexdumping;
>>>       > + c->loc = lsn->loc;
>>>       > + c->pfn = lsn->pfn;
>>>       > + c->pfn_data = lsn->pfn_data;
>>>       > + c->fn = lsn->fn;
>>>       > + c->fn_data = lsn->fn_data;
>>>       > + MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd,
>>>      mg_print_ip_port,
>>>       > + &c->rem, mg_print_ip_port, &c->loc));
>>>       > + mg_call(c, MG_EV_OPEN, NULL);
>>>       > + mg_call(c, MG_EV_ACCEPT, NULL);
>>>       > }
>>>       > }
>>>       >
>>>       > -static void http_cb(struct mg_connection *c, int ev, void *evd,
>>>      void *fnd) {
>>>       > - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
>>>       > - struct mg_http_message hm;
>>>       > - // mg_hexdump(c->recv.buf, c->recv.len);
>>>       > - while (c->recv.buf != NULL && c->recv.len > 0) {
>>>       > - bool next = false;
>>>       > - int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
>>>       > - if (hlen < 0) {
>>>       > - mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
>>>       > - break;
>>>       > - }
>>>       > - if (c->is_resp) break; // Response is still generated
>>>       > - if (hlen == 0) break; // Request is not buffered yet
>>>       > - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
>>>       > - hm.message.len = c->recv.len; // and closes now, deliver a MSG
>>>       > - hm.body.len = hm.message.len - (size_t) (hm.body.ptr -
>>>      hm.message.ptr);
>>>       > - }
>>>       > - if (mg_is_chunked(&hm)) {
>>>       > - deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
>>>       > - } else {
>>>       > - deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
>>>       > - }
>>>       > - if (next) continue; // Chunks & request were deleted
>>>       > - // Chunk events are delivered. If we have full body, deliver MSG
>>>       > - if (c->recv.len < hm.message.len) break;
>>>       > - if (c->is_accepted) c->is_resp = 1; // Start generating response
>>>       > - mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
>>>       > - mg_iobuf_del(&c->recv, 0, hm.message.len);
>>>       > - }
>>>       > - }
>>>       > - (void) evd, (void) fnd;
>>>       > +static bool can_read(const struct mg_connection *c) {
>>>       > + return c->is_full == false;
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>>>       > - if (c != NULL) c->pfn = http_cb;
>>>       > - return c;
>>>       > +static bool can_write(const struct mg_connection *c) {
>>>       > + return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>>>       > - if (c != NULL) c->pfn = http_cb;
>>>       > - return c;
>>>       > +static bool skip_iotest(const struct mg_connection *c) {
>>>       > + return (c->is_closing || c->is_resolving || FD(c) ==
>>>      MG_INVALID_SOCKET) ||
>>>       > + (can_read(c) == false && can_write(c) == false);
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/iobuf.c"
>>>       > -#endif
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -// Not using memset for zeroing memory, cause it can be dropped
>>>      by compiler
>>>       > -// See https://github.com/cesanta/mongoose/pull/1265
>>>      <https://github.com/cesanta/mongoose/pull/1265>
>>>       > -static void zeromem(volatile unsigned char *buf, size_t len) {
>>>       > - if (buf != NULL) {
>>>       > - while (len--) *buf++ = 0;
>>>       > +static void mg_iotest(struct mg_mgr *mgr, int ms) {
>>>       > +#if MG_ENABLE_FREERTOS_TCP
>>>       > + struct mg_connection *c;
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > + c->is_readable = c->is_writable = 0;
>>>       > + if (skip_iotest(c)) continue;
>>>       > + if (can_read(c))
>>>       > + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
>>>       > + if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
>>>       > + if (c->is_closing) ms = 1;
>>>       > }
>>>       > -}
>>>       > -
>>>       > -static size_t roundup(size_t size, size_t align) {
>>>       > - return align == 0 ? size : (size + align - 1) / align * align;
>>>       > -}
>>>       > -
>>>       > -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
>>>       > - int ok = 1;
>>>       > - new_size = roundup(new_size, io->align);
>>>       > - if (new_size == 0) {
>>>       > - zeromem(io->buf, io->size);
>>>       > - free(io->buf);
>>>       > - io->buf = NULL;
>>>       > - io->len = io->size = 0;
>>>       > - } else if (new_size != io->size) {
>>>       > - // NOTE(lsm): do not use realloc here. Use calloc/free only, to
>>>      ease the
>>>       > - // porting to some obscure platforms like FreeRTOS
>>>       > - void *p = calloc(1, new_size);
>>>       > - if (p != NULL) {
>>>       > - size_t len = new_size < io->len ? new_size : io->len;
>>>       > - if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
>>>       > - zeromem(io->buf, io->size);
>>>       > - free(io->buf);
>>>       > - io->buf = (unsigned char *) p;
>>>       > - io->size = new_size;
>>>       > + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
>>>       > + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
>>>       > + c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
>>>       > + if (c->fd != MG_INVALID_SOCKET)
>>>       > + FreeRTOS_FD_CLR(c->fd, mgr->ss,
>>>       > + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
>>>       > + }
>>>       > +#elif MG_ENABLE_EPOLL
>>>       > + size_t max = 1;
>>>       > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > + c->is_readable = c->is_writable = 0;
>>>       > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1,
>>>      c->is_readable = 1;
>>>       > + if (can_write(c)) MG_EPOLL_MOD(c, 1);
>>>       > + if (c->is_closing) ms = 1;
>>>       > + max++;
>>>       > + }
>>>       > + struct epoll_event *evs = (struct epoll_event *) alloca(max *
>>>      sizeof(evs[0]));
>>>       > + int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
>>>       > + for (int i = 0; i < n; i++) {
>>>       > + struct mg_connection *c = (struct mg_connection *)
>>>      evs[i].data.ptr;
>>>       > + if (evs[i].events & EPOLLERR) {
>>>       > + mg_error(c, "socket error");
>>>       > + } else if (c->is_readable == 0) {
>>>       > + bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
>>>       > + bool wr = evs[i].events & EPOLLOUT;
>>>       > + c->is_readable = can_read(c) && rd ? 1U : 0;
>>>       > + c->is_writable = can_write(c) && wr ? 1U : 0;
>>>       > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>>>       > + }
>>>       > + }
>>>       > + (void) skip_iotest;
>>>       > +#elif MG_ENABLE_POLL
>>>       > + nfds_t n = 0;
>>>       > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) n++;
>>>       > + struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
>>>       > + memset(fds, 0, n * sizeof(fds[0]));
>>>       > + n = 0;
>>>       > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > + c->is_readable = c->is_writable = 0;
>>>       > + if (skip_iotest(c)) {
>>>       > + // Socket not valid, ignore
>>>       > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
>>>       > + ms = 1; // Don't wait if TLS is ready
>>>       > } else {
>>>       > - ok = 0;
>>>       > - MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t)
>>>      new_size));
>>>       > + fds[n].fd = FD(c);
>>>       > + if (can_read(c)) fds[n].events |= POLLIN;
>>>       > + if (can_write(c)) fds[n].events |= POLLOUT;
>>>       > + if (c->is_closing) ms = 1;
>>>       > + n++;
>>>       > }
>>>       > }
>>>       > - return ok;
>>>       > -}
>>>       > -
>>>       > -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
>>>       > - io->buf = NULL;
>>>       > - io->align = align;
>>>       > - io->size = io->len = 0;
>>>       > - return mg_iobuf_resize(io, size);
>>>       > -}
>>>       >
>>>       > -size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void
>>>      *buf,
>>>       > - size_t len) {
>>>       > - size_t new_size = roundup(io->len + len, io->align);
>>>       > - mg_iobuf_resize(io, new_size); // Attempt to resize
>>>       > - if (new_size != io->size) len = 0; // Resize failure, append
>>>      nothing
>>>       > - if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs,
>>>      io->len - ofs);
>>>       > - if (buf != NULL) memmove(io->buf + ofs, buf, len);
>>>       > - if (ofs > io->len) io->len += ofs - io->len;
>>>       > - io->len += len;
>>>       > - return len;
>>>       > -}
>>>       > + // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
>>>       > + if (poll(fds, n, ms) < 0) {
>>>       > +#if MG_ARCH == MG_ARCH_WIN32
>>>       > + if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
>>>       > +#endif
>>>       > + memset(fds, 0, n * sizeof(fds[0]));
>>>       > + }
>>>       > + n = 0;
>>>       > + for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > + if (skip_iotest(c)) {
>>>       > + // Socket not valid, ignore
>>>       > + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
>>>       > + c->is_readable = 1;
>>>       > + } else {
>>>       > + if (fds[n].revents & POLLERR) {
>>>       > + mg_error(c, "socket error");
>>>       > + } else {
>>>       > + c->is_readable =
>>>       > + (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
>>>       > + c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
>>>       > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>>>       > + }
>>>       > + n++;
>>>       > + }
>>>       > + }
>>>       > +#else
>>>       > + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
>>>      {0, 0}, *tvp;
>>>       > + struct mg_connection *c;
>>>       > + fd_set rset, wset, eset;
>>>       > + MG_SOCKET_TYPE maxfd = 0;
>>>       > + int rc;
>>>       >
>>>       > -size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
>>>       > - if (ofs > io->len) ofs = io->len;
>>>       > - if (ofs + len > io->len) len = io->len - ofs;
>>>       > - if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len,
>>>      io->len - ofs - len);
>>>       > - if (io->buf) zeromem(io->buf + io->len - len, len);
>>>       > - io->len -= len;
>>>       > - return len;
>>>       > -}
>>>       > + FD_ZERO(&rset);
>>>       > + FD_ZERO(&wset);
>>>       > + FD_ZERO(&eset);
>>>       > + tvp = ms < 0 ? NULL : &tv;
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > + c->is_readable = c->is_writable = 0;
>>>       > + if (skip_iotest(c)) continue;
>>>       > + FD_SET(FD(c), &eset);
>>>       > + if (can_read(c)) FD_SET(FD(c), &rset);
>>>       > + if (can_write(c)) FD_SET(FD(c), &wset);
>>>       > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
>>>       > + if (FD(c) > maxfd) maxfd = FD(c);
>>>       > + if (c->is_closing) ms = 1;
>>>       > + }
>>>       >
>>>       > -void mg_iobuf_free(struct mg_iobuf *io) {
>>>       > - mg_iobuf_resize(io, 0);
>>>       > -}
>>>       > + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) <
>>>      0) {
>>>       > +#if MG_ARCH == MG_ARCH_WIN32
>>>       > + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
>>>      sockets
>>>       > +#else
>>>       > + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
>>>       > +#endif
>>>       > + FD_ZERO(&rset);
>>>       > + FD_ZERO(&wset);
>>>       > + FD_ZERO(&eset);
>>>       > + }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/json.c"
>>>       > + for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > + if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
>>>       > + mg_error(c, "socket error");
>>>       > + } else {
>>>       > + c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>>>      &rset);
>>>       > + c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>>>      &wset);
>>>       > + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
>>>       > + }
>>>       > + }
>>>       > #endif
>>>       > +}
>>>       >
>>>       > +static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
>>>       > + socklen_t n = sizeof(usa[0].sin);
>>>       > + bool success = false;
>>>       >
>>>       > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > + (void) memset(&usa[0], 0, sizeof(usa[0]));
>>>       > + usa[0].sin.sin_family = AF_INET;
>>>       > + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
>>>      127.0.0.1
>>>       > + usa[1] = usa[0];
>>>       >
>>>       > -
>>>       > -static const char *escapeseq(int esc) {
>>>       > - return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
>>>       > + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
>>>      MG_INVALID_SOCKET &&
>>>       > + (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
>>>       > + bind(sp[0], &usa[0].sa, n) == 0 && //
>>>       > + bind(sp[1], &usa[1].sa, n) == 0 && //
>>>       > + getsockname(sp[0], &usa[0].sa, &n) == 0 && //
>>>       > + getsockname(sp[1], &usa[1].sa, &n) == 0 && //
>>>       > + connect(sp[0], &usa[1].sa, n) == 0 && //
>>>       > + connect(sp[1], &usa[0].sa, n) == 0) { //
>>>       > + success = true;
>>>       > + }
>>>       > + if (!success) {
>>>       > + if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
>>>       > + if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
>>>       > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > + }
>>>       > + return success;
>>>       > }
>>>       >
>>>       > -static char json_esc(int c, int esc) {
>>>       > - const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
>>>       > - for (p = esc1; *p != '\0'; p++) {
>>>       > - if (*p == c) return esc2[p - esc1];
>>>       > +// mg_wakeup() event handler
>>>       > +static void wufn(struct mg_connection *c, int ev, void *ev_data) {
>>>       > + if (ev == MG_EV_READ) {
>>>       > + unsigned long *id = (unsigned long *) c->recv.buf;
>>>       > + // MG_INFO(("Got data"));
>>>       > + // mg_hexdump(c->recv.buf, c->recv.len);
>>>       > + if (c->recv.len >= sizeof(*id)) {
>>>       > + struct mg_connection *t;
>>>       > + for (t = c->mgr->conns; t != NULL; t = t->next) {
>>>       > + if (t->id == *id) {
>>>       > + struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
>>>       > + c->recv.len - sizeof(*id));
>>>       > + mg_call(t, MG_EV_WAKEUP, &data);
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > + c->recv.len = 0; // Consume received data
>>>       > + } else if (ev == MG_EV_CLOSE) {
>>>       > + closesocket(c->mgr->pipe); // When we're closing, close the other
>>>       > + c->mgr->pipe = MG_INVALID_SOCKET; // side of the socketpair, too
>>>       > }
>>>       > - return 0;
>>>       > + (void) ev_data;
>>>       > }
>>>       >
>>>       > -static int mg_pass_string(const char *s, int len) {
>>>       > - int i;
>>>       > - for (i = 0; i < len; i++) {
>>>       > - if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
>>>       > - i++;
>>>       > - } else if (s[i] == '\0') {
>>>       > - return MG_JSON_INVALID;
>>>       > - } else if (s[i] == '"') {
>>>       > - return i;
>>>       > +bool mg_wakeup_init(struct mg_mgr *mgr) {
>>>       > + bool ok = false;
>>>       > + if (mgr->pipe == MG_INVALID_SOCKET) {
>>>       > + union usa usa[2];
>>>       > + MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
>>>       > + struct mg_connection *c = NULL;
>>>       > + if (!mg_socketpair(sp, usa)) {
>>>       > + MG_ERROR(("Cannot create socket pair"));
>>>       > + } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) ==
>>>      NULL) {
>>>       > + closesocket(sp[0]);
>>>       > + closesocket(sp[1]);
>>>       > + sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > + } else {
>>>       > + tomgaddr(&usa[0], &c->rem, false);
>>>       > + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
>>>      sp[0]));
>>>       > + mgr->pipe = sp[0];
>>>       > + ok = true;
>>>       > }
>>>       > }
>>>       > - return MG_JSON_INVALID;
>>>       > + return ok;
>>>       > }
>>>       >
>>>       > -static double mg_atod(const char *p, int len, int *numlen) {
>>>       > - double d = 0.0;
>>>       > - int i = 0, sign = 1;
>>>       > -
>>>       > - // Sign
>>>       > - if (i < len && *p == '-') {
>>>       > - sign = -1, i++;
>>>       > - } else if (i < len && *p == '+') {
>>>       > - i++;
>>>       > +bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const
>>>      void *buf,
>>>       > + size_t len) {
>>>       > + if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
>>>       > + char *extended_buf = (char *) alloca(len + sizeof(conn_id));
>>>       > + memcpy(extended_buf, &conn_id, sizeof(conn_id));
>>>       > + memcpy(extended_buf + sizeof(conn_id), buf, len);
>>>       > + send(mgr->pipe, extended_buf, len + sizeof(conn_id),
>>>      MSG_NONBLOCKING);
>>>       > + return true;
>>>       > }
>>>       > + return false;
>>>       > +}
>>>       >
>>>       > - // Decimal
>>>       > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>>>       > - d *= 10.0;
>>>       > - d += p[i] - '0';
>>>       > - }
>>>       > - d *= sign;
>>>       > +void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>>>       > + struct mg_connection *c, *tmp;
>>>       > + uint64_t now;
>>>       >
>>>       > - // Fractional
>>>       > - if (i < len && p[i] == '.') {
>>>       > - double frac = 0.0, base = 0.1;
>>>       > - i++;
>>>       > - for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
>>>       > - frac += base * (p[i] - '0');
>>>       > - base /= 10.0;
>>>       > + mg_iotest(mgr, ms);
>>>       > + now = mg_millis();
>>>       > + mg_timer_poll(&mgr->timers, now);
>>>       > +
>>>       > + for (c = mgr->conns; c != NULL; c = tmp) {
>>>       > + bool is_resp = c->is_resp;
>>>       > + tmp = c->next;
>>>       > + mg_call(c, MG_EV_POLL, &now);
>>>       > + if (is_resp && !c->is_resp) {
>>>       > + long n = 0;
>>>       > + mg_call(c, MG_EV_READ, &n);
>>>       > + }
>>>       > + MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
>>>       > + c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
>>>       > + c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
>>>       > + c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
>>>       > + c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
>>>       > + if (c->is_resolving || c->is_closing) {
>>>       > + // Do nothing
>>>       > + } else if (c->is_listening && c->is_udp == 0) {
>>>       > + if (c->is_readable) accept_conn(mgr, c);
>>>       > + } else if (c->is_connecting) {
>>>       > + if (c->is_readable || c->is_writable) connect_conn(c);
>>>       > + //} else if (c->is_tls_hs) {
>>>       > + // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
>>>       > + } else {
>>>       > + if (c->is_readable) read_conn(c);
>>>       > + if (c->is_writable) write_conn(c);
>>>       > }
>>>       > - d += frac * sign;
>>>       > - }
>>>       >
>>>       > - // Exponential
>>>       > - if (i < len && (p[i] == 'e' || p[i] == 'E')) {
>>>       > - int j, exp = 0, minus = 0;
>>>       > - i++;
>>>       > - if (i < len && p[i] == '-') minus = 1, i++;
>>>       > - if (i < len && p[i] == '+') i++;
>>>       > - while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
>>>       > - exp = exp * 10 + (p[i++] - '0');
>>>       > - if (minus) exp = -exp;
>>>       > - for (j = 0; j < exp; j++) d *= 10.0;
>>>       > - for (j = 0; j < -exp; j++) d /= 10.0;
>>>       > + if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>>>       > + if (c->is_closing) close_conn(c);
>>>       > }
>>>       > -
>>>       > - if (numlen != NULL) *numlen = i;
>>>       > - return d;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -int mg_json_get(struct mg_str json, const char *path, int
>>>      *toklen) {
>>>       > - const char *s = json.ptr;
>>>       > - int len = (int) json.len;
>>>       > - enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting =
>>>      S_VALUE;
>>>       > - unsigned char nesting[MG_JSON_MAX_DEPTH];
>>>       > - int i = 0; // Current offset in `s`
>>>       > - int j = 0; // Offset in `s` we're looking for (return value)
>>>       > - int depth = 0; // Current depth (nesting level)
>>>       > - int ed = 0; // Expected depth
>>>       > - int pos = 1; // Current position in `path`
>>>       > - int ci = -1, ei = -1; // Current and expected index in array
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/ssi.c"
>>>       > +#endif
>>>       >
>>>       > - if (toklen) *toklen = 0;
>>>       > - if (path[0] != '$') return MG_JSON_INVALID;
>>>       >
>>>       > -#define MG_CHECKRET(x) \
>>>       > - do { \
>>>       > - if (depth == ed && path[pos] == '\0' && ci == ei) { \
>>>       > - if (toklen) *toklen = i - j + 1; \
>>>       > - return j; \
>>>       > - } \
>>>       > - } while (0)
>>>       >
>>>       > -// In the ascii table, the distance between `[` and `]` is 2.
>>>       > -// Ditto for `{` and `}`. Hence +2 in the code below.
>>>       > -#define MG_EOO(x) \
>>>       > - do { \
>>>       > - if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \
>>>       > - if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
>>>       > - depth--; \
>>>       > - MG_CHECKRET(x); \
>>>       > - } while (0)
>>>       >
>>>       > - for (i = 0; i < len; i++) {
>>>       > - unsigned char c = ((unsigned char *) s)[i];
>>>       > - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
>>>       > - switch (expecting) {
>>>       > - case S_VALUE:
>>>       > - // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed,
>>>      ci, ei);
>>>       > - if (depth == ed) j = i;
>>>       > - if (c == '{') {
>>>       > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>>>       > - if (depth == ed && path[pos] == '.' && ci == ei) {
>>>       > - // If we start the object, reset array indices
>>>       > - ed++, pos++, ci = ei = -1;
>>>       > - }
>>>       > - nesting[depth++] = c;
>>>       > - expecting = S_KEY;
>>>       > - break;
>>>       > - } else if (c == '[') {
>>>       > - if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
>>>       > - if (depth == ed && path[pos] == '[' && ei == ci) {
>>>       > - ed++, pos++, ci = 0;
>>>       > - for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
>>>       > - ei *= 10;
>>>       > - ei += path[pos] - '0';
>>>       > - }
>>>       > - if (path[pos] != 0) pos++;
>>>       > - }
>>>       > - nesting[depth++] = c;
>>>       > - break;
>>>       > - } else if (c == ']' && depth > 0) { // Empty array
>>>       > - MG_EOO(']');
>>>       > - } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4)
>>>      == 0) {
>>>       > - i += 3;
>>>       > - } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4)
>>>      == 0) {
>>>       > - i += 3;
>>>       > - } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5)
>>>      == 0) {
>>>       > - i += 4;
>>>       > - } else if (c == '-' || ((c >= '0' && c <= '9'))) {
>>>       > - int numlen = 0;
>>>       > - mg_atod(&s[i], len - i, &numlen);
>>>       > - i += numlen - 1;
>>>       > - } else if (c == '"') {
>>>       > - int n = mg_pass_string(&s[i + 1], len - i - 1);
>>>       > - if (n < 0) return n;
>>>       > - i += n + 1;
>>>       > - } else {
>>>       > - return MG_JSON_INVALID;
>>>       > - }
>>>       > - MG_CHECKRET('V');
>>>       > - if (depth == ed && ei >= 0) ci++;
>>>       > - expecting = S_COMMA_OR_EOO;
>>>       > - break;
>>>       > +#ifndef MG_MAX_SSI_DEPTH
>>>       > +#define MG_MAX_SSI_DEPTH 5
>>>       > +#endif
>>>       >
>>>       > - case S_KEY:
>>>       > - if (c == '"') {
>>>       > - int n = mg_pass_string(&s[i + 1], len - i - 1);
>>>       > - if (n < 0) return n;
>>>       > - if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
>>>       > - if (depth < ed) return MG_JSON_NOT_FOUND;
>>>       > - if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
>>>       > - // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
>>>       > - // &s[i + 1], n, depth, ed);
>>>       > - // NOTE(cpq): in the check sequence below is important.
>>>       > - // strncmp() must go first: it fails fast if the remaining
>>>      length of
>>>       > - // the path is smaller than `n`.
>>>       > - if (depth == ed && path[pos - 1] == '.' &&
>>>       > - strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
>>>       > - (path[pos + n] == '\0' || path[pos + n] == '.' ||
>>>       > - path[pos + n] == '[')) {
>>>       > - pos += n;
>>>       > - }
>>>       > - i += n + 1;
>>>       > - expecting = S_COLON;
>>>       > - } else if (c == '}') { // Empty object
>>>       > - MG_EOO('}');
>>>       > - expecting = S_COMMA_OR_EOO;
>>>       > - } else {
>>>       > - return MG_JSON_INVALID;
>>>       > - }
>>>       > - break;
>>>       > +#ifndef MG_SSI_BUFSIZ
>>>       > +#define MG_SSI_BUFSIZ 1024
>>>       > +#endif
>>>       >
>>>       > - case S_COLON:
>>>       > - if (c == ':') {
>>>       > - expecting = S_VALUE;
>>>       > +#if MG_ENABLE_SSI
>>>       > +static char *mg_ssi(const char *path, const char *root, int
>>>      depth) {
>>>       > + struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
>>>       > + FILE *fp = fopen(path, "rb");
>>>       > + if (fp != NULL) {
>>>       > + char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
>>>       > + int ch, intag = 0;
>>>       > + size_t len = 0;
>>>       > + buf[0] = arg[0] = '\0';
>>>       > + while ((ch = fgetc(fp)) != EOF) {
>>>       > + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
>>>      == '-') {
>>>       > + buf[len++] = (char) (ch & 0xff);
>>>       > + buf[len] = '\0';
>>>       > + if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
>>>       > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
>>>       > + *p = (char *) path + strlen(path), *data;
>>>       > + while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
>>>       > + mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
>>>      arg);
>>>       > + if (depth < MG_MAX_SSI_DEPTH &&
>>>       > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>>>       > + mg_iobuf_add(&b, b.len, data, strlen(data));
>>>       > + free(data);
>>>       > + } else {
>>>       > + MG_ERROR(("%s: file=%s error or too deep", path, arg));
>>>       > + }
>>>       > + } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
>>>       > + char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
>>>       > + mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
>>>       > + if (depth < MG_MAX_SSI_DEPTH &&
>>>       > + (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>>>       > + mg_iobuf_add(&b, b.len, data, strlen(data));
>>>       > + free(data);
>>>       > + } else {
>>>       > + MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
>>>       > + }
>>>       > } else {
>>>       > - return MG_JSON_INVALID;
>>>       > + // Unknown SSI tag
>>>       > + MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
>>>       > + mg_iobuf_add(&b, b.len, buf, len);
>>>       > }
>>>       > - break;
>>>       > -
>>>       > - case S_COMMA_OR_EOO:
>>>       > - if (depth <= 0) {
>>>       > - return MG_JSON_INVALID;
>>>       > - } else if (c == ',') {
>>>       > - expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
>>>       > - } else if (c == ']' || c == '}') {
>>>       > - MG_EOO('O');
>>>       > - if (depth == ed && ei >= 0) ci++;
>>>       > - } else {
>>>       > - return MG_JSON_INVALID;
>>>       > + intag = 0;
>>>       > + len = 0;
>>>       > + } else if (ch == '<') {
>>>       > + intag = 1;
>>>       > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>>>       > + len = 0;
>>>       > + buf[len++] = (char) (ch & 0xff);
>>>       > + } else if (intag) {
>>>       > + if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
>>>       > + intag = 0;
>>>       > + } else if (len >= sizeof(buf) - 2) {
>>>       > + MG_ERROR(("%s: SSI tag is too large", path));
>>>       > + len = 0;
>>>       > }
>>>       > - break;
>>>       > + buf[len++] = (char) (ch & 0xff);
>>>       > + } else {
>>>       > + buf[len++] = (char) (ch & 0xff);
>>>       > + if (len >= sizeof(buf)) {
>>>       > + mg_iobuf_add(&b, b.len, buf, len);
>>>       > + len = 0;
>>>       > + }
>>>       > + }
>>>       > }
>>>       > + if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>>>       > + if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
>>>       > + fclose(fp);
>>>       > }
>>>       > - return MG_JSON_NOT_FOUND;
>>>       > + (void) depth;
>>>       > + (void) root;
>>>       > + return (char *) b.buf;
>>>       > }
>>>       >
>>>       > -bool mg_json_get_num(struct mg_str json, const char *path,
>>>      double *v) {
>>>       > - int n, toklen, found = 0;
>>>       > - if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
>>>       > - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <=
>>>      '9'))) {
>>>       > - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
>>>       > - found = 1;
>>>       > - }
>>>       > - return found;
>>>       > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>>>       > + const char *fullpath) {
>>>       > + const char *headers = "Content-Type: text/html;
>>>      charset=utf-8\r\n";
>>>       > + char *data = mg_ssi(fullpath, root, 0);
>>>       > + mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
>>>       > + free(data);
>>>       > +}
>>>       > +#else
>>>       > +void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>>>       > + const char *fullpath) {
>>>       > + mg_http_reply(c, 501, NULL, "SSI not enabled");
>>>       > + (void) root, (void) fullpath;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -bool mg_json_get_bool(struct mg_str json, const char *path, bool
>>>      *v) {
>>>       > - int found = 0, off = mg_json_get(json, path, NULL);
>>>       > - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
>>>       > - if (v != NULL) *v = json.ptr[off] == 't';
>>>       > - found = 1;
>>>       > - }
>>>       > - return found;
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/str.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +struct mg_str mg_str_s(const char *s) {
>>>       > + struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
>>>       > + return str;
>>>       > }
>>>       >
>>>       > -static bool json_unescape(const char *s, size_t len, char *to,
>>>      size_t n) {
>>>       > - size_t i, j;
>>>       > - for (i = 0, j = 0; i < len && j < n; i++, j++) {
>>>       > - if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
>>>       > - // \uXXXX escape. We could process a simple one-byte chars
>>>       > - // \u00xx from the ASCII range. More complex chars would require
>>>       > - // dragging in a UTF8 library, which is too much for us
>>>       > - if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up
>>>       > - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i +
>>>      4, 2);
>>>       > +struct mg_str mg_str_n(const char *s, size_t n) {
>>>       > + struct mg_str str = {(char *) s, n};
>>>       > + return str;
>>>       > +}
>>>       >
>>>       > - i += 5;
>>>       > - } else if (s[i] == '\\' && i + 1 < len) {
>>>       > - char c = json_esc(s[i + 1], 0);
>>>       > - if (c == 0) return false;
>>>       > - to[j] = c;
>>>       > - i++;
>>>       > - } else {
>>>       > - to[j] = s[i];
>>>       > - }
>>>       > +static int mg_tolc(char c) {
>>>       > + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
>>>       > +}
>>>       > +
>>>       > +int mg_casecmp(const char *s1, const char *s2) {
>>>       > + int diff = 0;
>>>       > + do {
>>>       > + int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
>>>       > + diff = c - d;
>>>       > + } while (diff == 0 && s1[-1] != '\0');
>>>       > + return diff;
>>>       > +}
>>>       > +
>>>       > +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
>>>       > + size_t i = 0;
>>>       > + while (i < str1.len && i < str2.len) {
>>>       > + int c1 = str1.buf[i];
>>>       > + int c2 = str2.buf[i];
>>>       > + if (c1 < c2) return -1;
>>>       > + if (c1 > c2) return 1;
>>>       > + i++;
>>>       > }
>>>       > - if (j >= n) return false;
>>>       > - if (n > 0) to[j] = '\0';
>>>       > - return true;
>>>       > + if (i < str1.len) return 1;
>>>       > + if (i < str2.len) return -1;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -char *mg_json_get_str(struct mg_str json, const char *path) {
>>>       > - char *result = NULL;
>>>       > - int len = 0, off = mg_json_get(json, path, &len);
>>>       > - if (off >= 0 && len > 1 && json.ptr[off] == '"') {
>>>       > - if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
>>>       > - !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
>>>       > - (size_t) len)) {
>>>       > - free(result);
>>>       > - result = NULL;
>>>       > - }
>>>       > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
>>>      str2) {
>>>       > + size_t i = 0;
>>>       > + while (i < str1.len && i < str2.len) {
>>>       > + int c1 = mg_tolc(str1.buf[i]);
>>>       > + int c2 = mg_tolc(str2.buf[i]);
>>>       > + if (c1 < c2) return -1;
>>>       > + if (c1 > c2) return 1;
>>>       > + i++;
>>>       > }
>>>       > - return result;
>>>       > + if (i < str1.len) return 1;
>>>       > + if (i < str2.len) return -1;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -char *mg_json_get_b64(struct mg_str json, const char *path, int
>>>      *slen) {
>>>       > - char *result = NULL;
>>>       > - int len = 0, off = mg_json_get(json, path, &len);
>>>       > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
>>>       > - (result = (char *) calloc(1, (size_t) len)) != NULL) {
>>>       > - int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
>>>       > - if (slen != NULL) *slen = k;
>>>       > +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
>>>      *caps) {
>>>       > + size_t i = 0, j = 0, ni = 0, nj = 0;
>>>       > + if (caps) caps->buf = NULL, caps->len = 0;
>>>       > + while (i < p.len || j < s.len) {
>>>       > + if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] ==
>>>      p.buf[i])) {
>>>       > + if (caps == NULL) {
>>>       > + } else if (p.buf[i] == '?') {
>>>       > + caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap
>>>       > + caps++, caps->buf = NULL, caps->len = 0; // Init next cap
>>>       > + } else if (caps->buf != NULL && caps->len == 0) {
>>>       > + caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize
>>>      current cap
>>>       > + caps++, caps->len = 0, caps->buf = NULL; // Init next cap
>>>       > + }
>>>       > + i++, j++;
>>>       > + } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
>>>       > + if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j];
>>>      // Init cap
>>>       > + ni = i++, nj = j + 1;
>>>       > + } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' ||
>>>      s.buf[j] != '/')) {
>>>       > + i = ni, j = nj;
>>>       > + if (caps && caps->buf == NULL && caps->len == 0) {
>>>       > + caps--, caps->len = 0; // Restart previous cap
>>>       > + }
>>>       > + } else {
>>>       > + return false;
>>>       > + }
>>>       > }
>>>       > - return result;
>>>       > + if (caps && caps->buf && caps->len == 0) {
>>>       > + caps->len = (size_t) (&s.buf[j] - caps->buf);
>>>       > + }
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -char *mg_json_get_hex(struct mg_str json, const char *path, int
>>>      *slen) {
>>>       > - char *result = NULL;
>>>       > - int len = 0, off = mg_json_get(json, path, &len);
>>>       > - if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
>>>       > - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
>>>       > - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *)
>>>      result);
>>>       > - result[len / 2 - 1] = '\0';
>>>       > - if (slen != NULL) *slen = len / 2 - 1;
>>>       > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
>>>      *b, char sep) {
>>>       > + if (s.len == 0 || s.buf == NULL) {
>>>       > + return false; // Empty string, nothing to span - fail
>>>       > + } else {
>>>       > + size_t len = 0;
>>>       > + while (len < s.len && s.buf[len] != sep) len++; // Find separator
>>>       > + if (a) *a = mg_str_n(s.buf, len); // Init a
>>>       > + if (b) *b = mg_str_n(s.buf + len, s.len - len); // Init b
>>>       > + if (b && len < s.len) b->buf++, b->len--; // Skip separator
>>>       > + return true;
>>>       > }
>>>       > - return result;
>>>       > }
>>>       >
>>>       > -long mg_json_get_long(struct mg_str json, const char *path, long
>>>      dflt) {
>>>       > - double dv;
>>>       > - long result = dflt;
>>>       > - if (mg_json_get_num(json, path, &dv)) result = (long) dv;
>>>       > - return result;
>>>       > +bool mg_str_to_num(struct mg_str str, int base, void *val,
>>>      size_t val_len) {
>>>       > + size_t i = 0, ndigits = 0;
>>>       > + uint64_t max = val_len == sizeof(uint8_t) ? 0xFF
>>>       > + : val_len == sizeof(uint16_t) ? 0xFFFF
>>>       > + : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
>>>       > + : (uint64_t) ~0;
>>>       > + uint64_t result = 0;
>>>       > + if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return
>>>      false;
>>>       > + if (base == 0 && str.len >= 2) {
>>>       > + if (str.buf[i] == '0') {
>>>       > + i++;
>>>       > + base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
>>>       > + if (base != 10) ++i;
>>>       > + } else {
>>>       > + base = 10;
>>>       > + }
>>>       > + }
>>>       > + switch (base) {
>>>       > + case 2:
>>>       > + while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
>>>       > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
>>>       > + if (result > max/2) return false; // Overflow
>>>       > + result *= 2;
>>>       > + if (result > max - digit) return false; // Overflow
>>>       > + result += digit;
>>>       > + i++, ndigits++;
>>>       > + }
>>>       > + break;
>>>       > + case 10:
>>>       > + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
>>>       > + uint64_t digit = (uint64_t) (str.buf[i] - '0');
>>>       > + if (result > max/10) return false; // Overflow
>>>       > + result *= 10;
>>>       > + if (result > max - digit) return false; // Overflow
>>>       > + result += digit;
>>>       > + i++, ndigits++;
>>>       > + }
>>>       > + break;
>>>       > + case 16:
>>>       > + while (i < str.len) {
>>>       > + char c = str.buf[i];
>>>       > + uint64_t digit = (c >= '0' && c <= '9') ? (uint64_t) (c - '0')
>>>       > + : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
>>>       > + : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
>>>       > + : (uint64_t) ~0;
>>>       > + if (digit == (uint64_t) ~0) break;
>>>       > + if (result > max/16) return false; // Overflow
>>>       > + result *= 16;
>>>       > + if (result > max - digit) return false; // Overflow
>>>       > + result += digit;
>>>       > + i++, ndigits++;
>>>       > + }
>>>       > + break;
>>>       > + default:
>>>       > + return false;
>>>       > + }
>>>       > + if (ndigits == 0) return false;
>>>       > + if (i != str.len) return false;
>>>       > + if (val_len == 1) {
>>>       > + *((uint8_t *) val) = (uint8_t) result;
>>>       > + } else if (val_len == 2) {
>>>       > + *((uint16_t *) val) = (uint16_t) result;
>>>       > + } else if (val_len == 4) {
>>>       > + *((uint32_t *) val) = (uint32_t) result;
>>>       > + } else {
>>>       > + *((uint64_t *) val) = (uint64_t) result;
>>>       > + }
>>>       > + return true;
>>>       > }
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/log.c"
>>>       > +#line 1 "src/timer.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       > +#define MG_TIMER_CALLED 4
>>>       >
>>>       > -
>>>       > -static void default_logger(char c, void *param) {
>>>       > - putchar(c);
>>>       > - (void) c, (void) param;
>>>       > -}
>>>       > -
>>>       > -static int s_level = MG_LL_INFO;
>>>       > -static mg_pfn_t s_log_func = default_logger;
>>>       > -static void *s_log_func_param = NULL;
>>>       > -
>>>       > -void mg_log_set_fn(mg_pfn_t fn, void *param) {
>>>       > - s_log_func = fn;
>>>       > - s_log_func_param = param;
>>>       > -}
>>>       > -
>>>       > -static void logc(unsigned char c) {
>>>       > - s_log_func((char) c, s_log_func_param);
>>>       > -}
>>>       > -
>>>       > -static void logs(const char *buf, size_t len) {
>>>       > - size_t i;
>>>       > - for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
>>>       > -}
>>>       > -
>>>       > -void mg_log_set(int log_level) {
>>>       > - MG_DEBUG(("Setting log level to %d", log_level));
>>>       > - s_level = log_level;
>>>       > -}
>>>       > -
>>>       > -bool mg_log_prefix(int level, const char *file, int line, const
>>>      char *fname) {
>>>       > - if (level <= s_level) {
>>>       > - const char *p = strrchr(file, '/');
>>>       > - char buf[41];
>>>       > - size_t n;
>>>       > - if (p == NULL) p = strrchr(file, '\\');
>>>       > - n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s",
>>>      mg_millis(), level,
>>>       > - p == NULL ? file : p + 1, line, fname);
>>>       > - if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
>>>       > - while (n < sizeof(buf)) buf[n++] = ' ';
>>>       > - logs(buf, n - 1);
>>>       > - return true;
>>>       > - } else {
>>>       > - return false;
>>>       > - }
>>>       > +void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
>>>      uint64_t ms,
>>>       > + unsigned flags, void (*fn)(void *), void *arg) {
>>>       > + t->id = 0, t->period_ms = ms, t->expire = 0;
>>>       > + t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
>>>       > + *head = t;
>>>       > }
>>>       >
>>>       > -void mg_log(const char *fmt, ...) {
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
>>>       > - va_end(ap);
>>>       > - logc((unsigned char) '\n');
>>>       > +void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
>>>       > + while (*head && *head != t) head = &(*head)->next;
>>>       > + if (*head) *head = t->next;
>>>       > }
>>>       >
>>>       > -static unsigned char nibble(unsigned c) {
>>>       > - return (unsigned char) (c < 10 ? c + '0' : c + 'W');
>>>       > +// t: expiration time, prd: period, now: current time. Return
>>>      true if expired
>>>       > +bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
>>>       > + if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
>>>       > + if (*t == 0) *t = now + prd; // Firt poll? Set expiration
>>>       > + if (*t > now) return false; // Not expired yet, return
>>>       > + *t = (now - *t) > prd ? now + prd : *t + prd; // Next
>>>      expiration time
>>>       > + return true; // Expired, return true
>>>       > }
>>>       >
>>>       > -#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
>>>       > -void mg_hexdump(const void *buf, size_t len) {
>>>       > - const unsigned char *p = (const unsigned char *) buf;
>>>       > - unsigned char ascii[16], alen = 0;
>>>       > - size_t i;
>>>       > - for (i = 0; i < len; i++) {
>>>       > - if ((i % 16) == 0) {
>>>       > - // Print buffered ascii chars
>>>       > - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'),
>>>      alen = 0;
>>>       > - // Print hex address, then \t
>>>       > - logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
>>>       > - logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3);
>>>       > - }
>>>       > - logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two
>>>      nibbles, e.g. c5
>>>       > - logc(' '); // Space after hex number
>>>       > - ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii
>>>      buf
>>>       > +void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
>>>       > + struct mg_timer *t, *tmp;
>>>       > + for (t = *head; t != NULL; t = tmp) {
>>>       > + bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
>>>       > + !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
>>>       > + bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
>>>       > + tmp = t->next;
>>>       > + if (!once && !expired) continue;
>>>       > + if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
>>>      MG_TIMER_CALLED)) {
>>>       > + t->fn(t->arg);
>>>       > + }
>>>       > + t->flags |= MG_TIMER_CALLED;
>>>       > }
>>>       > - while (alen < 16) logs(" ", 3), ascii[alen++] = ' ';
>>>       > - logs(" ", 2), logs((char *) ascii, 16), logc('\n');
>>>       > }
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/md5.c"
>>>       > +#line 1 "src/tls_aes128.c"
>>>       > #endif
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>>>      THE GOOD OF ALL
>>>       > + *
>>>       > + * This is a simple and straightforward implementation of the
>>>      AES Rijndael
>>>       > + * 128-bit block cipher designed by Vincent Rijmen and Joan
>>>      Daemen. The focus
>>>       > + * of this work was correctness & accuracy. It is written in 'C'
>>>      without any
>>>       > + * particular focus upon optimization or speed. It should be
>>>      endian (memory
>>>       > + * byte order) neutral since the few places that care are
>>>      handled explicitly.
>>>       > + *
>>>       > + * This implementation of Rijndael was created by Steven M.
>>>      Gibson of GRC.com.
>>>       > + *
>>>       > + * It is intended for general purpose use, but was written in
>>>      support of GRC's
>>>       > + * reference implementation of the SQRL (Secure Quick Reliable
>>>      Login) client.
>>>       > + *
>>>       > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
>>>      <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
>>>       > + *
>>>       > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>>>      WARRANTY MADE
>>>       > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>>>      YOUR OWN RISK.
>>>       > + *
>>>       > +
>>>      *******************************************************************************/
>>>       > +
>>>       >
>>>      +/******************************************************************************/
>>>       > +#define AES_DECRYPTION 1 // whether AES decryption is supported
>>>       >
>>>      +/******************************************************************************/
>>>       > +
>>>       > +#define MG_ENCRYPT 1 // specify whether we're encrypting
>>>       > +#define MG_DECRYPT 0 // or decrypting
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_TLS == MG_TLS_BUILTIN
>>>       >
>>>      +/******************************************************************************
>>>       > + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
>>>       > +
>>>      ******************************************************************************/
>>>       > +static void aes_init_keygen_tables(void);
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * AES_SETKEY : called to expand the key for encryption or
>>>      decryption
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_setkey(aes_context *ctx, // pointer to context
>>>       > + int mode, // 1 or 0 for Encrypt/Decrypt
>>>       > + const uchar *key, // AES input key
>>>       > + uint keysize); // size in bytes (must be 16, 24, 32 for
>>>       > + // 128, 192 or 256-bit keys respectively)
>>>       > + // returns 0 for success
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block
>>>      of data
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_cipher(aes_context *ctx, // pointer to context
>>>       > + const uchar input[16], // 128-bit block to en/decipher
>>>       > + uchar output[16]); // 128-bit output result block
>>>       > + // returns 0 for success
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * GCM_CONTEXT : GCM context / holds keytables, instance data,
>>>      and AES ctx
>>>       > +
>>>      ******************************************************************************/
>>>       > +typedef struct {
>>>       > + int mode; // cipher direction: encrypt/decrypt
>>>       > + uint64_t len; // cipher data length processed so far
>>>       > + uint64_t add_len; // total add data length
>>>       > + uint64_t HL[16]; // precalculated lo-half HTable
>>>       > + uint64_t HH[16]; // precalculated hi-half HTable
>>>       > + uchar base_ectr[16]; // first counter-mode cipher output for tag
>>>       > + uchar y[16]; // the current cipher-input IV|Counter value
>>>       > + uchar buf[16]; // buf working value
>>>       > + aes_context aes_ctx; // cipher context used
>>>       > +} gcm_context;
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * GCM_SETKEY : sets the GCM (and AES) keying material for use
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_setkey(
>>>       > + gcm_context *ctx, // caller-provided context ptr
>>>       > + const uchar *key, // pointer to cipher key
>>>       > + const uint keysize // size in bytes (must be 16, 24, 32 for
>>>       > + // 128, 192 or 256-bit keys respectively)
>>>       > +); // returns 0 for success
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_CRYPT_AND_TAG
>>>       > + *
>>>       > + * This either encrypts or decrypts the user-provided data and,
>>>      either
>>>       > + * way, generates an authentication tag of the requested length.
>>>      It must be
>>>       > + * called with a GCM context whose key has already been set with
>>>      GCM_SETKEY.
>>>       > + *
>>>       > + * The user would typically call this explicitly to ENCRYPT a
>>>      buffer of data
>>>       > + * and optional associated data, and produce its an
>>>      authentication tag.
>>>       > + *
>>>       > + * To reverse the process the user would typically call the
>>>      companion
>>>       > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
>>>      user-provided
>>>       > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
>>>      function
>>>       > + * to perform its decryption and tag generation, which it then
>>>      compares.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_crypt_and_tag(
>>>       > + gcm_context *ctx, // gcm context with key already setup
>>>       > + int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
>>>       > + const uchar *iv, // pointer to the 12-byte initialization vector
>>>       > + size_t iv_len, // byte length if the IV. should always be 12
>>>       > + const uchar *add, // pointer to the non-ciphered additional data
>>>       > + size_t add_len, // byte length of the additional AEAD data
>>>       > + const uchar *input, // pointer to the cipher data source
>>>       > + uchar *output, // pointer to the cipher data destination
>>>       > + size_t length, // byte length of the cipher data
>>>       > + uchar *tag, // pointer to the tag to be generated
>>>       > + size_t tag_len); // byte length of the tag to be generated
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_START
>>>       > + *
>>>       > + * Given a user-provided GCM context, this initializes it, sets
>>>      the encryption
>>>       > + * mode, and preprocesses the initialization vector and
>>>      additional AEAD data.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_start(
>>>       > + gcm_context *ctx, // pointer to user-provided GCM context
>>>       > + int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0)
>>>       > + const uchar *iv, // pointer to initialization vector
>>>       > + size_t iv_len, // IV length in bytes (should == 12)
>>>       > + const uchar *add, // pointer to additional AEAD data (NULL if
>>>      none)
>>>       > + size_t add_len); // length of additional AEAD data (bytes)
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_UPDATE
>>>       > + *
>>>       > + * This is called once or more to process bulk plaintext or
>>>      ciphertext data.
>>>       > + * We give this some number of bytes of input and it returns the
>>>      same number
>>>       > + * of output bytes. If called multiple times (which is fine) all
>>>      but the final
>>>       > + * invocation MUST be called with length mod 16 == 0. (Only the
>>>      final call can
>>>       > + * have a partial block length of < 128 bits.)
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_update(gcm_context *ctx, // pointer to
>>>      user-provided GCM context
>>>       > + size_t length, // length, in bytes, of data to process
>>>       > + const uchar *input, // pointer to source data
>>>       > + uchar *output); // pointer to destination data
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_FINISH
>>>       > + *
>>>       > + * This is called once after all calls to GCM_UPDATE to finalize
>>>      the GCM.
>>>       > + * It performs the final GHASH to produce the resulting
>>>      authentication TAG.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_finish(
>>>       > + gcm_context *ctx, // pointer to user-provided GCM context
>>>       > + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0
>>>       > + size_t tag_len); // length, in bytes, of the tag-receiving buf
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_ZERO_CTX
>>>       > + *
>>>       > + * The GCM context contains both the GCM context and the AES
>>>      context.
>>>       > + * This includes keying and key-related material which is security-
>>>       > + * sensitive, so it MUST be zeroed after use. This function does
>>>      that.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static void gcm_zero_ctx(gcm_context *ctx);
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>>>      THE GOOD OF ALL
>>>       > + *
>>>       > + * This is a simple and straightforward implementation of the
>>>      AES Rijndael
>>>       > + * 128-bit block cipher designed by Vincent Rijmen and Joan
>>>      Daemen. The focus
>>>       > + * of this work was correctness & accuracy. It is written in 'C'
>>>      without any
>>>       > + * particular focus upon optimization or speed. It should be
>>>      endian (memory
>>>       > + * byte order) neutral since the few places that care are
>>>      handled explicitly.
>>>       > + *
>>>       > + * This implementation of Rijndael was created by Steven M.
>>>      Gibson of GRC.com.
>>>       > + *
>>>       > + * It is intended for general purpose use, but was written in
>>>      support of GRC's
>>>       > + * reference implementation of the SQRL (Secure Quick Reliable
>>>      Login) client.
>>>       > + *
>>>       > + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
>>>      <http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html>
>>>       > + *
>>>       > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>>>      WARRANTY MADE
>>>       > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>>>      YOUR OWN RISK.
>>>       > + *
>>>       > +
>>>      *******************************************************************************/
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +static int aes_tables_inited = 0; // run-once flag for
>>>      performing key
>>>       > + // expasion table generation (see below)
>>>       > +/*
>>>       > + * The following static local tables must be filled-in before
>>>      the first use of
>>>       > + * the GCM or AES ciphers. They are used for the AES key
>>>      expansion/scheduling
>>>       > + * and once built are read-only and thread safe. The
>>>      "gcm_initialize" function
>>>       > + * must be called once during system initialization to populate
>>>      these arrays
>>>       > + * for subsequent use by the AES key scheduler. If they have not
>>>      been built
>>>       > + * before attempted use, an error will be returned to the caller.
>>>       > + *
>>>       > + * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES
>>>      decryption. Since
>>>       > + * GCM uses AES in counter-mode, where the AES cipher output is
>>>      XORed with
>>>       > + * the GCM input, we ONLY NEED AES encryption. Thus, to save
>>>      space AES
>>>       > + * decryption is typically disabled by setting AES_DECRYPTION to
>>>      0 in aes.h.
>>>       > + */
>>>       > +// We always need our forward tables
>>>       > +static uchar FSb[256]; // Forward substitution box (FSb)
>>>       > +static uint32_t FT0[256]; // Forward key schedule assembly tables
>>>       > +static uint32_t FT1[256];
>>>       > +static uint32_t FT2[256];
>>>       > +static uint32_t FT3[256];
>>>       > +
>>>       > +#if AES_DECRYPTION // We ONLY need reverse for decryption
>>>       > +static uchar RSb[256]; // Reverse substitution box (RSb)
>>>       > +static uint32_t RT0[256]; // Reverse key schedule assembly tables
>>>       > +static uint32_t RT1[256];
>>>       > +static uint32_t RT2[256];
>>>       > +static uint32_t RT3[256];
>>>       > +#endif /* AES_DECRYPTION */
>>>       > +
>>>       > +static uint32_t RCON[10]; // AES round constants
>>>       >
>>>       > +/*
>>>       > + * Platform Endianness Neutralizing Load and Store Macro
>>>      definitions
>>>       > + * AES wants platform-neutral Little Endian (LE) byte ordering
>>>       > + */
>>>       > +#define GET_UINT32_LE(n, b, i) \
>>>       > + { \
>>>       > + (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) | \
>>>       > + ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] <<
>>>      24); \
>>>       > + }
>>>       >
>>>       > -
>>>       > -#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
>>>       > -
>>>       > -static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
>>>       > - if (MG_BIG_ENDIAN) {
>>>       > - do {
>>>       > - uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
>>>       > - ((unsigned) buf[1] << 8 | buf[0]);
>>>       > - *(uint32_t *) buf = t;
>>>       > - buf += 4;
>>>       > - } while (--longs);
>>>       > - } else {
>>>       > - (void) buf, (void) longs; // Little endian. Do nothing
>>>       > +#define PUT_UINT32_LE(n, b, i) \
>>>       > + { \
>>>       > + (b)[(i)] = (uchar) ((n)); \
>>>       > + (b)[(i) + 1] = (uchar) ((n) >> 8); \
>>>       > + (b)[(i) + 2] = (uchar) ((n) >> 16); \
>>>       > + (b)[(i) + 3] = (uchar) ((n) >> 24); \
>>>       > }
>>>       > -}
>>>       >
>>>       > -#define F1(x, y, z) (z ^ (x & (y ^ z)))
>>>       > -#define F2(x, y, z) F1(z, x, y)
>>>       > -#define F3(x, y, z) (x ^ y ^ z)
>>>       > -#define F4(x, y, z) (y ^ (x | ~z))
>>>       > +/*
>>>       > + * AES forward and reverse encryption round processing macros
>>>       > + */
>>>       > +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
>>>       > + { \
>>>       > + X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
>>>       > + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
>>>       > + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
>>>       > + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
>>>       > + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \
>>>       > + }
>>>       >
>>>       > -#define MD5STEP(f, w, x, y, z, data, s) \
>>>       > - (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
>>>       > +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
>>>       > + { \
>>>       > + X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
>>>       > + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
>>>       > + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
>>>       > + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \
>>>       > + \
>>>       > + X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
>>>       > + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \
>>>       > + }
>>>       >
>>>       > /*
>>>       > - * Start MD5 accumulation. Set bit count to 0 and buffer to
>>>      mysterious
>>>       > - * initialization constants.
>>>       > + * These macros improve the readability of the key
>>>       > + * generation initialization code by collapsing
>>>       > + * repetitive common operations into logical pieces.
>>>       > */
>>>       > -void mg_md5_init(mg_md5_ctx *ctx) {
>>>       > - ctx->buf[0] = 0x67452301;
>>>       > - ctx->buf[1] = 0xefcdab89;
>>>       > - ctx->buf[2] = 0x98badcfe;
>>>       > - ctx->buf[3] = 0x10325476;
>>>       > +#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
>>>       > +#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
>>>       > +#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
>>>       > +#define MIX(x, y) \
>>>       > + { \
>>>       > + y = ((y << 1) | (y >> 7)) & 0xFF; \
>>>       > + x ^= y; \
>>>       > + }
>>>       > +#define CPY128 \
>>>       > + { \
>>>       > + *RK++ = *SK++; \
>>>       > + *RK++ = *SK++; \
>>>       > + *RK++ = *SK++; \
>>>       > + *RK++ = *SK++; \
>>>       > + }
>>>       >
>>>       > - ctx->bits[0] = 0;
>>>       > - ctx->bits[1] = 0;
>>>       > -}
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * AES_INIT_KEYGEN_TABLES
>>>       > + *
>>>       > + * Fills the AES key expansion tables allocated above with their
>>>      static
>>>       > + * data. This is not "per key" data, but static system-wide
>>>      read-only
>>>       > + * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be
>>>      called once
>>>       > + * at system initialization to setup the tables for all
>>>      subsequent use.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +void aes_init_keygen_tables(void) {
>>>       > + int i, x, y, z; // general purpose iteration and computation
>>>      locals
>>>       > + int pow[256];
>>>       > + int log[256];
>>>       > +
>>>       > + if (aes_tables_inited) return;
>>>       > +
>>>       > + // fill the 'pow' and 'log' tables over GF(2^8)
>>>       > + for (i = 0, x = 1; i < 256; i++) {
>>>       > + pow[i] = x;
>>>       > + log[x] = i;
>>>       > + x = (x ^ XTIME(x)) & 0xFF;
>>>       > + }
>>>       > + // compute the round constants
>>>       > + for (i = 0, x = 1; i < 10; i++) {
>>>       > + RCON[i] = (uint32_t) x;
>>>       > + x = XTIME(x) & 0xFF;
>>>       > + }
>>>       > + // fill the forward and reverse substitution boxes
>>>       > + FSb[0x00] = 0x63;
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > + RSb[0x63] = 0x00;
>>>       > +#endif /* AES_DECRYPTION */
>>>       > +
>>>       > + for (i = 1; i < 256; i++) {
>>>       > + x = y = pow[255 - log[i]];
>>>       > + MIX(x, y);
>>>       > + MIX(x, y);
>>>       > + MIX(x, y);
>>>       > + MIX(x, y);
>>>       > + FSb[i] = (uchar) (x ^= 0x63);
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > + RSb[x] = (uchar) i;
>>>       > +#endif /* AES_DECRYPTION */
>>>       > + }
>>>       > + // generate the forward and reverse key expansion tables
>>>       > + for (i = 0; i < 256; i++) {
>>>       > + x = FSb[i];
>>>       > + y = XTIME(x) & 0xFF;
>>>       > + z = (y ^ x) & 0xFF;
>>>       >
>>>       > -static void mg_md5_transform(uint32_t buf[4], uint32_t const
>>>      in[16]) {
>>>       > - uint32_t a, b, c, d;
>>>       > + FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x
>>>      << 16) ^
>>>       > + ((uint32_t) z << 24);
>>>       >
>>>       > - a = buf[0];
>>>       > - b = buf[1];
>>>       > - c = buf[2];
>>>       > - d = buf[3];
>>>       > + FT1[i] = ROTL8(FT0[i]);
>>>       > + FT2[i] = ROTL8(FT1[i]);
>>>       > + FT3[i] = ROTL8(FT2[i]);
>>>       >
>>>       > - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
>>>       > - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
>>>       > - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
>>>       > - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
>>>       > - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
>>>       > - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
>>>       > - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
>>>       > - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
>>>       > - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
>>>       > - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
>>>       > - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
>>>       > - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
>>>       > - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
>>>       > - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
>>>       > - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
>>>       > - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > + x = RSb[i];
>>>       >
>>>       > - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
>>>       > - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
>>>       > - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
>>>       > - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
>>>       > - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
>>>       > - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
>>>       > - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
>>>       > - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
>>>       > - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
>>>       > - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
>>>       > - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
>>>       > - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
>>>       > - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
>>>       > - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
>>>       > - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
>>>       > - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
>>>       > + RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x)
>>>      << 8) ^
>>>       > + ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
>>>       >
>>>       > - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
>>>       > - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
>>>       > - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
>>>       > - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
>>>       > - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
>>>       > - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
>>>       > - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
>>>       > - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
>>>       > - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
>>>       > - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
>>>       > - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
>>>       > - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
>>>       > - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
>>>       > - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
>>>       > - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
>>>       > - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
>>>       > + RT1[i] = ROTL8(RT0[i]);
>>>       > + RT2[i] = ROTL8(RT1[i]);
>>>       > + RT3[i] = ROTL8(RT2[i]);
>>>       > +#endif /* AES_DECRYPTION */
>>>       > + }
>>>       > + aes_tables_inited = 1; // flag that the tables have been generated
>>>       > +} // to permit subsequent use of the AES cipher
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * AES_SET_ENCRYPTION_KEY
>>>       > + *
>>>       > + * This is called by 'aes_setkey' when we're establishing a key for
>>>       > + * subsequent encryption. We give it a pointer to the encryption
>>>       > + * context, a pointer to the key, and the key's length in bytes.
>>>       > + * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_set_encryption_key(aes_context *ctx, const uchar
>>>      *key,
>>>       > + uint keysize) {
>>>       > + uint i; // general purpose iteration local
>>>       > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
>>>       > +
>>>       > + for (i = 0; i < (keysize >> 2); i++) {
>>>       > + GET_UINT32_LE(RK[i], key, i << 2);
>>>       > + }
>>>       >
>>>       > - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
>>>       > - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
>>>       > - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
>>>       > - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
>>>       > - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
>>>       > - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
>>>       > - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
>>>       > - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
>>>       > - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
>>>       > - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
>>>       > - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
>>>       > - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
>>>       > - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
>>>       > - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
>>>       > - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
>>>       > - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
>>>       > + switch (ctx->rounds) {
>>>       > + case 10:
>>>       > + for (i = 0; i < 10; i++, RK += 4) {
>>>       > + RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
>>>       > +
>>>       > + RK[5] = RK[1] ^ RK[4];
>>>       > + RK[6] = RK[2] ^ RK[5];
>>>       > + RK[7] = RK[3] ^ RK[6];
>>>       > + }
>>>       > + break;
>>>       >
>>>       > - buf[0] += a;
>>>       > - buf[1] += b;
>>>       > - buf[2] += c;
>>>       > - buf[3] += d;
>>>       > -}
>>>       > + case 12:
>>>       > + for (i = 0; i < 8; i++, RK += 6) {
>>>       > + RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
>>>       > +
>>>       > + RK[7] = RK[1] ^ RK[6];
>>>       > + RK[8] = RK[2] ^ RK[7];
>>>       > + RK[9] = RK[3] ^ RK[8];
>>>       > + RK[10] = RK[4] ^ RK[9];
>>>       > + RK[11] = RK[5] ^ RK[10];
>>>       > + }
>>>       > + break;
>>>       >
>>>       > -void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf,
>>>      size_t len) {
>>>       > - uint32_t t;
>>>       > + case 14:
>>>       > + for (i = 0; i < 7; i++, RK += 8) {
>>>       > + RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
>>>       > +
>>>       > + RK[9] = RK[1] ^ RK[8];
>>>       > + RK[10] = RK[2] ^ RK[9];
>>>       > + RK[11] = RK[3] ^ RK[10];
>>>       > +
>>>       > + RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + RK[13] = RK[5] ^ RK[12];
>>>       > + RK[14] = RK[6] ^ RK[13];
>>>       > + RK[15] = RK[7] ^ RK[14];
>>>       > + }
>>>       > + break;
>>>       >
>>>       > - t = ctx->bits[0];
>>>       > - if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
>>>      ctx->bits[1]++;
>>>       > - ctx->bits[1] += (uint32_t) len >> 29;
>>>       > + default:
>>>       > + return -1;
>>>       > + }
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * AES_SET_DECRYPTION_KEY
>>>       > + *
>>>       > + * This is called by 'aes_setkey' when we're establishing a
>>>       > + * key for subsequent decryption. We give it a pointer to
>>>       > + * the encryption context, a pointer to the key, and the key's
>>>       > + * length in bits. Valid lengths are: 128, 192, or 256 bits.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_set_decryption_key(aes_context *ctx, const uchar
>>>      *key,
>>>       > + uint keysize) {
>>>       > + int i, j;
>>>       > + aes_context cty; // a calling aes context for set_encryption_key
>>>       > + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer
>>>       > + uint32_t *SK;
>>>       > + int ret;
>>>       >
>>>       > - t = (t >> 3) & 0x3f;
>>>       > + cty.rounds = ctx->rounds; // initialize our local aes context
>>>       > + cty.rk = cty.buf; // round count and key buf pointer
>>>       >
>>>       > - if (t) {
>>>       > - unsigned char *p = (unsigned char *) ctx->in + t;
>>>       > + if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0)
>>>      return (ret);
>>>       >
>>>       > - t = 64 - t;
>>>       > - if (len < t) {
>>>       > - memcpy(p, buf, len);
>>>       > - return;
>>>       > + SK = cty.rk + cty.rounds * 4;
>>>       > +
>>>       > + CPY128 // copy a 128-bit block from *SK to *RK
>>>       > +
>>>       > + for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
>>>       > + for (j = 0; j < 4; j++, SK++) {
>>>       > + *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
>>>       > + RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
>>>       > }
>>>       > - memcpy(p, buf, t);
>>>       > - mg_byte_reverse(ctx->in, 16);
>>>       > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > - buf += t;
>>>       > - len -= t;
>>>       > + }
>>>       > + CPY128 // copy a 128-bit block from *SK to *RK
>>>       > + memset(&cty, 0, sizeof(aes_context)); // clear local aes context
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       > +#endif /* AES_DECRYPTION */
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * AES_SETKEY
>>>       > + *
>>>       > + * Invoked to establish the key schedule for subsequent
>>>      encryption/decryption
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_setkey(aes_context *ctx, // AES context provided
>>>      by our caller
>>>       > + int mode, // ENCRYPT or DECRYPT flag
>>>       > + const uchar *key, // pointer to the key
>>>       > + uint keysize) // key length in bytes
>>>       > +{
>>>       > + // since table initialization is not thread safe, we could
>>>      either add
>>>       > + // system-specific mutexes and init the AES key generation
>>>      tables on
>>>       > + // demand, or ask the developer to simply call "gcm_initialize"
>>>      once during
>>>       > + // application startup before threading begins. That's what we
>>>      choose.
>>>       > + if (!aes_tables_inited) return (-1); // fail the call when not
>>>      inited.
>>>       > +
>>>       > + ctx->mode = mode; // capture the key type we're creating
>>>       > + ctx->rk = ctx->buf; // initialize our round key pointer
>>>       > +
>>>       > + switch (keysize) // set the rounds count based upon the keysize
>>>       > + {
>>>       > + case 16:
>>>       > + ctx->rounds = 10;
>>>       > + break; // 16-byte, 128-bit key
>>>       > + case 24:
>>>       > + ctx->rounds = 12;
>>>       > + break; // 24-byte, 192-bit key
>>>       > + case 32:
>>>       > + ctx->rounds = 14;
>>>       > + break; // 32-byte, 256-bit key
>>>       > + default:
>>>       > + return (-1);
>>>       > }
>>>       >
>>>       > - while (len >= 64) {
>>>       > - memcpy(ctx->in, buf, 64);
>>>       > - mg_byte_reverse(ctx->in, 16);
>>>       > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > - buf += 64;
>>>       > - len -= 64;
>>>       > +#if AES_DECRYPTION
>>>       > + if (mode == MG_DECRYPT) // expand our key for encryption or
>>>      decryption
>>>       > + return (aes_set_decryption_key(ctx, key, keysize));
>>>       > + else /* MG_ENCRYPT */
>>>       > +#endif /* AES_DECRYPTION */
>>>       > + return (aes_set_encryption_key(ctx, key, keysize));
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * AES_CIPHER
>>>       > + *
>>>       > + * Perform AES encryption and decryption.
>>>       > + * The AES context will have been setup with the encryption mode
>>>       > + * and all keying information appropriate for the task.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int aes_cipher(aes_context *ctx, const uchar input[16],
>>>       > + uchar output[16]) {
>>>       > + int i;
>>>       > + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general
>>>      purpose locals
>>>       > +
>>>       > + RK = ctx->rk;
>>>       > +
>>>       > + GET_UINT32_LE(X0, input, 0);
>>>       > + X0 ^= *RK++; // load our 128-bit
>>>       > + GET_UINT32_LE(X1, input, 4);
>>>       > + X1 ^= *RK++; // input buffer in a storage
>>>       > + GET_UINT32_LE(X2, input, 8);
>>>       > + X2 ^= *RK++; // memory endian-neutral way
>>>       > + GET_UINT32_LE(X3, input, 12);
>>>       > + X3 ^= *RK++;
>>>       > +
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > +
>>>       > + if (ctx->mode == MG_DECRYPT) {
>>>       > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
>>>       > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>>>       > + AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
>>>       > + }
>>>       > +
>>>       > + AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>>>       > +
>>>       > + X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
>>>       > + ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
>>>       > + ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
>>>       > + ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
>>>       > + ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
>>>       > + } else /* MG_ENCRYPT */
>>>       > + {
>>>       > +#endif /* AES_DECRYPTION */
>>>       > +
>>>       > + for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
>>>       > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>>>       > + AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
>>>       > + }
>>>       > +
>>>       > + AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
>>>       > +
>>>       > + X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > + X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
>>>       > + ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
>>>       > + ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
>>>       > + ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
>>>       > +
>>>       > +#if AES_DECRYPTION // whether AES decryption is supported
>>>       > }
>>>       > +#endif /* AES_DECRYPTION */
>>>       > +
>>>       > + PUT_UINT32_LE(X0, output, 0);
>>>       > + PUT_UINT32_LE(X1, output, 4);
>>>       > + PUT_UINT32_LE(X2, output, 8);
>>>       > + PUT_UINT32_LE(X3, output, 12);
>>>       > +
>>>       > + return (0);
>>>       > +}
>>>       > +/* end of aes.c */
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>>>      THE GOOD OF ALL
>>>       > + *
>>>       > + * This is a simple and straightforward implementation of
>>>      AES-GCM authenticated
>>>       > + * encryption. The focus of this work was correctness &
>>>      accuracy. It is written
>>>       > + * in straight 'C' without any particular focus upon
>>>      optimization or speed. It
>>>       > + * should be endian (memory byte order) neutral since the few
>>>      places that care
>>>       > + * are handled explicitly.
>>>       > + *
>>>       > + * This implementation of AES-GCM was created by Steven M.
>>>      Gibson of GRC.com.
>>>       > + *
>>>       > + * It is intended for general purpose use, but was written in
>>>      support of GRC's
>>>       > + * reference implementation of the SQRL (Secure Quick Reliable
>>>      Login) client.
>>>       > + *
>>>       > + * See:
>>>      http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
>>>      <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
>>>       > + *
>>>      http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
>>>      <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/>
>>>       > + * gcm/gcm-revised-spec.pdf
>>>       > + *
>>>       > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>>>      WARRANTY MADE
>>>       > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>>>      YOUR OWN RISK.
>>>       > + *
>>>       > +
>>>      *******************************************************************************/
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * ==== IMPLEMENTATION WARNING ====
>>>       > + *
>>>       > + * This code was developed for use within SQRL's fixed
>>>      environmnent. Thus, it
>>>       > + * is somewhat less "general purpose" than it would be if it
>>>      were designed as
>>>       > + * a general purpose AES-GCM library. Specifically, it bothers
>>>      with almost NO
>>>       > + * error checking on parameter limits, buffer bounds, etc. It
>>>      assumes that it
>>>       > + * is being invoked by its author or by someone who understands
>>>      the values it
>>>       > + * expects to receive. Its behavior will be undefined otherwise.
>>>       > + *
>>>       > + * All functions that might fail are defined to return 'ints' to
>>>      indicate a
>>>       > + * problem. Most do not do so now. But this allows for error
>>>      propagation out
>>>       > + * of internal functions if robust error checking should ever be
>>>      desired.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +
>>>       > +/* Calculating the "GHASH"
>>>       > + *
>>>       > + * There are many ways of calculating the so-called GHASH in
>>>      software, each with
>>>       > + * a traditional size vs performance tradeoff. The GHASH (Galois
>>>      field hash) is
>>>       > + * an intriguing construction which takes two 128-bit strings
>>>      (also the cipher's
>>>       > + * block size and the fundamental operation size for the system)
>>>      and hashes them
>>>       > + * into a third 128-bit result.
>>>       > + *
>>>       > + * Many implementation solutions have been worked out that use
>>>      large precomputed
>>>       > + * table lookups in place of more time consuming bit fiddling,
>>>      and this approach
>>>       > + * can be scaled easily upward or downward as needed to change
>>>      the time/space
>>>       > + * tradeoff. It's been studied extensively and there's a solid
>>>      body of theory
>>>       > + * and practice. For example, without using any lookup tables an
>>>      implementation
>>>       > + * might obtain 119 cycles per byte throughput, whereas using a
>>>      simple, though
>>>       > + * large, key-specific 64 kbyte 8-bit lookup table the
>>>      performance jumps to 13
>>>       > + * cycles per byte.
>>>       > + *
>>>       > + * And Intel's processors have, since 2010, included an
>>>      instruction which does
>>>       > + * the entire 128x128->128 bit job in just several 64x64->128
>>>      bit pieces.
>>>       > + *
>>>       > + * Since SQRL is interactive, and only processing a few 128-bit
>>>      blocks, I've
>>>       > + * settled upon a relatively slower but appealing small-table
>>>      compromise which
>>>       > + * folds a bunch of not only time consuming but also bit
>>>      twiddling into a simple
>>>       > + * 16-entry table which is attributed to Victor Shoup's 1996
>>>      work while at
>>>       > + * Bellcore: "On Fast and Provably Secure MessageAuthentication
>>>      Based on
>>>       > + * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf
>>>      <http://www.shoup.net/papers/macs.pdf>
>>>       > + * See, also section 4.1 of the "gcm-revised-spec" cited above.
>>>       > + */
>>>       >
>>>       > - memcpy(ctx->in, buf, len);
>>>       > -}
>>>       > +/*
>>>       > + * This 16-entry table of pre-computed constants is used by the
>>>       > + * GHASH multiplier to improve over a strictly table-free but
>>>       > + * significantly slower 128x128 bit multiple within GF(2^128).
>>>       > + */
>>>       > +static const uint64_t last4[16] = {
>>>       > + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
>>>       > + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
>>>       >
>>>       > -void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
>>>       > - unsigned count;
>>>       > - unsigned char *p;
>>>       > - uint32_t *a;
>>>       > +/*
>>>       > + * Platform Endianness Neutralizing Load and Store Macro
>>>      definitions
>>>       > + * GCM wants platform-neutral Big Endian (BE) byte ordering
>>>       > + */
>>>       > +#define GET_UINT32_BE(n, b, i) \
>>>       > + { \
>>>       > + (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] <<
>>>      16) | \
>>>       > + ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]); \
>>>       > + }
>>>       >
>>>       > - count = (ctx->bits[0] >> 3) & 0x3F;
>>>       > +#define PUT_UINT32_BE(n, b, i) \
>>>       > + { \
>>>       > + (b)[(i)] = (uchar) ((n) >> 24); \
>>>       > + (b)[(i) + 1] = (uchar) ((n) >> 16); \
>>>       > + (b)[(i) + 2] = (uchar) ((n) >> 8); \
>>>       > + (b)[(i) + 3] = (uchar) ((n)); \
>>>       > + }
>>>       >
>>>       > - p = ctx->in + count;
>>>       > - *p++ = 0x80;
>>>       > - count = 64 - 1 - count;
>>>       > - if (count < 8) {
>>>       > - memset(p, 0, count);
>>>       > - mg_byte_reverse(ctx->in, 16);
>>>       > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > - memset(ctx->in, 0, 56);
>>>       > - } else {
>>>       > - memset(p, 0, count - 8);
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_INITIALIZE
>>>       > + *
>>>       > + * Must be called once to initialize the GCM library.
>>>       > + *
>>>       > + * At present, this only calls the AES keygen table generator,
>>>      which expands
>>>       > + * the AES keying tables for use. This is NOT A THREAD-SAFE
>>>      function, so it
>>>       > + * MUST be called during system initialization before a
>>>      multi-threading
>>>       > + * environment is running.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +int mg_gcm_initialize(void) {
>>>       > + aes_init_keygen_tables();
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_MULT
>>>       > + *
>>>       > + * Performs a GHASH operation on the 128-bit input vector 'x',
>>>      setting
>>>       > + * the 128-bit output vector to 'x' times H using our
>>>      precomputed tables.
>>>       > + * 'x' and 'output' are seen as elements of GCM's GF(2^128)
>>>      Galois field.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static void gcm_mult(gcm_context *ctx, // pointer to established
>>>      context
>>>       > + const uchar x[16], // pointer to 128-bit input vector
>>>       > + uchar output[16]) // pointer to 128-bit output vector
>>>       > +{
>>>       > + int i;
>>>       > + uchar lo, hi, rem;
>>>       > + uint64_t zh, zl;
>>>       > +
>>>       > + lo = (uchar) (x[15] & 0x0f);
>>>       > + hi = (uchar) (x[15] >> 4);
>>>       > + zh = ctx->HH[lo];
>>>       > + zl = ctx->HL[lo];
>>>       > +
>>>       > + for (i = 15; i >= 0; i--) {
>>>       > + lo = (uchar) (x[i] & 0x0f);
>>>       > + hi = (uchar) (x[i] >> 4);
>>>       > +
>>>       > + if (i != 15) {
>>>       > + rem = (uchar) (zl & 0x0f);
>>>       > + zl = (zh << 60) | (zl >> 4);
>>>       > + zh = (zh >> 4);
>>>       > + zh ^= (uint64_t) last4[rem] << 48;
>>>       > + zh ^= ctx->HH[lo];
>>>       > + zl ^= ctx->HL[lo];
>>>       > + }
>>>       > + rem = (uchar) (zl & 0x0f);
>>>       > + zl = (zh << 60) | (zl >> 4);
>>>       > + zh = (zh >> 4);
>>>       > + zh ^= (uint64_t) last4[rem] << 48;
>>>       > + zh ^= ctx->HH[hi];
>>>       > + zl ^= ctx->HL[hi];
>>>       > }
>>>       > - mg_byte_reverse(ctx->in, 14);
>>>       > + PUT_UINT32_BE(zh >> 32, output, 0);
>>>       > + PUT_UINT32_BE(zh, output, 4);
>>>       > + PUT_UINT32_BE(zl >> 32, output, 8);
>>>       > + PUT_UINT32_BE(zl, output, 12);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_SETKEY
>>>       > + *
>>>       > + * This is called to set the AES-GCM key. It initializes the AES
>>>      key
>>>       > + * and populates the gcm context's pre-calculated HTables.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +static int gcm_setkey(
>>>       > + gcm_context *ctx, // pointer to caller-provided gcm context
>>>       > + const uchar *key, // pointer to the AES encryption key
>>>       > + const uint keysize) // size in bytes (must be 16, 24, 32 for
>>>       > + // 128, 192 or 256-bit keys respectively)
>>>       > +{
>>>       > + int ret, i, j;
>>>       > + uint64_t hi, lo;
>>>       > + uint64_t vl, vh;
>>>       > + unsigned char h[16];
>>>       > +
>>>       > + memset(ctx, 0, sizeof(gcm_context)); // zero caller-provided
>>>      GCM context
>>>       > + memset(h, 0, 16); // initialize the block to encrypt
>>>       > +
>>>       > + // encrypt the null 128-bit block to generate a key-based value
>>>       > + // which is then used to initialize our GHASH lookup tables
>>>       > + if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize))
>>>      != 0)
>>>       > + return (ret);
>>>       > + if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
>>>       > +
>>>       > + GET_UINT32_BE(hi, h, 0); // pack h as two 64-bit ints, big-endian
>>>       > + GET_UINT32_BE(lo, h, 4);
>>>       > + vh = (uint64_t) hi << 32 | lo;
>>>       > +
>>>       > + GET_UINT32_BE(hi, h, 8);
>>>       > + GET_UINT32_BE(lo, h, 12);
>>>       > + vl = (uint64_t) hi << 32 | lo;
>>>       > +
>>>       > + ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128)
>>>       > + ctx->HH[8] = vh;
>>>       > + ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128)
>>>       > + ctx->HL[0] = 0;
>>>       > +
>>>       > + for (i = 4; i > 0; i >>= 1) {
>>>       > + uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
>>>       > + vl = (vh << 63) | (vl >> 1);
>>>       > + vh = (vh >> 1) ^ ((uint64_t) T << 32);
>>>       > + ctx->HL[i] = vl;
>>>       > + ctx->HH[i] = vh;
>>>       > + }
>>>       > + for (i = 2; i < 16; i <<= 1) {
>>>       > + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
>>>       > + vh = *HiH;
>>>       > + vl = *HiL;
>>>       > + for (j = 1; j < i; j++) {
>>>       > + HiH[j] = vh ^ ctx->HH[j];
>>>       > + HiL[j] = vl ^ ctx->HL[j];
>>>       > + }
>>>       > + }
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM processing occurs four phases: SETKEY, START, UPDATE and
>>>      FINISH.
>>>       > + *
>>>       > + * SETKEY:
>>>       > + *
>>>       > + * START: Sets the Encryption/Decryption mode.
>>>       > + * Accepts the initialization vector and additional data.
>>>       > + *
>>>       > + * UPDATE: Encrypts or decrypts the plaintext or ciphertext.
>>>       > + *
>>>       > + * FINISH: Performs a final GHASH to generate the authentication
>>>      tag.
>>>       > + *
>>>       > +
>>>      ******************************************************************************
>>>       > + *
>>>       > + * GCM_START
>>>       > + *
>>>       > + * Given a user-provided GCM context, this initializes it, sets
>>>      the encryption
>>>       > + * mode, and preprocesses the initialization vector and
>>>      additional AEAD data.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +int gcm_start(gcm_context *ctx, // pointer to user-provided GCM
>>>      context
>>>       > + int mode, // GCM_ENCRYPT or GCM_DECRYPT
>>>       > + const uchar *iv, // pointer to initialization vector
>>>       > + size_t iv_len, // IV length in bytes (should == 12)
>>>       > + const uchar *add, // ptr to additional AEAD data (NULL if none)
>>>       > + size_t add_len) // length of additional AEAD data (bytes)
>>>       > +{
>>>       > + int ret; // our error return if the AES encrypt fails
>>>       > + uchar work_buf[16]; // XOR source built from provided IV if len
>>>      != 16
>>>       > + const uchar *p; // general purpose array pointer
>>>       > + size_t use_len; // byte count to process, up to 16 bytes
>>>       > + size_t i; // local loop iterator
>>>       > +
>>>       > + // since the context might be reused under the same key
>>>       > + // we zero the working buffers for this next new process
>>>       > + memset(ctx->y, 0x00, sizeof(ctx->y));
>>>       > + memset(ctx->buf, 0x00, sizeof(ctx->buf));
>>>       > + ctx->len = 0;
>>>       > + ctx->add_len = 0;
>>>       > +
>>>       > + ctx->mode = mode; // set the GCM encryption/decryption mode
>>>       > + ctx->aes_ctx.mode = MG_ENCRYPT; // GCM *always* runs AES in
>>>      ENCRYPTION mode
>>>       > +
>>>       > + if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV
>>>       > + memcpy(ctx->y, iv, iv_len); // copy the IV to the top of the
>>>      'y' buff
>>>       > + ctx->y[15] = 1; // start "counting" from 1 (not 0)
>>>       > + } else // if we don't have a 12-byte IV, we GHASH whatever
>>>      we've been given
>>>       > + {
>>>       > + memset(work_buf, 0x00, 16); // clear the working buffer
>>>       > + PUT_UINT32_BE(iv_len * 8, work_buf, 12); // place the IV into
>>>      buffer
>>>       > +
>>>       > + p = iv;
>>>       > + while (iv_len > 0) {
>>>       > + use_len = (iv_len < 16) ? iv_len : 16;
>>>       > + for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
>>>       > + gcm_mult(ctx, ctx->y, ctx->y);
>>>       > + iv_len -= use_len;
>>>       > + p += use_len;
>>>       > + }
>>>       > + for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
>>>       > + gcm_mult(ctx, ctx->y, ctx->y);
>>>       > + }
>>>       > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr))
>>>      != 0)
>>>       > + return (ret);
>>>       > +
>>>       > + ctx->add_len = add_len;
>>>       > + p = add;
>>>       > + while (add_len > 0) {
>>>       > + use_len = (add_len < 16) ? add_len : 16;
>>>       > + for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
>>>       > + gcm_mult(ctx, ctx->buf, ctx->buf);
>>>       > + add_len -= use_len;
>>>       > + p += use_len;
>>>       > + }
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_UPDATE
>>>       > + *
>>>       > + * This is called once or more to process bulk plaintext or
>>>      ciphertext data.
>>>       > + * We give this some number of bytes of input and it returns the
>>>      same number
>>>       > + * of output bytes. If called multiple times (which is fine) all
>>>      but the final
>>>       > + * invocation MUST be called with length mod 16 == 0. (Only the
>>>      final call can
>>>       > + * have a partial block length of < 128 bits.)
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +int gcm_update(gcm_context *ctx, // pointer to user-provided GCM
>>>      context
>>>       > + size_t length, // length, in bytes, of data to process
>>>       > + const uchar *input, // pointer to source data
>>>       > + uchar *output) // pointer to destination data
>>>       > +{
>>>       > + int ret; // our error return if the AES encrypt fails
>>>       > + uchar ectr[16]; // counter-mode cipher output for XORing
>>>       > + size_t use_len; // byte count to process, up to 16 bytes
>>>       > + size_t i; // local loop iterator
>>>       > +
>>>       > + ctx->len += length; // bump the GCM context's running length count
>>>       > +
>>>       > + while (length > 0) {
>>>       > + // clamp the length to process at 16 bytes
>>>       > + use_len = (length < 16) ? length : 16;
>>>       > +
>>>       > + // increment the context's 128-bit IV||Counter 'y' vector
>>>       > + for (i = 16; i > 12; i--)
>>>       > + if (++ctx->y[i - 1] != 0) break;
>>>       > +
>>>       > + // encrypt the context's 'y' vector under the established key
>>>       > + if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0)
>>>      return (ret);
>>>       > +
>>>       > + // encrypt or decrypt the input to the output
>>>       > + if (ctx->mode == MG_ENCRYPT) {
>>>       > + for (i = 0; i < use_len; i++) {
>>>       > + // XOR the cipher's ouptut vector (ectr) with our input
>>>       > + output[i] = (uchar) (ectr[i] ^ input[i]);
>>>       > + // now we mix in our data into the authentication hash.
>>>       > + // if we're ENcrypting we XOR in the post-XOR (output)
>>>       > + // results, but if we're DEcrypting we XOR in the input
>>>       > + // data
>>>       > + ctx->buf[i] ^= output[i];
>>>       > + }
>>>       > + } else {
>>>       > + for (i = 0; i < use_len; i++) {
>>>       > + // but if we're DEcrypting we XOR in the input data first,
>>>       > + // i.e. before saving to ouput data, otherwise if the input
>>>       > + // and output buffer are the same (inplace decryption) we
>>>       > + // would not get the correct auth tag
>>>       >
>>>       > - a = (uint32_t *) ctx->in;
>>>       > - a[14] = ctx->bits[0];
>>>       > - a[15] = ctx->bits[1];
>>>       > + ctx->buf[i] ^= input[i];
>>>       >
>>>       > - mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
>>>       > - mg_byte_reverse((unsigned char *) ctx->buf, 4);
>>>       > - memcpy(digest, ctx->buf, 16);
>>>       > - memset((char *) ctx, 0, sizeof(*ctx));
>>>       > -}
>>>       > -#endif
>>>       > + // XOR the cipher's ouptut vector (ectr) with our input
>>>       > + output[i] = (uchar) (ectr[i] ^ input[i]);
>>>       > + }
>>>       > + }
>>>       > + gcm_mult(ctx, ctx->buf, ctx->buf); // perform a GHASH operation
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/mqtt.c"
>>>       > -#endif
>>>       > + length -= use_len; // drop the remaining byte count to process
>>>       > + input += use_len; // bump our input pointer forward
>>>       > + output += use_len; // bump our output pointer forward
>>>       > + }
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_FINISH
>>>       > + *
>>>       > + * This is called once after all calls to GCM_UPDATE to finalize
>>>      the GCM.
>>>       > + * It performs the final GHASH to produce the resulting
>>>      authentication TAG.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM
>>>      context
>>>       > + uchar *tag, // pointer to buffer which receives the tag
>>>       > + size_t tag_len) // length, in bytes, of the tag-receiving buf
>>>       > +{
>>>       > + uchar work_buf[16];
>>>       > + uint64_t orig_len = ctx->len * 8;
>>>       > + uint64_t orig_add_len = ctx->add_len * 8;
>>>       > + size_t i;
>>>       >
>>>       > + if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
>>>       >
>>>       > + if (orig_len || orig_add_len) {
>>>       > + memset(work_buf, 0x00, 16);
>>>       >
>>>       > + PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
>>>       > + PUT_UINT32_BE((orig_add_len), work_buf, 4);
>>>       > + PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
>>>       > + PUT_UINT32_BE((orig_len), work_buf, 12);
>>>       >
>>>       > + for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
>>>       > + gcm_mult(ctx, ctx->buf, ctx->buf);
>>>       > + for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
>>>       > + }
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_CRYPT_AND_TAG
>>>       > + *
>>>       > + * This either encrypts or decrypts the user-provided data and,
>>>      either
>>>       > + * way, generates an authentication tag of the requested length.
>>>      It must be
>>>       > + * called with a GCM context whose key has already been set with
>>>      GCM_SETKEY.
>>>       > + *
>>>       > + * The user would typically call this explicitly to ENCRYPT a
>>>      buffer of data
>>>       > + * and optional associated data, and produce its an
>>>      authentication tag.
>>>       > + *
>>>       > + * To reverse the process the user would typically call the
>>>      companion
>>>       > + * GCM_AUTH_DECRYPT function to decrypt data and verify a
>>>      user-provided
>>>       > + * authentication tag. The GCM_AUTH_DECRYPT function calls this
>>>      function
>>>       > + * to perform its decryption and tag generation, which it then
>>>      compares.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +int gcm_crypt_and_tag(
>>>       > + gcm_context *ctx, // gcm context with key already setup
>>>       > + int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
>>>       > + const uchar *iv, // pointer to the 12-byte initialization vector
>>>       > + size_t iv_len, // byte length if the IV. should always be 12
>>>       > + const uchar *add, // pointer to the non-ciphered additional data
>>>       > + size_t add_len, // byte length of the additional AEAD data
>>>       > + const uchar *input, // pointer to the cipher data source
>>>       > + uchar *output, // pointer to the cipher data destination
>>>       > + size_t length, // byte length of the cipher data
>>>       > + uchar *tag, // pointer to the tag to be generated
>>>       > + size_t tag_len) // byte length of the tag to be generated
>>>       > +{ /*
>>>       > + assuming that the caller has already invoked gcm_setkey to
>>>       > + prepare the gcm context with the keying material, we simply
>>>       > + invoke each of the three GCM sub-functions in turn...
>>>       > + */
>>>       > + gcm_start(ctx, mode, iv, iv_len, add, add_len);
>>>       > + gcm_update(ctx, length, input, output);
>>>       > + gcm_finish(ctx, tag, tag_len);
>>>       > + return (0);
>>>       > +}
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * GCM_ZERO_CTX
>>>       > + *
>>>       > + * The GCM context contains both the GCM context and the AES
>>>      context.
>>>       > + * This includes keying and key-related material which is security-
>>>       > + * sensitive, so it MUST be zeroed after use. This function does
>>>      that.
>>>       > + *
>>>       > +
>>>      ******************************************************************************/
>>>       > +void gcm_zero_ctx(gcm_context *ctx) {
>>>       > + // zero the context originally provided to us
>>>       > + memset(ctx, 0, sizeof(gcm_context));
>>>       > +}
>>>       > +//
>>>       > +// aes-gcm.c
>>>       > +// Pods
>>>       > +//
>>>       > +// Created by Markus Kosmal on 20/11/14.
>>>       > +//
>>>       > +//
>>>       >
>>>       > +int mg_aes_gcm_encrypt(unsigned char *output, //
>>>       > + const unsigned char *input, size_t input_length,
>>>       > + const unsigned char *key, const size_t key_len,
>>>       > + const unsigned char *iv, const size_t iv_len,
>>>       > + unsigned char *aead, size_t aead_len, unsigned char *tag,
>>>       > + const size_t tag_len) {
>>>       > + int ret = 0; // our return value
>>>       > + gcm_context ctx; // includes the AES context structure
>>>       >
>>>       > + gcm_setkey(&ctx, key, (uint) key_len);
>>>       >
>>>       > + ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead,
>>>      aead_len, input,
>>>       > + output, input_length, tag, tag_len);
>>>       >
>>>       > -#define MQTT_CLEAN_SESSION 0x02
>>>       > -#define MQTT_HAS_WILL 0x04
>>>       > -#define MQTT_WILL_RETAIN 0x20
>>>       > -#define MQTT_HAS_PASSWORD 0x40
>>>       > -#define MQTT_HAS_USER_NAME 0x80
>>>       > + gcm_zero_ctx(&ctx);
>>>       >
>>>       > -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd,
>>>      uint8_t flags,
>>>       > - uint32_t len) {
>>>       > - uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
>>>       > - buf[0] = (uint8_t) ((cmd << 4) | flags);
>>>       > - do {
>>>       > - *vlen = len % 0x80;
>>>       > - len /= 0x80;
>>>       > - if (len > 0) *vlen |= 0x80;
>>>       > - vlen++;
>>>       > - } while (len > 0 && vlen < &buf[sizeof(buf)]);
>>>       > - mg_send(c, buf, (size_t) (vlen - buf));
>>>       > + return (ret);
>>>       > }
>>>       >
>>>       > -static void mg_send_u16(struct mg_connection *c, uint16_t value) {
>>>       > - mg_send(c, &value, sizeof(value));
>>>       > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
>>>      char *input,
>>>       > + size_t input_length, const unsigned char *key,
>>>       > + const size_t key_len, const unsigned char *iv,
>>>       > + const size_t iv_len) {
>>>       > + int ret = 0; // our return value
>>>       > + gcm_context ctx; // includes the AES context structure
>>>       > +
>>>       > + size_t tag_len = 0;
>>>       > + unsigned char *tag_buf = NULL;
>>>       > +
>>>       > + gcm_setkey(&ctx, key, (uint) key_len);
>>>       > +
>>>       > + ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0,
>>>      input, output,
>>>       > + input_length, tag_buf, tag_len);
>>>       > +
>>>       > + gcm_zero_ctx(&ctx);
>>>       > +
>>>       > + return (ret);
>>>       > }
>>>       > +#endif
>>>       > +// End of aes128 PD
>>>       >
>>>       > -void mg_mqtt_login(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts) {
>>>       > - char rnd[10], client_id[21], zero = 0;
>>>       > - struct mg_str cid = opts->client_id;
>>>       > - uint32_t total_len = 7 + 1 + 2 + 2;
>>>       > - uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/tls_builtin.c"
>>>       > +#endif
>>>       >
>>>       > - if (cid.len == 0) {
>>>       > - mg_random(rnd, sizeof(rnd));
>>>       > - mg_hex(rnd, sizeof(rnd), client_id);
>>>       > - client_id[sizeof(client_id) - 1] = '\0';
>>>       > - cid = mg_str(client_id);
>>>       > - }
>>>       >
>>>       > - if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4
>>>      (3.1.1)
>>>       > - c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag
>>>       > - hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3); // Connection
>>>      flags
>>>       > - if (opts->user.len > 0) {
>>>       > - total_len += 2 + (uint32_t) opts->user.len;
>>>       > - hdr[7] |= MQTT_HAS_USER_NAME;
>>>       > +
>>>       > +
>>>       > +#if MG_TLS == MG_TLS_BUILTIN
>>>       > +
>>>       > +/* TLS 1.3 Record Content Type (RFC8446 B.1) */
>>>       > +#define MG_TLS_CHANGE_CIPHER 20
>>>       > +#define MG_TLS_ALERT 21
>>>       > +#define MG_TLS_HANDSHAKE 22
>>>       > +#define MG_TLS_APP_DATA 23
>>>       > +#define MG_TLS_HEARTBEAT 24
>>>       > +
>>>       > +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
>>>       > +#define MG_TLS_CLIENT_HELLO 1
>>>       > +#define MG_TLS_SERVER_HELLO 2
>>>       > +#define MG_TLS_ENCRYPTED_EXTENSIONS 8
>>>       > +#define MG_TLS_CERTIFICATE 11
>>>       > +#define MG_TLS_CERTIFICATE_VERIFY 15
>>>       > +#define MG_TLS_FINISHED 20
>>>       > +
>>>       > +// handshake is re-entrant, so we need to keep track of its
>>>      state state names
>>>       > +// refer to RFC8446#A.1
>>>       > +enum mg_tls_hs_state {
>>>       > + // Client state machine:
>>>       > + MG_TLS_STATE_CLIENT_START, // Send ClientHello
>>>       > + MG_TLS_STATE_CLIENT_WAIT_SH, // Wait for ServerHello
>>>       > + MG_TLS_STATE_CLIENT_WAIT_EE, // Wait for EncryptedExtensions
>>>       > + MG_TLS_STATE_CLIENT_WAIT_CERT, // Wait for Certificate
>>>       > + MG_TLS_STATE_CLIENT_WAIT_CV, // Wait for CertificateVerify
>>>       > + MG_TLS_STATE_CLIENT_WAIT_FINISHED, // Wait for Finished
>>>       > + MG_TLS_STATE_CLIENT_CONNECTED, // Done
>>>       > +
>>>       > + // Server state machine:
>>>       > + MG_TLS_STATE_SERVER_START, // Wait for ClientHello
>>>       > + MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finished
>>>       > + MG_TLS_STATE_SERVER_CONNECTED // Done
>>>       > +};
>>>       > +
>>>       > +// per-connection TLS data
>>>       > +struct tls_data {
>>>       > + enum mg_tls_hs_state state; // keep track of connection
>>>      handshake progress
>>>       > +
>>>       > + struct mg_iobuf send; // For the receive path, we're reusing
>>>      c->rtls
>>>       > + struct mg_iobuf recv; // While c->rtls contains full records,
>>>      recv reuses
>>>       > + // the same underlying buffer but points at individual
>>>       > + // decrypted messages
>>>       > + uint8_t content_type; // Last received record content type
>>>       > +
>>>       > + mg_sha256_ctx sha256; // incremental SHA-256 hash for TLS
>>>      handshake
>>>       > +
>>>       > + uint32_t sseq; // server sequence number, used in encryption
>>>       > + uint32_t cseq; // client sequence number, used in decryption
>>>       > +
>>>       > + uint8_t random[32]; // client random from ClientHello
>>>       > + uint8_t session_id[32]; // client session ID between the
>>>      handshake states
>>>       > + uint8_t x25519_cli[32]; // client X25519 key between the
>>>      handshake states
>>>       > + uint8_t x25519_sec[32]; // x25519 secret between the handshake
>>>      states
>>>       > +
>>>       > + int skip_verification; // perform checks on server certificate?
>>>       > + struct mg_str server_cert_der; // server certificate in DER format
>>>       > + uint8_t server_key[32]; // server EC private key
>>>       > + char hostname[254]; // server hostname (client extension)
>>>       > +
>>>       > + uint8_t certhash[32]; // certificate message hash
>>>       > + uint8_t pubkey[64]; // server EC public key to verify cert
>>>       > + uint8_t sighash[32]; // server EC public key to verify cert
>>>       > +
>>>       > + // keys for AES encryption
>>>       > + uint8_t handshake_secret[32];
>>>       > + uint8_t server_write_key[16];
>>>       > + uint8_t server_write_iv[12];
>>>       > + uint8_t server_finished_key[32];
>>>       > + uint8_t client_write_key[16];
>>>       > + uint8_t client_write_iv[12];
>>>       > + uint8_t client_finished_key[32];
>>>       > +};
>>>       > +
>>>       > +#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) |
>>>      MG_U8P(p)[1]))
>>>       > +#define MG_LOAD_BE24(p) \
>>>       > + ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) |
>>>      MG_U8P(p)[2]))
>>>       > +#define MG_STORE_BE16(p, n) \
>>>       > + do { \
>>>       > + MG_U8P(p)[0] = ((n) >> 8U) & 255; \
>>>       > + MG_U8P(p)[1] = (n) & 255; \
>>>       > + } while (0)
>>>       > +
>>>       > +#define TLS_RECHDR_SIZE 5 // 1 byte type, 2 bytes version, 2
>>>      bytes length
>>>       > +#define TLS_MSGHDR_SIZE 4 // 1 byte type, 3 bytes length
>>>       > +
>>>       > +#if 1
>>>       > +static void mg_ssl_key_log(const char *label, uint8_t
>>>      client_random[32],
>>>       > + uint8_t *secret, size_t secretsz) {
>>>       > + (void) label;
>>>       > + (void) client_random;
>>>       > + (void) secret;
>>>       > + (void) secretsz;
>>>       > +}
>>>       > +#else
>>>       > +#include <stdio.h>
>>>       > +static void mg_ssl_key_log(const char *label, uint8_t
>>>      client_random[32],
>>>       > + uint8_t *secret, size_t secretsz) {
>>>       > + char *keylogfile = getenv("SSLKEYLOGFILE");
>>>       > + if (keylogfile == NULL) {
>>>       > + return;
>>>       > }
>>>       > - if (opts->pass.len > 0) {
>>>       > - total_len += 2 + (uint32_t) opts->pass.len;
>>>       > - hdr[7] |= MQTT_HAS_PASSWORD;
>>>       > + FILE *f = fopen(keylogfile, "a");
>>>       > + fprintf(f, "%s ", label);
>>>       > + for (int i = 0; i < 32; i++) {
>>>       > + fprintf(f, "%02x", client_random[i]);
>>>       > }
>>>       > - if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
>>>       > - total_len +=
>>>       > - 4 + (uint32_t) opts->will_topic.len + (uint32_t)
>>>      opts->will_message.len;
>>>       > - hdr[7] |= MQTT_HAS_WILL;
>>>       > + fprintf(f, " ");
>>>       > + for (unsigned int i = 0; i < secretsz; i++) {
>>>       > + fprintf(f, "%02x", secret[i]);
>>>       > }
>>>       > - if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
>>>       > - if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
>>>       > - total_len += (uint32_t) cid.len;
>>>       > - if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U
>>>      : 0);
>>>       > + fprintf(f, "\n");
>>>       > + fclose(f);
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
>>>       > - mg_send(c, hdr, sizeof(hdr));
>>>       > - // keepalive == 0 means "do not disconnect us!"
>>>       > - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
>>>       > +// for derived tls keys we need SHA256([0]*32)
>>>       > +static uint8_t zeros[32] = {0};
>>>       > +static uint8_t zeros_sha256_digest[32] = {
>>>       > + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
>>>       > + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
>>>       > + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
>>>       >
>>>       > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // V5 properties
>>>       > - mg_send_u16(c, mg_htons((uint16_t) cid.len));
>>>       > - mg_send(c, cid.ptr, cid.len);
>>>       > +// helper to hexdump buffers inline
>>>       > +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t
>>>      bufsz) {
>>>       > + MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
>>>       > +}
>>>       >
>>>       > - if (hdr[7] & MQTT_HAS_WILL) {
>>>       > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // will props
>>>       > - mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
>>>       > - mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
>>>       > - mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
>>>       > - mg_send(c, opts->will_message.ptr, opts->will_message.len);
>>>       > +// helper utilities to parse ASN.1 DER
>>>       > +struct mg_der_tlv {
>>>       > + uint8_t type;
>>>       > + uint32_t len;
>>>       > + uint8_t *value;
>>>       > +};
>>>       > +
>>>       > +// parse DER into a TLV record
>>>       > +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct
>>>      mg_der_tlv *tlv) {
>>>       > + if (dersz < 2) {
>>>       > + return -1;
>>>       > }
>>>       > - if (opts->user.len > 0) {
>>>       > - mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
>>>       > - mg_send(c, opts->user.ptr, opts->user.len);
>>>       > + tlv->type = der[0];
>>>       > + tlv->len = der[1];
>>>       > + tlv->value = der + 2;
>>>       > + if (tlv->len > 0x7f) {
>>>       > + uint32_t i, n = tlv->len - 0x80;
>>>       > + tlv->len = 0;
>>>       > + for (i = 0; i < n; i++) {
>>>       > + tlv->len = (tlv->len << 8) | (der[2 + i]);
>>>       > + }
>>>       > + tlv->value = der + 2 + n;
>>>       > }
>>>       > - if (opts->pass.len > 0) {
>>>       > - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
>>>       > - mg_send(c, opts->pass.ptr, opts->pass.len);
>>>       > + if (der + dersz < tlv->value + tlv->len) {
>>>       > + return -1;
>>>       > }
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
>>>       > - struct mg_str data, int qos, bool retain) {
>>>       > - uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 :
>>>      0)), zero = 0;
>>>       > - uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
>>>       > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char
>>>      *) topic.ptr,
>>>       > - (int) data.len, (char *) data.ptr));
>>>       > - if (qos > 0) len += 2;
>>>       > - if (c->is_mqtt5) len++;
>>>       > - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
>>>       > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
>>>       > - mg_send(c, topic.ptr, topic.len);
>>>       > - if (qos > 0) {
>>>       > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>>>       > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>>>       > +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid,
>>>      size_t oidsz,
>>>       > + struct mg_der_tlv *tlv) {
>>>       > + uint8_t *p, *end;
>>>       > + struct mg_der_tlv child = {0, 0, NULL};
>>>       > + if (mg_der_to_tlv(der, dersz, tlv) < 0) {
>>>       > + return -1; // invalid DER
>>>       > + } else if (tlv->type == 6) { // found OID, check value
>>>       > + return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
>>>       > + } else if ((tlv->type & 0x20) == 0) {
>>>       > + return 0; // Primitive, but not OID: not found
>>>       > + }
>>>       > + // Constructed object: scan children
>>>       > + p = tlv->value;
>>>       > + end = tlv->value + tlv->len;
>>>       > + while (end > p) {
>>>       > + int r;
>>>       > + mg_der_to_tlv(p, (size_t) (end - p), &child);
>>>       > + r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
>>>       > + if (r < 0) return -1; // error
>>>       > + if (r > 0) return 1; // found OID!
>>>       > + p = child.value + child.len;
>>>       > }
>>>       > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
>>>       > - mg_send(c, data.ptr, data.len);
>>>       > + return 0; // not found
>>>       > }
>>>       >
>>>       > -void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic,
>>>      int qos) {
>>>       > - uint8_t qos_ = qos & 3, zero = 0;
>>>       > - uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5
>>>      ? 1 : 0);
>>>       > - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
>>>       > - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
>>>       > - mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
>>>       > - if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
>>>       > - mg_send_u16(c, mg_htons((uint16_t) topic.len));
>>>       > - mg_send(c, topic.ptr, topic.len);
>>>       > - mg_send(c, &qos_, sizeof(qos_));
>>>       > +// Did we receive a full TLS record in the c->rtls buffer?
>>>       > +static bool mg_tls_got_record(struct mg_connection *c) {
>>>       > + return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
>>>       > + c->rtls.len >=
>>>       > + (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
>>>       > }
>>>       >
>>>       > -int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
>>>       > - struct mg_mqtt_message *m) {
>>>       > - uint8_t lc = 0, *p, *end;
>>>       > - uint32_t n = 0, len_len = 0;
>>>       > +// Remove a single TLS record from the recv buffer
>>>       > +static void mg_tls_drop_record(struct mg_connection *c) {
>>>       > + struct mg_iobuf *rio = &c->rtls;
>>>       > + uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
>>>       > + mg_iobuf_del(rio, 0, n);
>>>       > +}
>>>       >
>>>       > - memset(m, 0, sizeof(*m));
>>>       > - m->dgram.ptr = (char *) buf;
>>>       > - if (len < 2) return MQTT_INCOMPLETE;
>>>       > - m->cmd = (uint8_t) (buf[0] >> 4);
>>>       > - m->qos = (buf[0] >> 1) & 3;
>>>       > +// Remove a single TLS message from decrypted buffer, remove the
>>>      wrapping
>>>       > +// record if it was the last message within a record
>>>       > +static void mg_tls_drop_message(struct mg_connection *c) {
>>>       > + uint32_t len;
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (tls->recv.len == 0) {
>>>       > + return;
>>>       > + }
>>>       > + len = MG_LOAD_BE24(tls->recv.buf + 1);
>>>       > + mg_sha256_update(&tls->sha256, tls->recv.buf, len +
>>>      TLS_MSGHDR_SIZE);
>>>       > + tls->recv.buf += len + TLS_MSGHDR_SIZE;
>>>       > + tls->recv.len -= len + TLS_MSGHDR_SIZE;
>>>       > + if (tls->recv.len == 0) {
>>>       > + mg_tls_drop_record(c);
>>>       > + }
>>>       > +}
>>>       >
>>>       > - n = len_len = 0;
>>>       > - p = (uint8_t *) buf + 1;
>>>       > - while ((size_t) (p - buf) < len) {
>>>       > - lc = *((uint8_t *) p++);
>>>       > - n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
>>>       > - len_len++;
>>>       > - if (!(lc & 0x80)) break;
>>>       > - if (len_len >= 4) return MQTT_MALFORMED;
>>>       > +// TLS1.3 secret derivation based on the key label
>>>       > +static void mg_tls_derive_secret(const char *label, uint8_t
>>>      *key, size_t keysz,
>>>       > + uint8_t *data, size_t datasz, uint8_t *hash,
>>>       > + size_t hashsz) {
>>>       > + size_t labelsz = strlen(label);
>>>       > + uint8_t secret[32];
>>>       > + uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
>>>       > + // TODO: assert lengths of label, key, data and hash
>>>       > + if (labelsz > 0) memmove(packed + 3, label, labelsz);
>>>       > + packed[3 + labelsz] = (uint8_t) datasz;
>>>       > + if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
>>>       > + packed[4 + labelsz + datasz] = 1;
>>>       > +
>>>       > + mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
>>>       > + memmove(hash, secret, hashsz);
>>>       > +}
>>>       > +
>>>       > +// at this point we have x25519 shared secret, we can generate a
>>>      set of derived
>>>       > +// handshake encryption keys
>>>       > +static void mg_tls_generate_handshake_keys(struct mg_connection
>>>      *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > +
>>>       > + mg_sha256_ctx sha256;
>>>       > + uint8_t early_secret[32];
>>>       > + uint8_t pre_extract_secret[32];
>>>       > + uint8_t hello_hash[32];
>>>       > + uint8_t server_hs_secret[32];
>>>       > + uint8_t client_hs_secret[32];
>>>       > +
>>>       > + mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
>>>       > + mg_tls_derive_secret("tls13 derived", early_secret, 32,
>>>      zeros_sha256_digest,
>>>       > + 32, pre_extract_secret, 32);
>>>       > + mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
>>>       > + sizeof(pre_extract_secret), tls->x25519_sec,
>>>       > + sizeof(tls->x25519_sec));
>>>       > + mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
>>>       > +
>>>       > + // mg_sha256_final is not idempotent, need to copy sha256
>>>      context to calculate
>>>       > + // the digest
>>>       > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>>>       > + mg_sha256_final(hello_hash, &sha256);
>>>       > +
>>>       > + mg_tls_hexdump("hello hash", hello_hash, 32);
>>>       > + // derive keys needed for the rest of the handshake
>>>       > + mg_tls_derive_secret("tls13 s hs traffic",
>>>      tls->handshake_secret, 32,
>>>       > + hello_hash, 32, server_hs_secret, 32);
>>>       > + mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
>>>       > + tls->server_write_key, 16);
>>>       > + mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
>>>       > + tls->server_write_iv, 12);
>>>       > + mg_tls_derive_secret("tls13 finished", server_hs_secret, 32,
>>>      NULL, 0,
>>>       > + tls->server_finished_key, 32);
>>>       > +
>>>       > + mg_tls_derive_secret("tls13 c hs traffic",
>>>      tls->handshake_secret, 32,
>>>       > + hello_hash, 32, client_hs_secret, 32);
>>>       > + mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
>>>       > + tls->client_write_key, 16);
>>>       > + mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
>>>       > + tls->client_write_iv, 12);
>>>       > + mg_tls_derive_secret("tls13 finished", client_hs_secret, 32,
>>>      NULL, 0,
>>>       > + tls->client_finished_key, 32);
>>>       > +
>>>       > + mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
>>>       > + mg_tls_hexdump("s key", tls->server_write_key, 16);
>>>       > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
>>>       > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
>>>       > + mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
>>>       > + mg_tls_hexdump("c key", tls->client_write_key, 16);
>>>       > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
>>>       > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
>>>       > +
>>>       > + mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
>>>       > + server_hs_secret, 32);
>>>       > + mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
>>>       > + client_hs_secret, 32);
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_generate_application_keys(struct
>>>      mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + uint8_t hash[32];
>>>       > + uint8_t premaster_secret[32];
>>>       > + uint8_t master_secret[32];
>>>       > + uint8_t server_secret[32];
>>>       > + uint8_t client_secret[32];
>>>       > +
>>>       > + mg_sha256_ctx sha256;
>>>       > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>>>       > + mg_sha256_final(hash, &sha256);
>>>       > +
>>>       > + mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
>>>       > + zeros_sha256_digest, 32, premaster_secret, 32);
>>>       > + mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
>>>       > +
>>>       > + mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32,
>>>      hash, 32,
>>>       > + server_secret, 32);
>>>       > + mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
>>>       > + tls->server_write_key, 16);
>>>       > + mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
>>>       > + tls->server_write_iv, 12);
>>>       > + mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32,
>>>      hash, 32,
>>>       > + client_secret, 32);
>>>       > + mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
>>>       > + tls->client_write_key, 16);
>>>       > + mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
>>>       > + tls->client_write_iv, 12);
>>>       > +
>>>       > + mg_tls_hexdump("s ap traffic", server_secret, 32);
>>>       > + mg_tls_hexdump("s key", tls->server_write_key, 16);
>>>       > + mg_tls_hexdump("s iv", tls->server_write_iv, 12);
>>>       > + mg_tls_hexdump("s finished", tls->server_finished_key, 32);
>>>       > + mg_tls_hexdump("c ap traffic", client_secret, 32);
>>>       > + mg_tls_hexdump("c key", tls->client_write_key, 16);
>>>       > + mg_tls_hexdump("c iv", tls->client_write_iv, 16);
>>>       > + mg_tls_hexdump("c finished", tls->client_finished_key, 32);
>>>       > + tls->sseq = tls->cseq = 0;
>>>       > +
>>>       > + mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random,
>>>      server_secret, 32);
>>>       > + mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random,
>>>      client_secret, 32);
>>>       > +}
>>>       > +
>>>       > +// AES GCM encryption of the message + put encoded data into the
>>>      write buffer
>>>       > +static void mg_tls_encrypt(struct mg_connection *c, const
>>>      uint8_t *msg,
>>>       > + size_t msgsz, uint8_t msgtype) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *wio = &tls->send;
>>>       > + uint8_t *outmsg;
>>>       > + uint8_t *tag;
>>>       > + size_t encsz = msgsz + 16 + 1;
>>>       > + uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
>>>       > + (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
>>>       > + uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
>>>       > + (uint8_t) ((encsz >> 8) & 0xff),
>>>       > + (uint8_t) (encsz & 0xff)};
>>>       > + uint8_t nonce[12];
>>>       > +
>>>       > + mg_gcm_initialize();
>>>       > +
>>>       > + if (c->is_client) {
>>>       > + memmove(nonce, tls->client_write_iv,
>>>      sizeof(tls->client_write_iv));
>>>       > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
>>>       > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
>>>       > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
>>>       > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
>>>       > + } else {
>>>       > + memmove(nonce, tls->server_write_iv,
>>>      sizeof(tls->server_write_iv));
>>>       > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
>>>       > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
>>>       > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
>>>       > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
>>>       > }
>>>       > - end = p + n;
>>>       > - if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
>>>       > - m->dgram.len = (size_t) (end - buf);
>>>       >
>>>       > - switch (m->cmd) {
>>>       > - case MQTT_CMD_CONNACK:
>>>       > - if (end - p < 2) return MQTT_MALFORMED;
>>>       > - m->ack = p[1];
>>>       > - break;
>>>       > - case MQTT_CMD_PUBACK:
>>>       > - case MQTT_CMD_PUBREC:
>>>       > - case MQTT_CMD_PUBREL:
>>>       > - case MQTT_CMD_PUBCOMP:
>>>       > - case MQTT_CMD_SUBSCRIBE:
>>>       > - case MQTT_CMD_SUBACK:
>>>       > - case MQTT_CMD_UNSUBSCRIBE:
>>>       > - case MQTT_CMD_UNSUBACK:
>>>       > - if (p + 2 > end) return MQTT_MALFORMED;
>>>       > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > - p += 2;
>>>       > - break;
>>>       > - case MQTT_CMD_PUBLISH: {
>>>       > - if (p + 2 > end) return MQTT_MALFORMED;
>>>       > - m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > - m->topic.ptr = (char *) p + 2;
>>>       > - p += 2 + m->topic.len;
>>>       > - if (p > end) return MQTT_MALFORMED;
>>>       > - if (m->qos > 0) {
>>>       > - if (p + 2 > end) return MQTT_MALFORMED;
>>>       > - m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
>>>       > - p += 2;
>>>       > - }
>>>       > - if (p > end) return MQTT_MALFORMED;
>>>       > - if (version == 5 && p + 2 < end) p += 1 + p[0]; // Skip options
>>>       > - if (p > end) return MQTT_MALFORMED;
>>>       > - m->data.ptr = (char *) p;
>>>       > - m->data.len = (size_t) (end - p);
>>>       > - break;
>>>       > + mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
>>>       > + mg_iobuf_resize(wio, wio->len + encsz);
>>>       > + outmsg = wio->buf + wio->len;
>>>       > + tag = wio->buf + wio->len + msgsz + 1;
>>>       > + memmove(outmsg, msg, msgsz);
>>>       > + outmsg[msgsz] = msgtype;
>>>       > + if (c->is_client) {
>>>       > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
>>>      tls->client_write_key,
>>>       > + sizeof(tls->client_write_key), nonce, sizeof(nonce),
>>>       > + associated_data, sizeof(associated_data), tag, 16);
>>>       > + tls->cseq++;
>>>       > + } else {
>>>       > + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1,
>>>      tls->server_write_key,
>>>       > + sizeof(tls->server_write_key), nonce, sizeof(nonce),
>>>       > + associated_data, sizeof(associated_data), tag, 16);
>>>       > + tls->sseq++;
>>>       > + }
>>>       > + wio->len += encsz;
>>>       > +}
>>>       > +
>>>       > +// read an encrypted record, decrypt it in place
>>>       > +static int mg_tls_recv_record(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *rio = &c->rtls;
>>>       > + uint16_t msgsz;
>>>       > + uint8_t *msg;
>>>       > + uint8_t nonce[12];
>>>       > + int r;
>>>       > + if (tls->recv.len > 0) {
>>>       > + return 0; /* some data from previous record is still present */
>>>       > + }
>>>       > + for (;;) {
>>>       > + if (!mg_tls_got_record(c)) {
>>>       > + return MG_IO_WAIT;
>>>       > }
>>>       > - default:
>>>       > + if (rio->buf[0] == MG_TLS_APP_DATA) {
>>>       > break;
>>>       > + } else if (rio->buf[0] ==
>>>       > + MG_TLS_CHANGE_CIPHER) { // Skip ChangeCipher messages
>>>       > + mg_tls_drop_record(c);
>>>       > + } else if (rio->buf[0] == MG_TLS_ALERT) { // Skip Alerts
>>>       > + MG_INFO(("TLS ALERT packet received"));
>>>       > + mg_tls_drop_record(c);
>>>       > + } else {
>>>       > + mg_error(c, "unexpected packet");
>>>       > + return -1;
>>>       > + }
>>>       > }
>>>       > - return MQTT_OK;
>>>       > +
>>>       > + mg_gcm_initialize();
>>>       > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>>>       > + msg = rio->buf + 5;
>>>       > + if (c->is_client) {
>>>       > + memmove(nonce, tls->server_write_iv,
>>>      sizeof(tls->server_write_iv));
>>>       > + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
>>>       > + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
>>>       > + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
>>>       > + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
>>>       > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
>>>       > + sizeof(tls->server_write_key), nonce, sizeof(nonce));
>>>       > + tls->sseq++;
>>>       > + } else {
>>>       > + memmove(nonce, tls->client_write_iv,
>>>      sizeof(tls->client_write_iv));
>>>       > + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
>>>       > + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
>>>       > + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
>>>       > + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
>>>       > + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
>>>       > + sizeof(tls->client_write_key), nonce, sizeof(nonce));
>>>       > + tls->cseq++;
>>>       > + }
>>>       > + r = msgsz - 16 - 1;
>>>       > + tls->content_type = msg[msgsz - 16 - 1];
>>>       > + tls->recv.buf = msg;
>>>       > + tls->recv.size = tls->recv.len = msgsz - 16 - 1;
>>>       > + return r;
>>>       > }
>>>       >
>>>       > -static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
>>>       > - void *fn_data) {
>>>       > - if (ev == MG_EV_READ) {
>>>       > - for (;;) {
>>>       > - uint8_t version = c->is_mqtt5 ? 5 : 4;
>>>       > - struct mg_mqtt_message mm;
>>>       > - int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
>>>       > - if (rc == MQTT_MALFORMED) {
>>>       > - MG_ERROR(("%lu MQTT malformed message", c->id));
>>>       > - c->is_closing = 1;
>>>       > - break;
>>>       > - } else if (rc == MQTT_OK) {
>>>       > - MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
>>>       > - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
>>>       > - switch (mm.cmd) {
>>>       > - case MQTT_CMD_CONNACK:
>>>       > - mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
>>>       > - if (mm.ack == 0) {
>>>       > - MG_DEBUG(("%lu Connected", c->id));
>>>       > - } else {
>>>       > - MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
>>>       > - c->is_closing = 1;
>>>       > - }
>>>       > - break;
>>>       > - case MQTT_CMD_PUBLISH: {
>>>       > - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
>>>       > - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
>>>       > - if (mm.qos > 0) {
>>>       > - uint16_t id = mg_htons(mm.id <http://mm.id>);
>>>       > - mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
>>>       > - mg_send(c, &id, sizeof(id));
>>>       > - }
>>>       > - mg_call(c, MG_EV_MQTT_MSG, &mm);
>>>       > - break;
>>>       > - }
>>>       > - }
>>>       > - mg_call(c, MG_EV_MQTT_CMD, &mm);
>>>       > - mg_iobuf_del(&c->recv, 0, mm.dgram.len);
>>>       > - } else {
>>>       > - break;
>>>       > +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
>>>       > + uint8_t hash[32]) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + uint8_t sig_content[130] = {
>>>       > + " "
>>>       > + " "
>>>       > + "TLS 1.3, server CertificateVerify\0"};
>>>       > + mg_sha256_ctx sha256;
>>>       > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>>>       > + mg_sha256_final(sig_content + 98, &sha256);
>>>       > +
>>>       > + mg_sha256_init(&sha256);
>>>       > + mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
>>>       > + mg_sha256_final(hash, &sha256);
>>>       > +}
>>>       > +
>>>       > +// read and parse ClientHello record
>>>       > +static int mg_tls_server_recv_hello(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *rio = &c->rtls;
>>>       > + uint8_t session_id_len;
>>>       > + uint16_t j;
>>>       > + uint16_t cipher_suites_len;
>>>       > + uint16_t ext_len;
>>>       > + uint8_t *ext;
>>>       > + uint16_t msgsz;
>>>       > +
>>>       > + if (!mg_tls_got_record(c)) {
>>>       > + return MG_IO_WAIT;
>>>       > + }
>>>       > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
>>>      MG_TLS_CLIENT_HELLO) {
>>>       > + mg_error(c, "not a client hello packet");
>>>       > + return -1;
>>>       > + }
>>>       > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>>>       > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
>>>       > + // store client random
>>>       > + memmove(tls->random, rio->buf + 11, sizeof(tls->random));
>>>       > + // store session_id
>>>       > + session_id_len = rio->buf[43];
>>>       > + if (session_id_len == sizeof(tls->session_id)) {
>>>       > + memmove(tls->session_id, rio->buf + 44, session_id_len);
>>>       > + } else if (session_id_len != 0) {
>>>       > + MG_INFO(("bad session id len"));
>>>       > + }
>>>       > + cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
>>>       > + ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len +
>>>      cipher_suites_len);
>>>       > + ext = rio->buf + 50 + session_id_len + cipher_suites_len;
>>>       > + for (j = 0; j < ext_len;) {
>>>       > + uint16_t k;
>>>       > + uint16_t key_exchange_len;
>>>       > + uint8_t *key_exchange;
>>>       > + uint16_t n = MG_LOAD_BE16(ext + j + 2);
>>>       > + if (ext[j] != 0x00 ||
>>>       > + ext[j + 1] != 0x33) { // not a key share extension, ignore
>>>       > + j += (uint16_t) (n + 4);
>>>       > + continue;
>>>       > + }
>>>       > + key_exchange_len = MG_LOAD_BE16(ext + j + 5);
>>>       > + key_exchange = ext + j + 6;
>>>       > + for (k = 0; k < key_exchange_len;) {
>>>       > + uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
>>>       > + if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1]
>>>      == 0x1d) {
>>>       > + memmove(tls->x25519_cli, key_exchange + k + 4, m);
>>>       > + mg_tls_drop_record(c);
>>>       > + return 0;
>>>       > }
>>>       > + k += (uint16_t) (m + 4);
>>>       > }
>>>       > + j += (uint16_t) (n + 4);
>>>       > }
>>>       > - (void) ev_data;
>>>       > - (void) fn_data;
>>>       > -}
>>>       > -
>>>       > -void mg_mqtt_ping(struct mg_connection *nc) {
>>>       > - mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
>>>       > -}
>>>       > -
>>>       > -void mg_mqtt_pong(struct mg_connection *nc) {
>>>       > - mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
>>>       > + mg_error(c, "bad client hello");
>>>       > + return -1;
>>>       > }
>>>       >
>>>       > -void mg_mqtt_disconnect(struct mg_connection *nc) {
>>>       > - mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
>>>       > -}
>>>       > +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
>>>       > +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
>>>       > +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
>>>       > +
>>>       > +// put ServerHello record into wio buffer
>>>       > +static void mg_tls_server_send_hello(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *wio = &tls->send;
>>>       > +
>>>       > + uint8_t msg_server_hello[122] = {
>>>       > + // server hello, tls 1.2
>>>       > + 0x02,
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + 0x76,
>>>       > + 0x03,
>>>       > + 0x03,
>>>       > + // random (32 bytes)
>>>       > + PLACEHOLDER_32B,
>>>       > + // session ID length + session ID (32 bytes)
>>>       > + 0x20,
>>>       > + PLACEHOLDER_32B,
>>>       > +#if defined(CHACHA20) && CHACHA20
>>>       > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
>>>       > + 0x13,
>>>       > + 0x03,
>>>       > + 0x00,
>>>       > +#else
>>>       > + // TLS_AES_128_GCM_SHA256 + no compression
>>>       > + 0x13,
>>>       > + 0x01,
>>>       > + 0x00,
>>>       > +#endif
>>>       > + // extensions + keyshare
>>>       > + 0x00,
>>>       > + 0x2e,
>>>       > + 0x00,
>>>       > + 0x33,
>>>       > + 0x00,
>>>       > + 0x24,
>>>       > + 0x00,
>>>       > + 0x1d,
>>>       > + 0x00,
>>>       > + 0x20,
>>>       > + // x25519 keyshare
>>>       > + PLACEHOLDER_32B,
>>>       > + // supported versions (tls1.3 == 0x304)
>>>       > + 0x00,
>>>       > + 0x2b,
>>>       > + 0x00,
>>>       > + 0x02,
>>>       > + 0x03,
>>>       > + 0x04
>>>       > + };
>>>       >
>>>       > -struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > - const struct mg_mqtt_opts *opts,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
>>>       > - if (c != NULL) {
>>>       > - struct mg_mqtt_opts empty;
>>>       > - memset(&empty, 0, sizeof(empty));
>>>       > - mg_mqtt_login(c, opts == NULL ? &empty : opts);
>>>       > - c->pfn = mqtt_cb;
>>>       > + // calculate keyshare
>>>       > + uint8_t x25519_pub[X25519_BYTES];
>>>       > + uint8_t x25519_prv[X25519_BYTES];
>>>       > + mg_random(x25519_prv, sizeof(x25519_prv));
>>>       > + mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
>>>       > + mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
>>>       > + mg_tls_hexdump("s x25519 sec", tls->x25519_sec,
>>>      sizeof(tls->x25519_sec));
>>>       > +
>>>       > + // fill in the gaps: random + session ID + keyshare
>>>       > + memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
>>>       > + memmove(msg_server_hello + 39, tls->session_id,
>>>      sizeof(tls->session_id));
>>>       > + memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
>>>       > +
>>>       > + // server hello message
>>>       > + mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
>>>       > + mg_iobuf_add(wio, wio->len, msg_server_hello,
>>>      sizeof(msg_server_hello));
>>>       > + mg_sha256_update(&tls->sha256, msg_server_hello,
>>>      sizeof(msg_server_hello));
>>>       > +
>>>       > + // change cipher message
>>>       > + mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_server_send_ext(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + // server extensions
>>>       > + uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
>>>       > + mg_sha256_update(&tls->sha256, ext, sizeof(ext));
>>>       > + mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_server_send_cert(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + // server DER certificate (empty)
>>>       > + size_t n = tls->server_cert_der.len;
>>>       > + uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
>>>       > + if (cert == NULL) {
>>>       > + mg_error(c, "tls cert oom");
>>>       > + return;
>>>       > }
>>>       > - return c;
>>>       > -}
>>>       > + cert[0] = 0x0b; // handshake header
>>>       > + cert[1] = (uint8_t) (((n + 9) >> 16) & 255U); // 3 bytes:
>>>      payload length
>>>       > + cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
>>>       > + cert[3] = (uint8_t) ((n + 9) & 255U);
>>>       > + cert[4] = 0; // request context
>>>       > + cert[5] = (uint8_t) (((n + 5) >> 16) & 255U); // 3 bytes: cert
>>>      (s) length
>>>       > + cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
>>>       > + cert[7] = (uint8_t) ((n + 5) & 255U);
>>>       > + cert[8] =
>>>       > + (uint8_t) (((n) >> 16) & 255U); // 3 bytes: first (and only)
>>>      cert len
>>>       > + cert[9] = (uint8_t) (((n) >> 8) & 255U);
>>>       > + cert[10] = (uint8_t) (n & 255U);
>>>       > + // bytes 11+ are certificate in DER format
>>>       > + memmove(cert + 11, tls->server_cert_der.buf, n);
>>>       > + cert[11 + n] = cert[12 + n] = 0; // certificate extensions (none)
>>>       > + mg_sha256_update(&tls->sha256, cert, 13 + n);
>>>       > + mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
>>>       > + free(cert);
>>>       > +}
>>>       > +
>>>       > +// type adapter between uECC hash context and our sha256
>>>      implementation
>>>       > +typedef struct SHA256_HashContext {
>>>       > + MG_UECC_HashContext uECC;
>>>       > + mg_sha256_ctx ctx;
>>>       > +} SHA256_HashContext;
>>>       > +
>>>       > +static void init_SHA256(const MG_UECC_HashContext *base) {
>>>       > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>>>       > + mg_sha256_init(&c->ctx);
>>>       > +}
>>>       > +
>>>       > +static void update_SHA256(const MG_UECC_HashContext *base,
>>>       > + const uint8_t *message, unsigned message_size) {
>>>       > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>>>       > + mg_sha256_update(&c->ctx, message, message_size);
>>>       > +}
>>>       > +static void finish_SHA256(const MG_UECC_HashContext *base,
>>>       > + uint8_t *hash_result) {
>>>       > + SHA256_HashContext *c = (SHA256_HashContext *) base;
>>>       > + mg_sha256_final(hash_result, &c->ctx);
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_server_send_cert_verify(struct mg_connection
>>>      *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + // server certificate verify packet
>>>       > + uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00,
>>>      0x00};
>>>       > + size_t sigsz, verifysz = 0;
>>>       > + uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
>>>       > + struct SHA256_HashContext ctx = {
>>>       > + {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
>>>       > + {{0}, 0, 0, {0}}};
>>>       > + int neg1, neg2;
>>>       > + uint8_t sig[64] = {0};
>>>       > +
>>>       > + mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
>>>       > +
>>>       > + mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash),
>>>      &ctx.uECC,
>>>       > + sig, mg_uecc_secp256r1());
>>>       > +
>>>       > + neg1 = !!(sig[0] & 0x80);
>>>       > + neg2 = !!(sig[32] & 0x80);
>>>       > + verify[8] = 0x30; // ASN.1 SEQUENCE
>>>       > + verify[9] = (uint8_t) (68 + neg1 + neg2);
>>>       > + verify[10] = 0x02; // ASN.1 INTEGER
>>>       > + verify[11] = (uint8_t) (32 + neg1);
>>>       > + memmove(verify + 12 + neg1, sig, 32);
>>>       > + verify[12 + 32 + neg1] = 0x02; // ASN.1 INTEGER
>>>       > + verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
>>>       > + memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
>>>       > +
>>>       > + sigsz = (size_t) (70 + neg1 + neg2);
>>>       > + verifysz = 8U + sigsz;
>>>       > + verify[3] = (uint8_t) (sigsz + 4);
>>>       > + verify[7] = (uint8_t) sigsz;
>>>       > +
>>>       > + mg_sha256_update(&tls->sha256, verify, verifysz);
>>>       > + mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_server_send_finish(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *wio = &tls->send;
>>>       > + mg_sha256_ctx sha256;
>>>       > + uint8_t hash[32];
>>>       > + uint8_t finish[36] = {0x14, 0, 0, 32};
>>>       > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>>>       > + mg_sha256_final(hash, &sha256);
>>>       > + mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash,
>>>      32);
>>>       > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
>>>       > + mg_io_send(c, wio->buf, wio->len);
>>>       > + wio->len = 0;
>>>       > +
>>>       > + mg_sha256_update(&tls->sha256, finish, sizeof(finish));
>>>       > +}
>>>       > +
>>>       > +static int mg_tls_server_recv_finish(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + // we have to backup sha256 value to restore it later, since
>>>      Finished record
>>>       > + // is exceptional and is not supposed to be added to the
>>>      rolling hash
>>>       > + // calculation.
>>>       > + mg_sha256_ctx sha256 = tls->sha256;
>>>       > + if (mg_tls_recv_record(c) < 0) {
>>>       > + return -1;
>>>       > + }
>>>       > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
>>>       > + mg_error(c, "expected Finish but got msg 0x%02x",
>>>      tls->recv.buf[0]);
>>>       > + return -1;
>>>       > + }
>>>       > + mg_tls_drop_message(c);
>>>       >
>>>       > -struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
>>>       > - if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
>>>       > - return c;
>>>       > + // restore hash
>>>       > + tls->sha256 = sha256;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/net.c"
>>>       > +static void mg_tls_client_send_hello(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *wio = &tls->send;
>>>       > +
>>>       > + const char *hostname = tls->hostname;
>>>       > + size_t hostnamesz = strlen(tls->hostname);
>>>       > + uint8_t x25519_pub[X25519_BYTES];
>>>       > +
>>>       > + uint8_t msg_client_hello[162 + 32] = {
>>>       > + // TLS Client Hello header reported as TLS1.2 (5)
>>>       > + 0x16,
>>>       > + 0x03,
>>>       > + 0x01,
>>>       > + 0x00,
>>>       > + 0xfe,
>>>       > + // server hello, tls 1.2 (6)
>>>       > + 0x01,
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + 0x8c,
>>>       > + 0x03,
>>>       > + 0x03,
>>>       > + // random (32 bytes)
>>>       > + PLACEHOLDER_32B,
>>>       > + // session ID length + session ID (32 bytes)
>>>       > + 0x20,
>>>       > + PLACEHOLDER_32B,
>>>       > +#if defined(CHACHA20) && CHACHA20
>>>       > + // TLS_CHACHA20_POLY1305_SHA256 + no compression
>>>       > + 0x13,
>>>       > + 0x03,
>>>       > + 0x00,
>>>       > +#else
>>>       > + 0x00,
>>>       > + 0x02, // size = 2 bytes
>>>       > + 0x13,
>>>       > + 0x01, // TLS_AES_128_GCM_SHA256
>>>       > + 0x01,
>>>       > + 0x00, // no compression
>>>       > #endif
>>>       >
>>>       > + // extensions + keyshare
>>>       > + 0x00,
>>>       > + 0xfe,
>>>       > + // x25519 keyshare
>>>       > + 0x00,
>>>       > + 0x33,
>>>       > + 0x00,
>>>       > + 0x26,
>>>       > + 0x00,
>>>       > + 0x24,
>>>       > + 0x00,
>>>       > + 0x1d,
>>>       > + 0x00,
>>>       > + 0x20,
>>>       > + PLACEHOLDER_32B,
>>>       > + // supported groups (x25519)
>>>       > + 0x00,
>>>       > + 0x0a,
>>>       > + 0x00,
>>>       > + 0x04,
>>>       > + 0x00,
>>>       > + 0x02,
>>>       > + 0x00,
>>>       > + 0x1d,
>>>       > + // supported versions (tls1.3 == 0x304)
>>>       > + 0x00,
>>>       > + 0x2b,
>>>       > + 0x00,
>>>       > + 0x03,
>>>       > + 0x02,
>>>       > + 0x03,
>>>       > + 0x04,
>>>       > + // session ticket (none)
>>>       > + 0x00,
>>>       > + 0x23,
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + // signature algorithms (we don't care, so list all the common
>>>      ones)
>>>       > + 0x00,
>>>       > + 0x0d,
>>>       > + 0x00,
>>>       > + 0x24,
>>>       > + 0x00,
>>>       > + 0x22,
>>>       > + 0x04,
>>>       > + 0x03,
>>>       > + 0x05,
>>>       > + 0x03,
>>>       > + 0x06,
>>>       > + 0x03,
>>>       > + 0x08,
>>>       > + 0x07,
>>>       > + 0x08,
>>>       > + 0x08,
>>>       > + 0x08,
>>>       > + 0x1a,
>>>       > + 0x08,
>>>       > + 0x1b,
>>>       > + 0x08,
>>>       > + 0x1c,
>>>       > + 0x08,
>>>       > + 0x09,
>>>       > + 0x08,
>>>       > + 0x0a,
>>>       > + 0x08,
>>>       > + 0x0b,
>>>       > + 0x08,
>>>       > + 0x04,
>>>       > + 0x08,
>>>       > + 0x05,
>>>       > + 0x08,
>>>       > + 0x06,
>>>       > + 0x04,
>>>       > + 0x01,
>>>       > + 0x05,
>>>       > + 0x01,
>>>       > + 0x06,
>>>       > + 0x01,
>>>       > + // server name
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + 0xfe,
>>>       > + 0x00,
>>>       > + 0xfe,
>>>       > + 0x00,
>>>       > + 0x00,
>>>       > + 0xfe
>>>       > + };
>>>       >
>>>       > + // patch ClientHello with correct hostname length + offset:
>>>       > + MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
>>>       > + MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
>>>       > + MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
>>>       > + MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
>>>       > + MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
>>>       > + MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
>>>       > +
>>>       > + // calculate keyshare
>>>       > + mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
>>>       > + mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
>>>       > +
>>>       > + // fill in the gaps: random + session ID + keyshare
>>>       > + mg_random(tls->session_id, sizeof(tls->session_id));
>>>       > + mg_random(tls->random, sizeof(tls->random));
>>>       > + memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
>>>       > + memmove(msg_client_hello + 44, tls->session_id,
>>>      sizeof(tls->session_id));
>>>       > + memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
>>>       > +
>>>       > + // server hello message
>>>       > + mg_iobuf_add(wio, wio->len, msg_client_hello,
>>>      sizeof(msg_client_hello));
>>>       > + mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
>>>       > + mg_sha256_update(&tls->sha256, msg_client_hello + 5,
>>>       > + sizeof(msg_client_hello) - 5);
>>>       > + mg_sha256_update(&tls->sha256, (uint8_t *) hostname,
>>>      strlen(hostname));
>>>       > +
>>>       > + // change cipher message
>>>       > + mg_iobuf_add(wio, wio->len, (const char *)
>>>      "\x14\x03\x03\x00\x01\x01", 6);
>>>       > + mg_io_send(c, wio->buf, wio->len);
>>>       > + wio->len = 0;
>>>       > +}
>>>       > +
>>>       > +static int mg_tls_client_recv_hello(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *rio = &c->rtls;
>>>       > + uint16_t msgsz;
>>>       > + uint8_t *ext;
>>>       > + uint16_t ext_len;
>>>       > + int j;
>>>       > +
>>>       > + if (!mg_tls_got_record(c)) {
>>>       > + return MG_IO_WAIT;
>>>       > + }
>>>       > + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] !=
>>>      MG_TLS_SERVER_HELLO) {
>>>       > + if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
>>>       > + mg_error(c, "tls alert %d", rio->buf[6]);
>>>       > + return -1;
>>>       > + }
>>>       > + MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0],
>>>      rio->buf[5]));
>>>       > + mg_error(c, "not a server hello packet");
>>>       > + return -1;
>>>       > + }
>>>       >
>>>       > -
>>>       > -
>>>       > -
>>>       > -
>>>       > -size_t mg_vprintf(struct mg_connection *c, const char *fmt,
>>>      va_list *ap) {
>>>       > - size_t old = c->send.len;
>>>       > - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>>>       > - return c->send.len - old;
>>>       > -}
>>>       > -
>>>       > -size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
>>>       > - size_t len = 0;
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - len = mg_vprintf(c, fmt, &ap);
>>>       > - va_end(ap);
>>>       > - return len;
>>>       > -}
>>>       > -
>>>       > -static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
>>>       > - if (mg_vcasecmp(&str, "localhost") != 0) return false;
>>>       > - addr->ip = mg_htonl(0x7f000001);
>>>       > - addr->is_ip6 = false;
>>>       > - return true;
>>>       > + msgsz = MG_LOAD_BE16(rio->buf + 3);
>>>       > + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
>>>       > +
>>>       > + ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
>>>       > + ext = rio->buf + 5 + 39 + 32 + 3 + 2;
>>>       > +
>>>       > + for (j = 0; j < ext_len;) {
>>>       > + uint16_t ext_type = MG_LOAD_BE16(ext + j);
>>>       > + uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
>>>       > + uint16_t group;
>>>       > + uint8_t *key_exchange;
>>>       > + uint16_t key_exchange_len;
>>>       > + if (ext_type != 0x0033) { // not a key share extension, ignore
>>>       > + j += (uint16_t) (ext_len2 + 4);
>>>       > + continue;
>>>       > + }
>>>       > + group = MG_LOAD_BE16(ext + j + 4);
>>>       > + if (group != 0x001d) {
>>>       > + mg_error(c, "bad key exchange group");
>>>       > + return -1;
>>>       > + }
>>>       > + key_exchange_len = MG_LOAD_BE16(ext + j + 6);
>>>       > + key_exchange = ext + j + 8;
>>>       > + if (key_exchange_len != 32) {
>>>       > + mg_error(c, "bad key exchange length");
>>>       > + return -1;
>>>       > + }
>>>       > + mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
>>>       > + mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
>>>       > + mg_tls_drop_record(c);
>>>       > + /* generate handshake keys */
>>>       > + mg_tls_generate_handshake_keys(c);
>>>       > + return 0;
>>>       > + }
>>>       > + mg_error(c, "bad client hello");
>>>       > + return -1;
>>>       > }
>>>       >
>>>       > -static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
>>>       > - if (str.len > 0) return false;
>>>       > - addr->ip = 0;
>>>       > - addr->is_ip6 = false;
>>>       > - return true;
>>>       > +static int mg_tls_client_recv_ext(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (mg_tls_recv_record(c) < 0) {
>>>       > + return -1;
>>>       > + }
>>>       > + if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
>>>       > + mg_error(c, "expected server extensions but got msg 0x%02x",
>>>       > + tls->recv.buf[0]);
>>>       > + return -1;
>>>       > + }
>>>       > + mg_tls_drop_message(c);
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
>>>       > - uint8_t data[4] = {0, 0, 0, 0};
>>>       > - size_t i, num_dots = 0;
>>>       > - for (i = 0; i < str.len; i++) {
>>>       > - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>>>       > - int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
>>>       > - if (octet > 255) return false;
>>>       > - data[num_dots] = (uint8_t) octet;
>>>       > - } else if (str.ptr[i] == '.') {
>>>       > - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return
>>>      false;
>>>       > - num_dots++;
>>>       > - } else {
>>>       > - return false;
>>>       > - }
>>>       > +static int mg_tls_client_recv_cert(struct mg_connection *c) {
>>>       > + uint8_t *cert;
>>>       > + uint32_t certsz;
>>>       > + struct mg_der_tlv oid, pubkey, seq, subj;
>>>       > + int subj_match = 0;
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (mg_tls_recv_record(c) < 0) {
>>>       > + return -1;
>>>       > + }
>>>       > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
>>>       > + mg_error(c, "expected server certificate but got msg 0x%02x",
>>>       > + tls->recv.buf[0]);
>>>       > + return -1;
>>>       > + }
>>>       > + if (tls->skip_verification) {
>>>       > + mg_tls_drop_message(c);
>>>       > + return 0;
>>>       > }
>>>       > - if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
>>>       > - memcpy(&addr->ip, data, sizeof(data));
>>>       > - addr->is_ip6 = false;
>>>       > - return true;
>>>       > -}
>>>       >
>>>       > -static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
>>>       > - int i;
>>>       > - if (str.len < 14) return false;
>>>       > - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] !=
>>>      ':') return false;
>>>       > - for (i = 2; i < 6; i++) {
>>>       > - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
>>>       > + if (tls->recv.len < 11) {
>>>       > + mg_error(c, "certificate list too short");
>>>       > + return -1;
>>>       > }
>>>       > - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return
>>>      false;
>>>       > - memset(addr->ip6, 0, sizeof(addr->ip6));
>>>       > - addr->ip6[10] = addr->ip6[11] = 255;
>>>       > - memcpy(&addr->ip6[12], &addr->ip, 4);
>>>       > - addr->is_ip6 = true;
>>>       > - return true;
>>>       > -}
>>>       >
>>>       > -static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
>>>       > - size_t i, j = 0, n = 0, dc = 42;
>>>       > - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
>>>       > - if (mg_v4mapped(str, addr)) return true;
>>>       > - for (i = 0; i < str.len; i++) {
>>>       > - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
>>>       > - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
>>>       > - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
>>>       > - unsigned long val;
>>>       > - if (i > j + 3) return false;
>>>       > - // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1),
>>>      &str.ptr[j]));
>>>       > - val = mg_unhexn(&str.ptr[j], i - j + 1);
>>>       > - addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
>>>       > - addr->ip6[n + 1] = (uint8_t) (val & 255);
>>>       > - } else if (str.ptr[i] == ':') {
>>>       > - j = i + 1;
>>>       > - if (i > 0 && str.ptr[i - 1] == ':') {
>>>       > - dc = n; // Double colon
>>>       > - if (i > 1 && str.ptr[i - 2] == ':') return false;
>>>       > - } else if (i > 0) {
>>>       > - n += 2;
>>>       > + cert = tls->recv.buf + 11;
>>>       > + certsz = MG_LOAD_BE24(tls->recv.buf + 8);
>>>       > + if (certsz > tls->recv.len - 11) {
>>>       > + mg_error(c, "certificate too long: %d vs %d", certsz,
>>>      tls->recv.len - 11);
>>>       > + return -1;
>>>       > + }
>>>       > +
>>>       > + do {
>>>       > + // secp256r1 public key
>>>       > + if (mg_der_find(cert, certsz,
>>>       > + (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
>>>       > + &oid) < 0) {
>>>       > + mg_error(c, "certificate secp256r1 public key OID not found");
>>>       > + return -1;
>>>       > + }
>>>       > + if (mg_der_to_tlv(oid.value + oid.len,
>>>       > + (size_t) (cert + certsz - (oid.value + oid.len)),
>>>       > + &pubkey) < 0) {
>>>       > + mg_error(c, "certificate secp256r1 public key not found");
>>>       > + return -1;
>>>       > + }
>>>       > +
>>>       > + // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32
>>>      content bytes
>>>       > + if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] !=
>>>      0 ||
>>>       > + pubkey.value[1] != 4) {
>>>       > + mg_error(c, "unsupported public key bitstring encoding");
>>>       > + return -1;
>>>       > + }
>>>       > + memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
>>>       > + } while (0);
>>>       > +
>>>       > + // Subject Alternative Names
>>>       > + do {
>>>       > + if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3,
>>>      &oid) < 0) {
>>>       > + mg_error(c, "certificate does not contain subject alternative
>>>      names");
>>>       > + return -1;
>>>       > + }
>>>       > + if (mg_der_to_tlv(oid.value + oid.len,
>>>       > + (size_t) (cert + certsz - (oid.value + oid.len)),
>>>       > + &seq) < 0) {
>>>       > + mg_error(c, "certificate subject alternative names not found");
>>>       > + return -1;
>>>       > + }
>>>       > + if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
>>>       > + mg_error(
>>>       > + c,
>>>       > + "certificate subject alternative names is not a constructed
>>>      object");
>>>       > + return -1;
>>>       > + }
>>>       > + MG_VERBOSE(("verify hostname %s", tls->hostname));
>>>       > + while (seq.len > 0) {
>>>       > + if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
>>>       > + mg_error(c, "bad subject alternative name");
>>>       > + return -1;
>>>       > }
>>>       > - if (n > 14) return false;
>>>       > - addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing ::
>>>       > - } else {
>>>       > - return false;
>>>       > + MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
>>>       > + if (mg_match(mg_str((const char *) tls->hostname),
>>>       > + mg_str_n((const char *) subj.value, subj.len), NULL)) {
>>>       > + subj_match = 1;
>>>       > + break;
>>>       > + }
>>>       > + seq.len = (uint32_t) (seq.value + seq.len - (subj.value +
>>>      subj.len));
>>>       > + seq.value = subj.value + subj.len;
>>>       > + }
>>>       > + if (!subj_match) {
>>>       > + mg_error(c, "certificate did not match the hostname");
>>>       > + return -1;
>>>       > }
>>>       > + } while (0);
>>>       > +
>>>       > + mg_tls_drop_message(c);
>>>       > + mg_tls_calc_cert_verify_hash(c, tls->sighash);
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +static int mg_tls_client_recv_cert_verify(struct mg_connection
>>>      *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (mg_tls_recv_record(c) < 0) {
>>>       > + return -1;
>>>       > }
>>>       > - if (n < 14 && dc == 42) return false;
>>>       > - if (n < 14) {
>>>       > - memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
>>>       > - memset(&addr->ip6[dc], 0, 14 - n);
>>>       > + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
>>>       > + mg_error(c, "expected server certificate verify but got msg
>>>      0x%02x",
>>>       > + tls->recv.buf[0]);
>>>       > + return -1;
>>>       > + }
>>>       > + // Ignore CertificateVerify is strict checks are not required
>>>       > + if (tls->skip_verification) {
>>>       > + mg_tls_drop_message(c);
>>>       > + return 0;
>>>       > }
>>>       > - addr->is_ip6 = true;
>>>       > - return true;
>>>       > -}
>>>       >
>>>       > -bool mg_aton(struct mg_str str, struct mg_addr *addr) {
>>>       > - // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
>>>       > - return mg_atone(str, addr) || mg_atonl(str, addr) ||
>>>      mg_aton4(str, addr) ||
>>>       > - mg_aton6(str, addr);
>>>       > + // Extract certificate signature and verify it using pubkey and
>>>      sighash
>>>       > + do {
>>>       > + uint8_t sig[64];
>>>       > + struct mg_der_tlv seq, a, b;
>>>       > + if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) <
>>>      0) {
>>>       > + mg_error(c, "verification message is not an ASN.1 DER sequence");
>>>       > + return -1;
>>>       > + }
>>>       > + if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
>>>       > + mg_error(c, "missing first part of the signature");
>>>       > + return -1;
>>>       > + }
>>>       > + if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
>>>       > + mg_error(c, "missing second part of the signature");
>>>       > + return -1;
>>>       > + }
>>>       > + // Integers may be padded with zeroes
>>>       > + if (a.len > 32) {
>>>       > + a.value = a.value + (a.len - 32);
>>>       > + a.len = 32;
>>>       > + }
>>>       > + if (b.len > 32) {
>>>       > + b.value = b.value + (b.len - 32);
>>>       > + b.len = 32;
>>>       > + }
>>>       > +
>>>       > + memmove(sig, a.value, a.len);
>>>       > + memmove(sig + 32, b.value, b.len);
>>>       > +
>>>       > + if (mg_uecc_verify(tls->pubkey, tls->sighash,
>>>      sizeof(tls->sighash), sig,
>>>       > + mg_uecc_secp256r1()) != 1) {
>>>       > + mg_error(c, "failed to verify certificate");
>>>       > + return -1;
>>>       > + }
>>>       > + } while (0);
>>>       > +
>>>       > + mg_tls_drop_message(c);
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
>>>       > - struct mg_connection *c =
>>>       > - (struct mg_connection *) calloc(1, sizeof(*c) +
>>>      mgr->extraconnsize);
>>>       > - if (c != NULL) {
>>>       > - c->mgr = mgr;
>>>       > - c->send.align = c->recv.align = MG_IO_SIZE;
>>>       > - c->id = ++mgr->nextid;
>>>       > +static int mg_tls_client_recv_finish(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (mg_tls_recv_record(c) < 0) {
>>>       > + return -1;
>>>       > }
>>>       > - return c;
>>>       > + if (tls->recv.buf[0] != MG_TLS_FINISHED) {
>>>       > + mg_error(c, "expected server finished but got msg 0x%02x",
>>>       > + tls->recv.buf[0]);
>>>       > + return -1;
>>>       > + }
>>>       > + mg_tls_drop_message(c);
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -void mg_close_conn(struct mg_connection *c) {
>>>       > - mg_resolve_cancel(c); // Close any pending DNS query
>>>       > - LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
>>>       > - if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
>>>       > - if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
>>>       > - // Order of operations is important. `MG_EV_CLOSE` event must
>>>      be fired
>>>       > - // before we deallocate received data, see #1331
>>>       > - mg_call(c, MG_EV_CLOSE, NULL);
>>>       > - MG_DEBUG(("%lu %p closed", c->id, c->fd));
>>>       > -
>>>       > - mg_tls_free(c);
>>>       > - mg_iobuf_free(&c->recv);
>>>       > - mg_iobuf_free(&c->send);
>>>       > - memset(c, 0, sizeof(*c));
>>>       > - free(c);
>>>       > +static void mg_tls_client_send_finish(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + struct mg_iobuf *wio = &tls->send;
>>>       > + mg_sha256_ctx sha256;
>>>       > + uint8_t hash[32];
>>>       > + uint8_t finish[36] = {0x14, 0, 0, 32};
>>>       > + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
>>>       > + mg_sha256_final(hash, &sha256);
>>>       > + mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash,
>>>      32);
>>>       > + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
>>>       > + mg_io_send(c, wio->buf, wio->len);
>>>       > + wio->len = 0;
>>>       > +}
>>>       > +
>>>       > +static void mg_tls_client_handshake(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + switch (tls->state) {
>>>       > + case MG_TLS_STATE_CLIENT_START:
>>>       > + mg_tls_client_send_hello(c);
>>>       > + tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
>>>       > + // Fallthrough
>>>       > + case MG_TLS_STATE_CLIENT_WAIT_SH:
>>>       > + if (mg_tls_client_recv_hello(c) < 0) {
>>>       > + break;
>>>       > + }
>>>       > + tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
>>>       > + // Fallthrough
>>>       > + case MG_TLS_STATE_CLIENT_WAIT_EE:
>>>       > + if (mg_tls_client_recv_ext(c) < 0) {
>>>       > + break;
>>>       > + }
>>>       > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
>>>       > + // Fallthrough
>>>       > + case MG_TLS_STATE_CLIENT_WAIT_CERT:
>>>       > + if (mg_tls_client_recv_cert(c) < 0) {
>>>       > + break;
>>>       > + }
>>>       > + tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
>>>       > + // Fallthrough
>>>       > + case MG_TLS_STATE_CLIENT_WAIT_CV:
>>>       > + if (mg_tls_client_recv_cert_verify(c) < 0) {
>>>       > + break;
>>>       > + }
>>>       > + tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
>>>       > + // Fallthrough
>>>       > + case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
>>>       > + if (mg_tls_client_recv_finish(c) < 0) {
>>>       > + break;
>>>       > + }
>>>       > + mg_tls_client_send_finish(c);
>>>       > + mg_tls_generate_application_keys(c);
>>>       > + tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
>>>       > + c->is_tls_hs = 0;
>>>       > + break;
>>>       > + default: mg_error(c, "unexpected client state: %d",
>>>      tls->state); break;
>>>       > + }
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char
>>>      *url,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = NULL;
>>>       > - if (url == NULL || url[0] == '\0') {
>>>       > - MG_ERROR(("null url"));
>>>       > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > - MG_ERROR(("OOM"));
>>>       > - } else {
>>>       > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > - c->is_udp = (strncmp(url, "udp:", 4) == 0);
>>>       > - c->fd = (void *) (size_t) MG_INVALID_SOCKET;
>>>       > - c->fn = fn;
>>>       > - c->is_client = true;
>>>       > - c->fn_data = fn_data;
>>>       > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
>>>       > - mg_call(c, MG_EV_OPEN, NULL);
>>>       > - mg_resolve(c, url);
>>>       > +static void mg_tls_server_handshake(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + switch (tls->state) {
>>>       > + case MG_TLS_STATE_SERVER_START:
>>>       > + if (mg_tls_server_recv_hello(c) < 0) {
>>>       > + return;
>>>       > + }
>>>       > + mg_tls_server_send_hello(c);
>>>       > + mg_tls_generate_handshake_keys(c);
>>>       > + mg_tls_server_send_ext(c);
>>>       > + mg_tls_server_send_cert(c);
>>>       > + mg_tls_server_send_cert_verify(c);
>>>       > + mg_tls_server_send_finish(c);
>>>       > + tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
>>>       > + // fallthrough
>>>       > + case MG_TLS_STATE_SERVER_NEGOTIATED:
>>>       > + if (mg_tls_server_recv_finish(c) < 0) {
>>>       > + return;
>>>       > + }
>>>       > + mg_tls_generate_application_keys(c);
>>>       > + tls->state = MG_TLS_STATE_SERVER_CONNECTED;
>>>       > + c->is_tls_hs = 0;
>>>       > + return;
>>>       > + default: mg_error(c, "unexpected server state: %d",
>>>      tls->state); break;
>>>       > }
>>>       > - return c;
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_listen(struct mg_mgr *mgr, const char
>>>      *url,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = NULL;
>>>       > - if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > - MG_ERROR(("OOM %s", url));
>>>       > - } else if (!mg_open_listener(c, url)) {
>>>       > - MG_ERROR(("Failed: %s, errno %d", url, errno));
>>>       > - free(c);
>>>       > - c = NULL;
>>>       > +void mg_tls_handshake(struct mg_connection *c) {
>>>       > + if (c->is_client) {
>>>       > + mg_tls_client_handshake(c);
>>>       > } else {
>>>       > - c->is_listening = 1;
>>>       > - c->is_udp = strncmp(url, "udp:", 4) == 0;
>>>       > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > - c->fn = fn;
>>>       > - c->fn_data = fn_data;
>>>       > - mg_call(c, MG_EV_OPEN, NULL);
>>>       > - MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
>>>       > + mg_tls_server_handshake(c);
>>>       > }
>>>       > - return c;
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
>>>       > - mg_event_handler_t fn, void *fn_data) {
>>>       > - struct mg_connection *c = mg_alloc_conn(mgr);
>>>       > - if (c != NULL) {
>>>       > - c->fd = (void *) (size_t) fd;
>>>       > - c->fn = fn;
>>>       > - c->fn_data = fn_data;
>>>       > - MG_EPOLL_ADD(c);
>>>       > - mg_call(c, MG_EV_OPEN, NULL);
>>>       > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > +static int mg_parse_pem(const struct mg_str pem, const struct
>>>      mg_str label,
>>>       > + struct mg_str *der) {
>>>       > + size_t n = 0, m = 0;
>>>       > + char *s;
>>>       > + const char *c;
>>>       > + struct mg_str caps[5];
>>>       > + if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END
>>>      #-----#"), caps)) {
>>>       > + der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
>>>       > + der->len = pem.len;
>>>       > + return 0;
>>>       > + }
>>>       > + if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label)
>>>      != 0) {
>>>       > + return -1; // bad label
>>>       > + }
>>>       > + if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
>>>       > + return -1;
>>>       > }
>>>       > - return c;
>>>       > -}
>>>       >
>>>       > -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>>>      milliseconds,
>>>       > - unsigned flags, void (*fn)(void *), void *arg) {
>>>       > - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
>>>       > - if (t != NULL) {
>>>       > - mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
>>>       > - t->id = mgr->timerid++;
>>>       > + for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
>>>       > + if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
>>>       > + continue;
>>>       > + }
>>>       > + s[n++] = *c;
>>>       > }
>>>       > - return t;
>>>       > + m = mg_base64_decode(s, n, s, n);
>>>       > + if (m == 0) {
>>>       > + free(s);
>>>       > + return -1;
>>>       > + }
>>>       > + der->buf = s;
>>>       > + der->len = m;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -void mg_mgr_free(struct mg_mgr *mgr) {
>>>       > - struct mg_connection *c;
>>>       > - struct mg_timer *tmp, *t = mgr->timers;
>>>       > - while (t != NULL) tmp = t->next, free(t), t = tmp;
>>>       > - mgr->timers = NULL; // Important. Next call to poll won't touch
>>>      timers
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
>>>       > - mg_mgr_poll(mgr, 0);
>>>       > -#if MG_ENABLE_FREERTOS_TCP
>>>       > - FreeRTOS_DeleteSocketSet(mgr->ss);
>>>       > -#endif
>>>       > - MG_DEBUG(("All connections closed"));
>>>       > -#if MG_ENABLE_EPOLL
>>>       > - if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
>>>       > -#endif
>>>       > -}
>>>       > +void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > + struct mg_str key;
>>>       > + struct tls_data *tls = (struct tls_data *) calloc(1,
>>>      sizeof(struct tls_data));
>>>       > + if (tls == NULL) {
>>>       > + mg_error(c, "tls oom");
>>>       > + return;
>>>       > + }
>>>       >
>>>       > -void mg_mgr_init(struct mg_mgr *mgr) {
>>>       > - memset(mgr, 0, sizeof(*mgr));
>>>       > -#if MG_ENABLE_EPOLL
>>>       > - if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll:
>>>      %d", errno));
>>>       > -#else
>>>       > - mgr->epoll_fd = -1;
>>>       > -#endif
>>>       > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > - // clang-format off
>>>       > - { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
>>>       > - // clang-format on
>>>       > -#elif MG_ENABLE_FREERTOS_TCP
>>>       > - mgr->ss = FreeRTOS_CreateSocketSet();
>>>       > -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
>>>       > - // Ignore SIGPIPE signal, so if client cancels the request, it
>>>       > - // won't kill the whole process.
>>>       > - signal(SIGPIPE, SIG_IGN);
>>>       > -#endif
>>>       > - mgr->dnstimeout = 3000;
>>>       > - mgr->dns4.url = "udp://8.8.8.8:53 <http://8.8.8.8:53>";
>>>       > - mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
>>>       > -}
>>>       > + tls->state =
>>>       > + c->is_client ? MG_TLS_STATE_CLIENT_START :
>>>      MG_TLS_STATE_SERVER_START;
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/rpc.c"
>>>       > -#endif
>>>       > + tls->skip_verification = opts->skip_verification;
>>>       > + tls->send.align = MG_IO_SIZE;
>>>       >
>>>       > + c->tls = tls;
>>>       > + c->is_tls = c->is_tls_hs = 1;
>>>       > + mg_sha256_init(&tls->sha256);
>>>       >
>>>       > -void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
>>>       > - void (*fn)(struct mg_rpc_req *), void *fn_data) {
>>>       > - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
>>>       > - if (rpc != NULL) {
>>>       > - rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data =
>>>      fn_data;
>>>       > - rpc->next = *head, *head = rpc;
>>>       > + // save hostname (client extension)
>>>       > + if (opts->name.len > 0) {
>>>       > + if (opts->name.len >= sizeof(tls->hostname) - 1) {
>>>       > + mg_error(c, "hostname too long");
>>>       > + }
>>>       > + strncpy((char *) tls->hostname, opts->name.buf,
>>>      sizeof(tls->hostname) - 1);
>>>       > + tls->hostname[opts->name.len] = 0;
>>>       > }
>>>       > -}
>>>       >
>>>       > -void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct
>>>      mg_rpc_req *)) {
>>>       > - struct mg_rpc *r;
>>>       > - while ((r = *head) != NULL) {
>>>       > - if (r->fn == fn || fn == NULL) {
>>>       > - *head = r->next;
>>>       > - free((void *) r->method.ptr);
>>>       > - free(r);
>>>       > - } else {
>>>       > - head = &(*head)->next;
>>>       > - }
>>>       > + if (c->is_client) {
>>>       > + tls->server_cert_der.buf = NULL;
>>>       > + return;
>>>       > }
>>>       > -}
>>>       >
>>>       > -static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str
>>>      method) {
>>>       > - struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
>>>       > - while (h != NULL && !mg_match(method, h->method, NULL)) h =
>>>      h->next;
>>>       > - if (h != NULL) {
>>>       > - r->rpc = h;
>>>       > - h->fn(r);
>>>       > + // parse PEM or DER certificate
>>>       > + if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"),
>>>      &tls->server_cert_der) <
>>>       > + 0) {
>>>       > + MG_ERROR(("Failed to load certificate"));
>>>       > + return;
>>>       > + }
>>>       > +
>>>       > + // parse PEM or DER EC key
>>>       > + if (opts->key.buf == NULL) {
>>>       > + mg_error(c, "certificate provided without a private key");
>>>       > + return;
>>>       > + }
>>>       > +
>>>       > + if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key)
>>>      == 0) {
>>>       > + if (key.len < 39) {
>>>       > + MG_ERROR(("EC private key too short"));
>>>       > + return;
>>>       > + }
>>>       > + // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
>>>       > + // 30 nn 02 01 01 04 20 [key] ...
>>>       > + if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
>>>       > + MG_ERROR(("EC private key: ASN.1 bad sequence"));
>>>       > + return;
>>>       > + }
>>>       > + if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
>>>       > + MG_ERROR(("EC private key: ASN.1 bad data"));
>>>       > + }
>>>       > + memmove(tls->server_key, key.buf + 7, 32);
>>>       > + free((void *) key.buf);
>>>       > + } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"),
>>>      &key) == 0) {
>>>       > + mg_error(c, "PKCS8 private key format is not supported");
>>>       > } else {
>>>       > - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len,
>>>      method.ptr);
>>>       > + mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
>>>       > }
>>>       > }
>>>       >
>>>       > -void mg_rpc_process(struct mg_rpc_req *r) {
>>>       > - int len, off = mg_json_get(r->frame, "$.method", &len);
>>>       > - if (off > 0 && r->frame.ptr[off] == '"') {
>>>       > - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1],
>>>      (size_t) len - 2);
>>>       > - mg_rpc_call(r, method);
>>>       > - } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
>>>       > - (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
>>>       > - mg_rpc_call(r, mg_str("")); // JSON response! call "" method
>>>      handler
>>>       > - } else {
>>>       > - mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len,
>>>      r->frame.ptr); // Invalid
>>>       > +void mg_tls_free(struct mg_connection *c) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + if (tls != NULL) {
>>>       > + mg_iobuf_free(&tls->send);
>>>       > + free((void *) tls->server_cert_der.buf);
>>>       > }
>>>       > + free(c->tls);
>>>       > + c->tls = NULL;
>>>       > }
>>>       >
>>>       > -void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list
>>>      *ap) {
>>>       > - int len, off = mg_json_get(r->frame, "$.id", &len);
>>>       > - if (off > 0) {
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
>>>       > - &r->frame.ptr[off], "result");
>>>       > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "}");
>>>       > +long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + long n = MG_IO_WAIT;
>>>       > + if (len > MG_IO_SIZE) len = MG_IO_SIZE;
>>>       > + mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
>>>       > + while (tls->send.len > 0 &&
>>>       > + (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
>>>       > + mg_iobuf_del(&tls->send, 0, (size_t) n);
>>>       > }
>>>       > + if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
>>>       > + return (long) len;
>>>       > }
>>>       >
>>>       > -void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - mg_rpc_vok(r, fmt, &ap);
>>>       > - va_end(ap);
>>>       > -}
>>>       > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > + int r = 0;
>>>       > + struct tls_data *tls = (struct tls_data *) c->tls;
>>>       > + size_t minlen;
>>>       >
>>>       > -void mg_rpc_verr(struct mg_rpc_req *r, int code, const char
>>>      *fmt, va_list *ap) {
>>>       > - int len, off = mg_json_get(r->frame, "$.id", &len);
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "{");
>>>       > - if (off > 0) {
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len,
>>>      &r->frame.ptr[off]);
>>>       > + r = mg_tls_recv_record(c);
>>>       > + if (r < 0) {
>>>       > + return r;
>>>       > }
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error",
>>>      "code", code,
>>>       > - "message");
>>>       > - mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
>>>       > - mg_xprintf(r->pfn, r->pfn_data, "}}");
>>>       > + if (tls->content_type != MG_TLS_APP_DATA) {
>>>       > + tls->recv.len = 0;
>>>       > + mg_tls_drop_record(c);
>>>       > + return MG_IO_WAIT;
>>>       > + }
>>>       > + minlen = len < tls->recv.len ? len : tls->recv.len;
>>>       > + memmove(buf, tls->recv.buf, minlen);
>>>       > + tls->recv.buf += minlen;
>>>       > + tls->recv.len -= minlen;
>>>       > + if (tls->recv.len == 0) {
>>>       > + mg_tls_drop_record(c);
>>>       > + }
>>>       > + return (long) minlen;
>>>       > }
>>>       >
>>>       > -void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt,
>>>      ...) {
>>>       > - va_list ap;
>>>       > - va_start(ap, fmt);
>>>       > - mg_rpc_verr(r, code, fmt, &ap);
>>>       > - va_end(ap);
>>>       > +size_t mg_tls_pending(struct mg_connection *c) {
>>>       > + return mg_tls_got_record(c) ? 1 : 0;
>>>       > }
>>>       >
>>>       > -static size_t print_methods(mg_pfn_t pfn, void *pfn_data,
>>>      va_list *ap) {
>>>       > - struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void
>>>      **);
>>>       > - size_t len = 0;
>>>       > - for (h = *head; h != NULL; h = h->next) {
>>>       > - if (h->method.len == 0) continue; // Ignore response handler
>>>       > - len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
>>>       > - (int) h->method.len, h->method.ptr);
>>>       > - }
>>>       > - return len;
>>>       > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > }
>>>       >
>>>       > -void mg_rpc_list(struct mg_rpc_req *r) {
>>>       > - mg_rpc_ok(r, "[%M]", print_methods, r->head);
>>>       > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > }
>>>       > +#endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/sha1.c"
>>>       > +#line 1 "src/tls_dummy.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_TLS == MG_TLS_NONE
>>>       > +void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > + (void) opts;
>>>       > + mg_error(c, "TLS is not enabled");
>>>       > +}
>>>       > +void mg_tls_handshake(struct mg_connection *c) {
>>>       > + (void) c;
>>>       > +}
>>>       > +void mg_tls_free(struct mg_connection *c) {
>>>       > + (void) c;
>>>       > +}
>>>       > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>>>       > +}
>>>       > +long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > + return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>>>       > +}
>>>       > +size_t mg_tls_pending(struct mg_connection *c) {
>>>       > + (void) c;
>>>       > + return 0;
>>>       > +}
>>>       > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > +}
>>>       > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > +}
>>>       > #endif
>>>       > -/* Copyright(c) By Steve Reid <st...@edmweb.com> */
>>>       > -/* 100% Public Domain */
>>>       >
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/tls_mbed.c"
>>>       > +#endif
>>>       >
>>>       >
>>>       > -union char64long16 {
>>>       > - unsigned char c[64];
>>>       > - uint32_t l[16];
>>>       > -};
>>>       >
>>>       > -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32
>>>      - (bits))))
>>>       > +#if MG_TLS == MG_TLS_MBED
>>>       >
>>>       > -static uint32_t blk0(union char64long16 *block, int i) {
>>>       > - if (MG_BIG_ENDIAN) {
>>>       > - } else {
>>>       > - block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
>>>       > - (rol(block->l[i], 8) & 0x00FF00FF);
>>>       > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>>>      0x03000000
>>>       > +#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
>>>       > +#else
>>>       > +#define MG_MBEDTLS_RNG_GET
>>>       > +#endif
>>>       > +
>>>       > +static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
>>>       > + mg_random(buf, len);
>>>       > + (void) ctx;
>>>       > + return 0;
>>>       > +}
>>>       > +
>>>       > +static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
>>>       > + int rc;
>>>       > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
>>>      return true;
>>>       > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
>>>       > + if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf,
>>>      str.len)) != 0) {
>>>       > + MG_ERROR(("cert err %#x", -rc));
>>>       > + return false;
>>>       > }
>>>       > - return block->l[i];
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h
>>>      define R0~R4) */
>>>       > -#undef blk
>>>       > -#undef R0
>>>       > -#undef R1
>>>       > -#undef R2
>>>       > -#undef R3
>>>       > -#undef R4
>>>       > +static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
>>>       > + int rc;
>>>       > + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*')
>>>      return true;
>>>       > + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL
>>>       > + if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len,
>>>      NULL,
>>>       > + 0 MG_MBEDTLS_RNG_GET)) != 0) {
>>>       > + MG_ERROR(("key err %#x", -rc));
>>>       > + return false;
>>>       > + }
>>>       > + return true;
>>>       > +}
>>>       >
>>>       > -#define blk(i) \
>>>       > - (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i +
>>>      8) & 15] ^ \
>>>       > - block->l[(i + 2) & 15] ^ block->l[i & 15], \
>>>       > - 1))
>>>       > -#define R0(v, w, x, y, z, i) \
>>>       > - z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v,
>>>      5); \
>>>       > - w = rol(w, 30);
>>>       > -#define R1(v, w, x, y, z, i) \
>>>       > - z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
>>>       > - w = rol(w, 30);
>>>       > -#define R2(v, w, x, y, z, i) \
>>>       > - z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
>>>       > - w = rol(w, 30);
>>>       > -#define R3(v, w, x, y, z, i) \
>>>       > - z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v,
>>>      5); \
>>>       > - w = rol(w, 30);
>>>       > -#define R4(v, w, x, y, z, i) \
>>>       > - z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
>>>       > - w = rol(w, 30);
>>>       > +void mg_tls_free(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + if (tls != NULL) {
>>>       > + mbedtls_ssl_free(&tls->ssl);
>>>       > + mbedtls_pk_free(&tls->pk);
>>>       > + mbedtls_x509_crt_free(&tls->ca);
>>>       > + mbedtls_x509_crt_free(&tls->cert);
>>>       > + mbedtls_ssl_config_free(&tls->conf);
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + mbedtls_ssl_ticket_free(&tls->ticket);
>>>       > +#endif
>>>       > + free(tls);
>>>       > + c->tls = NULL;
>>>       > + }
>>>       > +}
>>>       >
>>>       > -static void mg_sha1_transform(uint32_t state[5],
>>>       > - const unsigned char buffer[64]) {
>>>       > - uint32_t a, b, c, d, e;
>>>       > - union char64long16 block[1];
>>>       > +static int mg_net_send(void *ctx, const unsigned char *buf,
>>>      size_t len) {
>>>       > + long n = mg_io_send((struct mg_connection *) ctx, buf, len);
>>>       > + MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *)
>>>      ctx)->id, n, errno));
>>>       > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>>>       > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>>>       > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
>>>       > + return (int) n;
>>>       > +}
>>>       >
>>>       > - memcpy(block, buffer, 64);
>>>       > - a = state[0];
>>>       > - b = state[1];
>>>       > - c = state[2];
>>>       > - d = state[3];
>>>       > - e = state[4];
>>>       > - R0(a, b, c, d, e, 0);
>>>       > - R0(e, a, b, c, d, 1);
>>>       > - R0(d, e, a, b, c, 2);
>>>       > - R0(c, d, e, a, b, 3);
>>>       > - R0(b, c, d, e, a, 4);
>>>       > - R0(a, b, c, d, e, 5);
>>>       > - R0(e, a, b, c, d, 6);
>>>       > - R0(d, e, a, b, c, 7);
>>>       > - R0(c, d, e, a, b, 8);
>>>       > - R0(b, c, d, e, a, 9);
>>>       > - R0(a, b, c, d, e, 10);
>>>       > - R0(e, a, b, c, d, 11);
>>>       > - R0(d, e, a, b, c, 12);
>>>       > - R0(c, d, e, a, b, 13);
>>>       > - R0(b, c, d, e, a, 14);
>>>       > - R0(a, b, c, d, e, 15);
>>>       > - R1(e, a, b, c, d, 16);
>>>       > - R1(d, e, a, b, c, 17);
>>>       > - R1(c, d, e, a, b, 18);
>>>       > - R1(b, c, d, e, a, 19);
>>>       > - R2(a, b, c, d, e, 20);
>>>       > - R2(e, a, b, c, d, 21);
>>>       > - R2(d, e, a, b, c, 22);
>>>       > - R2(c, d, e, a, b, 23);
>>>       > - R2(b, c, d, e, a, 24);
>>>       > - R2(a, b, c, d, e, 25);
>>>       > - R2(e, a, b, c, d, 26);
>>>       > - R2(d, e, a, b, c, 27);
>>>       > - R2(c, d, e, a, b, 28);
>>>       > - R2(b, c, d, e, a, 29);
>>>       > - R2(a, b, c, d, e, 30);
>>>       > - R2(e, a, b, c, d, 31);
>>>       > - R2(d, e, a, b, c, 32);
>>>       > - R2(c, d, e, a, b, 33);
>>>       > - R2(b, c, d, e, a, 34);
>>>       > - R2(a, b, c, d, e, 35);
>>>       > - R2(e, a, b, c, d, 36);
>>>       > - R2(d, e, a, b, c, 37);
>>>       > - R2(c, d, e, a, b, 38);
>>>       > - R2(b, c, d, e, a, 39);
>>>       > - R3(a, b, c, d, e, 40);
>>>       > - R3(e, a, b, c, d, 41);
>>>       > - R3(d, e, a, b, c, 42);
>>>       > - R3(c, d, e, a, b, 43);
>>>       > - R3(b, c, d, e, a, 44);
>>>       > - R3(a, b, c, d, e, 45);
>>>       > - R3(e, a, b, c, d, 46);
>>>       > - R3(d, e, a, b, c, 47);
>>>       > - R3(c, d, e, a, b, 48);
>>>       > - R3(b, c, d, e, a, 49);
>>>       > - R3(a, b, c, d, e, 50);
>>>       > - R3(e, a, b, c, d, 51);
>>>       > - R3(d, e, a, b, c, 52);
>>>       > - R3(c, d, e, a, b, 53);
>>>       > - R3(b, c, d, e, a, 54);
>>>       > - R3(a, b, c, d, e, 55);
>>>       > - R3(e, a, b, c, d, 56);
>>>       > - R3(d, e, a, b, c, 57);
>>>       > - R3(c, d, e, a, b, 58);
>>>       > - R3(b, c, d, e, a, 59);
>>>       > - R4(a, b, c, d, e, 60);
>>>       > - R4(e, a, b, c, d, 61);
>>>       > - R4(d, e, a, b, c, 62);
>>>       > - R4(c, d, e, a, b, 63);
>>>       > - R4(b, c, d, e, a, 64);
>>>       > - R4(a, b, c, d, e, 65);
>>>       > - R4(e, a, b, c, d, 66);
>>>       > - R4(d, e, a, b, c, 67);
>>>       > - R4(c, d, e, a, b, 68);
>>>       > - R4(b, c, d, e, a, 69);
>>>       > - R4(a, b, c, d, e, 70);
>>>       > - R4(e, a, b, c, d, 71);
>>>       > - R4(d, e, a, b, c, 72);
>>>       > - R4(c, d, e, a, b, 73);
>>>       > - R4(b, c, d, e, a, 74);
>>>       > - R4(a, b, c, d, e, 75);
>>>       > - R4(e, a, b, c, d, 76);
>>>       > - R4(d, e, a, b, c, 77);
>>>       > - R4(c, d, e, a, b, 78);
>>>       > - R4(b, c, d, e, a, 79);
>>>       > - state[0] += a;
>>>       > - state[1] += b;
>>>       > - state[2] += c;
>>>       > - state[3] += d;
>>>       > - state[4] += e;
>>>       > - /* Erase working structures. The order of operations is important,
>>>       > - * used to ensure that compiler doesn't optimize those out. */
>>>       > - memset(block, 0, sizeof(block));
>>>       > - a = b = c = d = e = 0;
>>>       > - (void) a;
>>>       > - (void) b;
>>>       > - (void) c;
>>>       > - (void) d;
>>>       > - (void) e;
>>>       > +static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
>>>       > + long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
>>>       > + MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>>>       > + if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>>>       > + if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>>>       > + if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
>>>       > + return (int) n;
>>>       > +}
>>>       > +
>>>       > +void mg_tls_handshake(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + int rc = mbedtls_ssl_handshake(&tls->ssl);
>>>       > + if (rc == 0) { // Success
>>>       > + MG_DEBUG(("%lu success", c->id));
>>>       > + c->is_tls_hs = 0;
>>>       > + mg_call(c, MG_EV_TLS_HS, NULL);
>>>       > + } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
>>>       > + rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
>>>       > + MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
>>>      c->is_connecting,
>>>       > + c->is_tls_hs, rc, -rc));
>>>       > + } else {
>>>       > + mg_error(c, "TLS handshake: -%#x", -rc); // Error
>>>       > + }
>>>       > }
>>>       >
>>>       > -void mg_sha1_init(mg_sha1_ctx *context) {
>>>       > - context->state[0] = 0x67452301;
>>>       > - context->state[1] = 0xEFCDAB89;
>>>       > - context->state[2] = 0x98BADCFE;
>>>       > - context->state[3] = 0x10325476;
>>>       > - context->state[4] = 0xC3D2E1F0;
>>>       > - context->count[0] = context->count[1] = 0;
>>>       > +static void debug_cb(void *c, int lev, const char *s, int n,
>>>      const char *s2) {
>>>       > + n = (int) strlen(s2) - 1;
>>>       > + MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev,
>>>      n, s2));
>>>       > + (void) s;
>>>       > }
>>>       >
>>>       > -void mg_sha1_update(mg_sha1_ctx *context, const unsigned char
>>>      *data,
>>>       > - size_t len) {
>>>       > - size_t i, j;
>>>       > +void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>>>       > + int rc = 0;
>>>       > + c->tls = tls;
>>>       > + if (c->tls == NULL) {
>>>       > + mg_error(c, "TLS OOM");
>>>       > + goto fail;
>>>       > + }
>>>       > + if (c->is_listening) goto fail;
>>>       > + MG_DEBUG(("%lu Setting TLS", c->id));
>>>       > + MG_PROF_ADD(c, "mbedtls_init_start");
>>>       > +#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>>>      0x03000000 && \
>>>       > + defined(MBEDTLS_PSA_CRYPTO_C)
>>>       > + psa_crypto_init(); //
>>>      https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711 <https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711>
>>>       > +#endif
>>>       > + mbedtls_ssl_init(&tls->ssl);
>>>       > + mbedtls_ssl_config_init(&tls->conf);
>>>       > + mbedtls_x509_crt_init(&tls->ca);
>>>       > + mbedtls_x509_crt_init(&tls->cert);
>>>       > + mbedtls_pk_init(&tls->pk);
>>>       > + mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
>>>       > +#if defined(MG_MBEDTLS_DEBUG_LEVEL)
>>>       > + mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
>>>       > +#endif
>>>       > + if ((rc = mbedtls_ssl_config_defaults(
>>>       > + &tls->conf,
>>>       > + c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
>>>       > + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
>>>       > + mg_error(c, "tls defaults %#x", -rc);
>>>       > + goto fail;
>>>       > + }
>>>       > + mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
>>>       >
>>>       > - j = context->count[0];
>>>       > - if ((context->count[0] += (uint32_t) len << 3) < j)
>>>      context->count[1]++;
>>>       > - context->count[1] += (uint32_t) (len >> 29);
>>>       > - j = (j >> 3) & 63;
>>>       > - if ((j + len) > 63) {
>>>       > - memcpy(&context->buffer[j], data, (i = 64 - j));
>>>       > - mg_sha1_transform(context->state, context->buffer);
>>>       > - for (; i + 63 < len; i += 64) {
>>>       > - mg_sha1_transform(context->state, &data[i]);
>>>       > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
>>>       > + // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on
>>>      client side
>>>       > + // See https://github.com/Mbed-TLS/mbedtls/issues/7075
>>>      <https://github.com/Mbed-TLS/mbedtls/issues/7075>
>>>       > + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
>>>       > + } else {
>>>       > + if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
>>>       > + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
>>>       > + if (c->is_client && opts->name.buf != NULL && opts->name.buf[0]
>>>      != '\0') {
>>>       > + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
>>>       > + mbedtls_ssl_set_hostname(&tls->ssl, host);
>>>       > + MG_DEBUG(("%lu hostname verification: %s", c->id, host));
>>>       > + free(host);
>>>       > }
>>>       > - j = 0;
>>>       > - } else
>>>       > - i = 0;
>>>       > - memcpy(&context->buffer[j], &data[i], len - i);
>>>       > + mbedtls_ssl_conf_authmode(&tls->conf,
>>>      MBEDTLS_SSL_VERIFY_REQUIRED);
>>>       > + }
>>>       > + if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
>>>       > + if (!mg_load_key(opts->key, &tls->pk)) goto fail;
>>>       > + if (tls->cert.version &&
>>>       > + (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert,
>>>      &tls->pk)) != 0) {
>>>       > + mg_error(c, "own cert %#x", -rc);
>>>       > + goto fail;
>>>       > + }
>>>       > +
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + mbedtls_ssl_conf_session_tickets_cb(
>>>       > + &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
>>>       > + &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
>>>       > +#endif
>>>       > +
>>>       > + if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
>>>       > + mg_error(c, "setup err %#x", -rc);
>>>       > + goto fail;
>>>       > + }
>>>       > + c->is_tls = 1;
>>>       > + c->is_tls_hs = 1;
>>>       > + mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
>>>       > + MG_PROF_ADD(c, "mbedtls_init_end");
>>>       > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>>>      0) {
>>>       > + mg_tls_handshake(c);
>>>       > + }
>>>       > + return;
>>>       > +fail:
>>>       > + mg_tls_free(c);
>>>       > }
>>>       >
>>>       > -void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx
>>>      *context) {
>>>       > - unsigned i;
>>>       > - unsigned char finalcount[8], c;
>>>       > +size_t mg_tls_pending(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
>>>       > +}
>>>       >
>>>       > - for (i = 0; i < 8; i++) {
>>>       > - finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 :
>>>      1)] >>
>>>       > - ((3 - (i & 3)) * 8)) &
>>>       > - 255);
>>>       > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
>>>       > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>>>      MBEDTLS_ERR_SSL_WANT_WRITE)
>>>       > + return MG_IO_WAIT;
>>>       > +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
>>>       > + if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
>>>       > + return MG_IO_WAIT;
>>>       > }
>>>       > - c = 0200;
>>>       > - mg_sha1_update(context, &c, 1);
>>>       > - while ((context->count[0] & 504) != 448) {
>>>       > - c = 0000;
>>>       > - mg_sha1_update(context, &c, 1);
>>>       > +#endif
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
>>>       > + if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>>>      MBEDTLS_ERR_SSL_WANT_WRITE)
>>>       > + return MG_IO_WAIT;
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > +}
>>>       > +
>>>       > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>>>       > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1,
>>>      sizeof(*ctx));
>>>       > + if (ctx == NULL) {
>>>       > + MG_ERROR(("TLS context init OOM"));
>>>       > + } else {
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + int rc;
>>>       > + mbedtls_ssl_ticket_init(&ctx->tickets);
>>>       > + if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng,
>>>      NULL,
>>>       > + MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
>>>       > + 0) {
>>>       > + MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
>>>       > + }
>>>       > +#endif
>>>       > + mgr->tls_ctx = ctx;
>>>       > }
>>>       > - mg_sha1_update(context, finalcount, 8);
>>>       > - for (i = 0; i < 20; i++) {
>>>       > - digest[i] =
>>>       > - (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) *
>>>      8)) & 255);
>>>       > +}
>>>       > +
>>>       > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>>>       > + struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
>>>       > + if (ctx != NULL) {
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + mbedtls_ssl_ticket_free(&ctx->tickets);
>>>       > +#endif
>>>       > + free(ctx);
>>>       > + mgr->tls_ctx = NULL;
>>>       > }
>>>       > - memset(context, '\0', sizeof(*context));
>>>       > - memset(&finalcount, '\0', sizeof(finalcount));
>>>       > }
>>>       > +#endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/sntp.c"
>>>       > +#line 1 "src/tls_openssl.c"
>>>       > #endif
>>>       >
>>>       >
>>>       >
>>>       > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>>>       >
>>>       > +static int tls_err_cb(const char *s, size_t len, void *c) {
>>>       > + int n = (int) len - 1;
>>>       > + MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
>>>       > + return 0; // undocumented
>>>       > +}
>>>       >
>>>       > +static int mg_tls_err(struct mg_connection *c, struct mg_tls
>>>      *tls, int res) {
>>>       > + int err = SSL_get_error(tls->ssl, res);
>>>       > + // We've just fetched the last error from the queue.
>>>       > + // Now we need to clear the error queue. If we do not, then the
>>>      following
>>>       > + // can happen (actually reported):
>>>       > + // - A new connection is accept()-ed with cert error (e.g.
>>>      self-signed cert)
>>>       > + // - Since all accept()-ed connections share listener's context,
>>>       > + // - *ALL* SSL accepted connection report read error on the
>>>      next poll cycle.
>>>       > + // Thus a single errored connection can close all the rest,
>>>      unrelated ones.
>>>       > + // Clearing the error keeps the shared SSL_CTX in an OK state.
>>>       >
>>>       > -#define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds
>>>       > -#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1
>>>       > + if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
>>>       > + ERR_clear_error();
>>>       > + if (err == SSL_ERROR_WANT_READ) return 0;
>>>       > + if (err == SSL_ERROR_WANT_WRITE) return 0;
>>>       > + return err;
>>>       > +}
>>>       >
>>>       > -static int64_t gettimestamp(const uint32_t *data) {
>>>       > - uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
>>>       > - if (sec) sec -= SNTP_TIME_OFFSET;
>>>       > - return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC
>>>      * 1000.0);
>>>       > +static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
>>>       > + BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
>>>       > + STACK_OF(X509_INFO) *certs =
>>>       > + bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
>>>       > + if (bio) BIO_free(bio);
>>>       > + return certs;
>>>       > }
>>>       >
>>>       > -int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
>>>       > - int64_t res = -1;
>>>       > - int mode = len > 0 ? buf[0] & 7 : 0;
>>>       > - int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
>>>       > - if (len < 48) {
>>>       > - MG_ERROR(("%s", "corrupt packet"));
>>>       > - } else if (mode != 4 && mode != 5) {
>>>       > - MG_ERROR(("%s", "not a server reply"));
>>>       > - } else if (buf[1] == 0) {
>>>       > - MG_ERROR(("%s", "server sent a kiss of death"));
>>>       > - } else if (version == 4 || version == 3) {
>>>       > - // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
>>>       > - int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
>>>       > - int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
>>>       > - int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
>>>       > - int64_t t3 = (int64_t) mg_millis();
>>>       > - int64_t delta = (t3 - t0) - (t2 - t1);
>>>       > - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3,
>>>      delta));
>>>       > - res = t2 + delta / 2;
>>>       > - } else {
>>>       > - MG_ERROR(("unexpected version: %d", version));
>>>       > +static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) *
>>>      certs) {
>>>       > + X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
>>>       > + for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
>>>       > + X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
>>>       > + if (cert_info->x509 && !X509_STORE_add_cert(cert_store,
>>>      cert_info->x509))
>>>       > + return false;
>>>       > + }
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > +static EVP_PKEY *load_key(struct mg_str s) {
>>>       > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
>>>       > + EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0,
>>>      NULL) : NULL;
>>>       > + if (bio) BIO_free(bio);
>>>       > + return key;
>>>       > +}
>>>       > +
>>>       > +static X509 *load_cert(struct mg_str s) {
>>>       > + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
>>>       > + X509 *cert = bio == NULL ? NULL
>>>       > + : s.buf[0] == '-'
>>>       > + ? PEM_read_bio_X509(bio, NULL, NULL, NULL) // PEM
>>>       > + : d2i_X509_bio(bio, NULL); // DER
>>>       > + if (bio) BIO_free(bio);
>>>       > + return cert;
>>>       > +}
>>>       > +
>>>       > +static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
>>>       > + long ret = 0;
>>>       > + if (cmd == BIO_CTRL_PUSH) ret = 1;
>>>       > + if (cmd == BIO_CTRL_POP) ret = 1;
>>>       > + if (cmd == BIO_CTRL_FLUSH) ret = 1;
>>>       > +#if MG_TLS == MG_TLS_OPENSSL
>>>       > + if (cmd == BIO_C_SET_NBIO) ret = 1;
>>>       > +#endif
>>>       > + // MG_DEBUG(("%d -> %ld", cmd, ret));
>>>       > + (void) b, (void) cmd, (void) larg, (void) pargs;
>>>       > + return ret;
>>>       > +}
>>>       > +
>>>       > +static int mg_bio_read(BIO *bio, char *buf, int len) {
>>>       > + struct mg_connection *c = (struct mg_connection *)
>>>      BIO_get_data(bio);
>>>       > + long res = mg_io_recv(c, buf, (size_t) len);
>>>       > + // MG_DEBUG(("%p %d %ld", buf, len, res));
>>>       > + len = res > 0 ? (int) res : -1;
>>>       > + if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +static int mg_bio_write(BIO *bio, const char *buf, int len) {
>>>       > + struct mg_connection *c = (struct mg_connection *)
>>>      BIO_get_data(bio);
>>>       > + long res = mg_io_send(c, buf, (size_t) len);
>>>       > + // MG_DEBUG(("%p %d %ld", buf, len, res));
>>>       > + len = res > 0 ? (int) res : -1;
>>>       > + if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
>>>       > + return len;
>>>       > +}
>>>       > +
>>>       > +void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>>>       > + const char *id = "mongoose";
>>>       > + static unsigned char s_initialised = 0;
>>>       > + BIO *bio = NULL;
>>>       > + int rc;
>>>       > +
>>>       > + if (tls == NULL) {
>>>       > + mg_error(c, "TLS OOM");
>>>       > + goto fail;
>>>       > + }
>>>       > +
>>>       > + if (!s_initialised) {
>>>       > + SSL_library_init();
>>>       > + s_initialised++;
>>>       > + }
>>>       > + MG_DEBUG(("%lu Setting TLS", c->id));
>>>       > + tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
>>>       > + : SSL_CTX_new(SSLv23_server_method());
>>>       > + if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
>>>       > + mg_error(c, "SSL_new");
>>>       > + goto fail;
>>>       > + }
>>>       > + SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
>>>       > + (unsigned) strlen(id));
>>>       > + // Disable deprecated protocols
>>>       > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
>>>       > + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
>>>       > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
>>>       > + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
>>>       > +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
>>>       > + SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
>>>       > +#endif
>>>       > +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
>>>       > + SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
>>>       > +#endif
>>>       > +
>>>       > +#if MG_TLS == MG_TLS_WOLFSSL &&
>>>      !defined(OPENSSL_COMPATIBLE_DEFAULTS)
>>>       > + if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
>>>       > + // Older versions require that either the CA is loaded or
>>>      SSL_VERIFY_NONE
>>>       > + // explicitly set
>>>       > + SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
>>>       > + }
>>>       > +#endif
>>>       > + if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
>>>       > + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
>>>      SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
>>>       > + NULL);
>>>       > + STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
>>>       > + rc = add_ca_certs(tls->ctx, certs);
>>>       > + sk_X509_INFO_pop_free(certs, X509_INFO_free);
>>>       > + if (!rc) {
>>>       > + mg_error(c, "CA err");
>>>       > + goto fail;
>>>       > + }
>>>       > + }
>>>       > + if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
>>>       > + X509 *cert = load_cert(opts->cert);
>>>       > + rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
>>>       > + X509_free(cert);
>>>       > + if (cert == NULL || rc != 1) {
>>>       > + mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
>>>       > + goto fail;
>>>       > + }
>>>       > + }
>>>       > + if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
>>>       > + EVP_PKEY *key = load_key(opts->key);
>>>       > + rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
>>>       > + EVP_PKEY_free(key);
>>>       > + if (key == NULL || rc != 1) {
>>>       > + mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
>>>       > + goto fail;
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
>>>       > +#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER >
>>>      0x10002000L
>>>       > + (void) SSL_set_ecdh_auto(tls->ssl, 1);
>>>       > +#endif
>>>       > +#if OPENSSL_VERSION_NUMBER >= 0x10100000L
>>>       > + if (opts->name.len > 0) {
>>>       > + char *s = mg_mprintf("%.*s", (int) opts->name.len,
>>>      opts->name.buf);
>>>       > +#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >=
>>>      0x05005002
>>>       > + SSL_set1_host(tls->ssl, s);
>>>       > +#else
>>>       > + X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
>>>       > +#endif
>>>       > + SSL_set_tlsext_host_name(tls->ssl, s);
>>>       > + free(s);
>>>       > }
>>>       > - return res;
>>>       > -}
>>>       > +#endif
>>>       > +#if MG_TLS == MG_TLS_WOLFSSL
>>>       > + tls->bm = BIO_meth_new(0, "bio_mg");
>>>       > +#else
>>>       > + tls->bm = BIO_meth_new(BIO_get_new_index() |
>>>      BIO_TYPE_SOURCE_SINK, "bio_mg");
>>>       > +#endif
>>>       > + BIO_meth_set_write(tls->bm, mg_bio_write);
>>>       > + BIO_meth_set_read(tls->bm, mg_bio_read);
>>>       > + BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
>>>       >
>>>       > -static void sntp_cb(struct mg_connection *c, int ev, void *evd,
>>>      void *fnd) {
>>>       > - if (ev == MG_EV_READ) {
>>>       > - int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
>>>       > - if (milliseconds > 0) {
>>>       > - MG_INFO(("%lu got time: %lld ms from epoch", c->id,
>>>      milliseconds));
>>>       > - mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
>>>       > - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
>>>       > - (unsigned) (milliseconds % 1000)));
>>>       > - }
>>>       > - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer
>>>       > - } else if (ev == MG_EV_CONNECT) {
>>>       > - mg_sntp_request(c);
>>>       > - } else if (ev == MG_EV_CLOSE) {
>>>       > + bio = BIO_new(tls->bm);
>>>       > + BIO_set_data(bio, c);
>>>       > + SSL_set_bio(tls->ssl, bio, bio);
>>>       > +
>>>       > + c->tls = tls;
>>>       > + c->is_tls = 1;
>>>       > + c->is_tls_hs = 1;
>>>       > + if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>>>      0) {
>>>       > + mg_tls_handshake(c);
>>>       > }
>>>       > - (void) fnd;
>>>       > - (void) evd;
>>>       > + MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
>>>      "client"));
>>>       > + return;
>>>       > +fail:
>>>       > + free(tls);
>>>       > }
>>>       >
>>>       > -void mg_sntp_request(struct mg_connection *c) {
>>>       > - if (c->is_resolving) {
>>>       > - MG_ERROR(("%lu wait until resolved", c->id));
>>>       > +void mg_tls_handshake(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + int rc = c->is_client ? SSL_connect(tls->ssl) :
>>>      SSL_accept(tls->ssl);
>>>       > + if (rc == 1) {
>>>       > + MG_DEBUG(("%lu success", c->id));
>>>       > + c->is_tls_hs = 0;
>>>       > + mg_call(c, MG_EV_TLS_HS, NULL);
>>>       > } else {
>>>       > - int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98
>>>       > - uint8_t buf[48] = {0};
>>>       > - uint32_t *t = (uint32_t *) &buf[40];
>>>       > - double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
>>>       > - buf[0] = (0 << 6) | (4 << 3) | 3;
>>>       > - t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
>>>       > - t[1] = mg_htonl((uint32_t) frac);
>>>       > - mg_send(c, buf, sizeof(buf));
>>>       > + int code = mg_tls_err(c, tls, rc);
>>>       > + if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
>>>       > }
>>>       > }
>>>       >
>>>       > -struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > - mg_event_handler_t fn, void *fnd) {
>>>       > - struct mg_connection *c = NULL;
>>>       > - if (url == NULL) url = "udp://time.google.com:123
>>>      <http://time.google.com:123>";
>>>       > - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
>>>       > - return c;
>>>       > +void mg_tls_free(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + if (tls == NULL) return;
>>>       > + SSL_free(tls->ssl);
>>>       > + SSL_CTX_free(tls->ctx);
>>>       > + BIO_meth_free(tls->bm);
>>>       > + free(tls);
>>>       > + c->tls = NULL;
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/sock.c"
>>>       > -#endif
>>>       > -
>>>       > +size_t mg_tls_pending(struct mg_connection *c) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
>>>       > +}
>>>       >
>>>       > +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + int n = SSL_read(tls->ssl, buf, (int) len);
>>>       > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > +}
>>>       >
>>>       > +long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > + struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > + int n = SSL_write(tls->ssl, buf, (int) len);
>>>       > + if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
>>>       > + if (n <= 0) return MG_IO_ERR;
>>>       > + return n;
>>>       > +}
>>>       >
>>>       > +void mg_tls_ctx_init(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > +}
>>>       >
>>>       > +void mg_tls_ctx_free(struct mg_mgr *mgr) {
>>>       > + (void) mgr;
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/tls_uecc.c"
>>>       > +#endif
>>>       > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
>>>      2-clause license. */
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_SOCKET
>>>       > +#if MG_TLS == MG_TLS_BUILTIN
>>>       >
>>>       > -#ifndef closesocket
>>>       > -#define closesocket(x) close(x)
>>>       > +#ifndef MG_UECC_RNG_MAX_TRIES
>>>       > +#define MG_UECC_RNG_MAX_TRIES 64
>>>       > #endif
>>>       >
>>>       > -#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
>>>       > -#define S2PTR(s_) ((void *) (size_t) (s_))
>>>       > -
>>>       > -#ifndef MSG_NONBLOCKING
>>>       > -#define MSG_NONBLOCKING 0
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +#define MG_UECC_VLI_API
>>>       > +#else
>>>       > +#define MG_UECC_VLI_API static
>>>       > #endif
>>>       >
>>>       > -#ifndef AF_INET6
>>>       > -#define AF_INET6 10
>>>       > +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM ==
>>>      mg_uecc_arm) || \
>>>       > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb) || \
>>>       > + (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
>>>       > +#define CONCATX(a, ...) a##__VA_ARGS__
>>>       > +#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
>>>       > +
>>>       > +#define STRX(a) #a
>>>       > +#define STR(a) STRX(a)
>>>       > +
>>>       > +#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
>>>       > +#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
>>>       > +#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
>>>       > +#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
>>>       > +#define EVAL4(...) __VA_ARGS__
>>>       > +
>>>       > +#define DEC_1 0
>>>       > +#define DEC_2 1
>>>       > +#define DEC_3 2
>>>       > +#define DEC_4 3
>>>       > +#define DEC_5 4
>>>       > +#define DEC_6 5
>>>       > +#define DEC_7 6
>>>       > +#define DEC_8 7
>>>       > +#define DEC_9 8
>>>       > +#define DEC_10 9
>>>       > +#define DEC_11 10
>>>       > +#define DEC_12 11
>>>       > +#define DEC_13 12
>>>       > +#define DEC_14 13
>>>       > +#define DEC_15 14
>>>       > +#define DEC_16 15
>>>       > +#define DEC_17 16
>>>       > +#define DEC_18 17
>>>       > +#define DEC_19 18
>>>       > +#define DEC_20 19
>>>       > +#define DEC_21 20
>>>       > +#define DEC_22 21
>>>       > +#define DEC_23 22
>>>       > +#define DEC_24 23
>>>       > +#define DEC_25 24
>>>       > +#define DEC_26 25
>>>       > +#define DEC_27 26
>>>       > +#define DEC_28 27
>>>       > +#define DEC_29 28
>>>       > +#define DEC_30 29
>>>       > +#define DEC_31 30
>>>       > +#define DEC_32 31
>>>       > +
>>>       > +#define DEC(N) CONCAT(DEC_, N)
>>>       > +
>>>       > +#define SECOND_ARG(_, val, ...) val
>>>       > +#define SOME_CHECK_0 ~, 0
>>>       > +#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
>>>       > +#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
>>>       > +
>>>       > +#define EMPTY(...)
>>>       > +#define DEFER(...) __VA_ARGS__ EMPTY()
>>>       > +
>>>       > +#define REPEAT_NAME_0() REPEAT_0
>>>       > +#define REPEAT_NAME_SOME() REPEAT_SOME
>>>       > +#define REPEAT_0(...)
>>>       > +#define REPEAT_SOME(N, stuff) \
>>>       > + DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff)
>>>      stuff
>>>       > +#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
>>>       > +
>>>       > +#define REPEATM_NAME_0() REPEATM_0
>>>       > +#define REPEATM_NAME_SOME() REPEATM_SOME
>>>       > +#define REPEATM_0(...)
>>>       > +#define REPEATM_SOME(N, macro) \
>>>       > + macro(N) DEFER(CONCAT(REPEATM_NAME_,
>>>      SOME_OR_0(DEC(N))))()(DEC(N), macro)
>>>       > +#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
>>>       > #endif
>>>       >
>>>       > -union usa {
>>>       > - struct sockaddr sa;
>>>       > - struct sockaddr_in sin;
>>>       > -#if MG_ENABLE_IPV6
>>>       > - struct sockaddr_in6 sin6;
>>>       > -#endif
>>>       > -};
>>>       > +//
>>>       >
>>>       > -static socklen_t tousa(struct mg_addr *a, union usa *usa) {
>>>       > - socklen_t len = sizeof(usa->sin);
>>>       > - memset(usa, 0, sizeof(*usa));
>>>       > - usa->sin.sin_family = AF_INET;
>>>       > - usa->sin.sin_port = a->port;
>>>       > - *(uint32_t *) &usa->sin.sin_addr = a->ip;
>>>       > -#if MG_ENABLE_IPV6
>>>       > - if (a->is_ip6) {
>>>       > - usa->sin.sin_family = AF_INET6;
>>>       > - usa->sin6.sin6_port = a->port;
>>>       > - memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
>>>       > - len = sizeof(usa->sin6);
>>>       > - }
>>>       > +#if (MG_UECC_WORD_SIZE == 1)
>>>       > +#if MG_UECC_SUPPORTS_secp160r1
>>>       > +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
>>>       > #endif
>>>       > - return len;
>>>       > -}
>>>       > -
>>>       > -static void tomgaddr(union usa *usa, struct mg_addr *a, bool
>>>      is_ip6) {
>>>       > - a->is_ip6 = is_ip6;
>>>       > - a->port = usa->sin.sin_port;
>>>       > - memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
>>>       > -#if MG_ENABLE_IPV6
>>>       > - if (is_ip6) {
>>>       > - memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
>>>       > - a->port = usa->sin6.sin6_port;
>>>       > - }
>>>       > +#if MG_UECC_SUPPORTS_secp192r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 24
>>>       > #endif
>>>       > -}
>>>       > -
>>>       > -static bool mg_sock_would_block(void) {
>>>       > - int err = MG_SOCKET_ERRNO;
>>>       > - return err == EINPROGRESS || err == EWOULDBLOCK
>>>       > -#ifndef WINCE
>>>       > - || err == EAGAIN || err == EINTR
>>>       > +#if MG_UECC_SUPPORTS_secp224r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 28
>>>       > #endif
>>>       > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > - || err == WSAEINTR || err == WSAEWOULDBLOCK
>>>       > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 32
>>>       > #endif
>>>       > - ;
>>>       > -}
>>>       > -
>>>       > -static bool mg_sock_conn_reset(void) {
>>>       > - int err = MG_SOCKET_ERRNO;
>>>       > -#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > - return err == WSAECONNRESET;
>>>       > -#else
>>>       > - return err == EPIPE || err == ECONNRESET;
>>>       > +#elif (MG_UECC_WORD_SIZE == 4)
>>>       > +#if MG_UECC_SUPPORTS_secp160r1
>>>       > +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
>>>       > #endif
>>>       > -}
>>>       > +#if MG_UECC_SUPPORTS_secp192r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 6
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp224r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 7
>>>       > +#endif
>>>       > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 8
>>>       > +#endif
>>>       > +#elif (MG_UECC_WORD_SIZE == 8)
>>>       > +#if MG_UECC_SUPPORTS_secp160r1
>>>       > +#define MG_UECC_MAX_WORDS 3
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp192r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 3
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp224r1
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 4
>>>       > +#endif
>>>       > +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
>>>       > +#undef MG_UECC_MAX_WORDS
>>>       > +#define MG_UECC_MAX_WORDS 4
>>>       > +#endif
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +
>>>       > +#define BITS_TO_WORDS(num_bits) \
>>>       > + ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
>>>       > + (MG_UECC_WORD_SIZE * 8)))
>>>       > +#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
>>>       > +
>>>       > +struct MG_UECC_Curve_t {
>>>       > + wordcount_t num_words;
>>>       > + wordcount_t num_bytes;
>>>       > + bitcount_t num_n_bits;
>>>       > + mg_uecc_word_t p[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t n[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
>>>       > + mg_uecc_word_t b[MG_UECC_MAX_WORDS];
>>>       > + void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
>>>       > +#endif
>>>       > + void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
>>>       > + MG_UECC_Curve curve);
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t
>>>      *product);
>>>       > +#endif
>>>       > +};
>>>       >
>>>       > -static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
>>>       > - union usa usa;
>>>       > - socklen_t n = sizeof(usa);
>>>       > - if (getsockname(fd, &usa.sa <http://usa.sa>, &n) == 0) {
>>>       > - tomgaddr(&usa, addr, n != sizeof(usa.sin));
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > +static void bcopy(uint8_t *dst, const uint8_t *src, unsigned
>>>      num_bytes) {
>>>       > + while (0 != num_bytes) {
>>>       > + num_bytes--;
>>>       > + dst[num_bytes] = src[num_bytes];
>>>       > }
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -static void iolog(struct mg_connection *c, char *buf, long n,
>>>      bool r) {
>>>       > - if (n == MG_IO_WAIT) {
>>>       > - // Do nothing
>>>       > - } else if (n <= 0) {
>>>       > - c->is_closing = 1; // Termination. Don't call mg_error(): #1529
>>>       > - } else if (n > 0) {
>>>       > - if (c->is_hexdumping) {
>>>       > - union usa usa;
>>>       > - socklen_t slen = sizeof(usa.sin);
>>>       > - if (getsockname(FD(c), &usa.sa <http://usa.sa>, &slen) < 0)
>>>      (void) 0; // Ignore result
>>>       > - MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
>>>       > - r ? "<-" : "->", 4, &c->rem.ip, n));
>>>       > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
>>>      *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words);
>>>       >
>>>       > - mg_hexdump(buf, (size_t) n);
>>>       > - }
>>>       > - if (r) {
>>>       > - c->recv.len += (size_t) n;
>>>       > - mg_call(c, MG_EV_READ, &n);
>>>       > - } else {
>>>       > - mg_iobuf_del(&c->send, 0, (size_t) n);
>>>       > - // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
>>>       > - if (c->send.len == 0) {
>>>       > - MG_EPOLL_MOD(c, 0);
>>>       > - }
>>>       > - mg_call(c, MG_EV_WRITE, &n);
>>>       > - }
>>>       > - }
>>>       > -}
>>>       > +#if (MG_UECC_PLATFORM == mg_uecc_arm || \
>>>       > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
>>>       > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
>>>       >
>>>       > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > - long n;
>>>       > - if (c->is_udp) {
>>>       > - union usa usa;
>>>       > - socklen_t slen = tousa(&c->rem, &usa);
>>>       > - n = sendto(FD(c), (char *) buf, len, 0, &usa.sa
>>>      <http://usa.sa>, slen);
>>>       > - if (n > 0) setlocaddr(FD(c), &c->loc);
>>>       > - } else {
>>>       > - n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>>>       > -#if MG_ARCH == MG_ARCH_RTX
>>>       > - if (n == EWOULDBLOCK) return MG_IO_WAIT;
>>>       > #endif
>>>       > - }
>>>       > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
>>>       > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > -}
>>>       >
>>>       > -bool mg_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > - if (c->is_udp) {
>>>       > - long n = mg_io_send(c, buf, len);
>>>       > - MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int)
>>>      c->send.len,
>>>       > - (int) c->recv.len, n, MG_SOCKET_ERRNO));
>>>       > - iolog(c, (char *) buf, n, false);
>>>       > - return n > 0;
>>>       > - } else {
>>>       > - return mg_iobuf_add(&c->send, c->send.len, buf, len);
>>>       > - }
>>>       > -}
>>>       > +#if (MG_UECC_PLATFORM == mg_uecc_avr)
>>>       >
>>>       > -static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
>>>       > -#if defined(MG_CUSTOM_NONBLOCK)
>>>       > - MG_CUSTOM_NONBLOCK(fd);
>>>       > -#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
>>>       > - unsigned long on = 1;
>>>       > - ioctlsocket(fd, FIONBIO, &on);
>>>       > -#elif MG_ARCH == MG_ARCH_RTX
>>>       > - unsigned long on = 1;
>>>       > - ioctlsocket(fd, FIONBIO, &on);
>>>       > -#elif MG_ENABLE_FREERTOS_TCP
>>>       > - const BaseType_t off = 0;
>>>       > - if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off))
>>>      != 0) (void) 0;
>>>       > - if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off))
>>>      != 0) (void) 0;
>>>       > -#elif MG_ENABLE_LWIP
>>>       > - lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
>>>       > -#elif MG_ARCH == MG_ARCH_AZURERTOS
>>>       > - fcntl(fd, F_SETFL, O_NONBLOCK);
>>>       > -#elif MG_ARCH == MG_ARCH_TIRTOS
>>>       > - int val = 0;
>>>       > - setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
>>>       > - // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
>>>       > - int sz = sizeof(val);
>>>       > - getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
>>>       > - val /= 2; // set send low-water mark at half send buffer size
>>>       > - setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
>>>       > -#else
>>>       > - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); //
>>>      Non-blocking mode
>>>       > - fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec
>>>       > #endif
>>>       > -}
>>>       >
>>>       > -bool mg_open_listener(struct mg_connection *c, const char *url) {
>>>       > - MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
>>>       > - bool success = false;
>>>       > - c->loc.port = mg_htons(mg_url_port(url));
>>>       > - if (!mg_aton(mg_url_host(url), &c->loc)) {
>>>       > - MG_ERROR(("invalid listening URL: %s", url));
>>>       > - } else {
>>>       > - union usa usa;
>>>       > - socklen_t slen = tousa(&c->loc, &usa);
>>>       > - int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
>>>       > - int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM :
>>>      SOCK_STREAM;
>>>       > - int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
>>>       > - (void) on;
>>>       > +#ifndef asm_clear
>>>       > +#define asm_clear 0
>>>       > +#endif
>>>       > +#ifndef asm_set
>>>       > +#define asm_set 0
>>>       > +#endif
>>>       > +#ifndef asm_add
>>>       > +#define asm_add 0
>>>       > +#endif
>>>       > +#ifndef asm_sub
>>>       > +#define asm_sub 0
>>>       > +#endif
>>>       > +#ifndef asm_mult
>>>       > +#define asm_mult 0
>>>       > +#endif
>>>       > +#ifndef asm_rshift1
>>>       > +#define asm_rshift1 0
>>>       > +#endif
>>>       > +#ifndef asm_mmod_fast_secp256r1
>>>       > +#define asm_mmod_fast_secp256r1 0
>>>       > +#endif
>>>       >
>>>       > - if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
>>>       > - MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
>>>       > -#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
>>>       > - (defined(LWIP_SOCKET) && SO_REUSE == 1))
>>>       > - } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
>>>       > - sizeof(on)) != 0) {
>>>       > - // 1. SO_RESUSEADDR is not enabled on Windows because the
>>>      semantics of
>>>       > - // SO_REUSEADDR on UNIX and Windows is different. On Windows,
>>>       > - // SO_REUSEADDR allows to bind a socket to a port without error
>>>      even
>>>       > - // if the port is already open by another program. This is not the
>>>       > - // behavior SO_REUSEADDR was designed for, and leads to
>>>      hard-to-track
>>>       > - // failure scenarios. Therefore, SO_REUSEADDR was disabled on
>>>      Windows
>>>       > - // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
>>>       > - // 2. In case of LWIP, SO_REUSEADDR should be explicitly
>>>      enabled, by
>>>       > - // defining
>>>       > - // SO_REUSE (in lwipopts.h), otherwise the code below will compile
>>>       > - // but won't work! (setsockopt will return EINVAL)
>>>       > - MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
>>>       > -#endif
>>>       > -#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) &&
>>>      !defined(WINCE)
>>>       > - } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
>>>      (char *) &on,
>>>       > - sizeof(on)) != 0) {
>>>       > - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
>>>       > - MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
>>>       > +#if defined(default_RNG_defined) && default_RNG_defined
>>>       > +static MG_UECC_RNG_Function g_rng_function = &default_RNG;
>>>       > +#else
>>>       > +static MG_UECC_RNG_Function g_rng_function = 0;
>>>       > #endif
>>>       > - } else if (bind(fd, &usa.sa <http://usa.sa>, slen) != 0) {
>>>       > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
>>>       > - } else if ((type == SOCK_STREAM &&
>>>       > - listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
>>>       > - // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
>>>       > - // In case port was set to 0, get the real port number
>>>       > - MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
>>>       > - } else {
>>>       > - setlocaddr(fd, &c->loc);
>>>       > - mg_set_non_blocking_mode(fd);
>>>       > - c->fd = S2PTR(fd);
>>>       > - MG_EPOLL_ADD(c);
>>>       > - success = true;
>>>       > - }
>>>       > - }
>>>       > - if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
>>>       > - return success;
>>>       > +
>>>       > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
>>>       > + g_rng_function = rng_function;
>>>       > +}
>>>       > +
>>>       > +MG_UECC_RNG_Function mg_uecc_get_rng(void) {
>>>       > + return g_rng_function;
>>>       > }
>>>       >
>>>       > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > - long n = 0;
>>>       > - if (c->is_udp) {
>>>       > - union usa usa;
>>>       > - socklen_t slen = tousa(&c->rem, &usa);
>>>       > - n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa
>>>      <http://usa.sa>, &slen);
>>>       > - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
>>>       > - } else {
>>>       > - n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
>>>       > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
>>>       > + return BITS_TO_BYTES(curve->num_n_bits);
>>>       > +}
>>>       > +
>>>       > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
>>>       > + return 2 * curve->num_bytes;
>>>       > +}
>>>       > +
>>>       > +#if !asm_clear
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
>>>       > + wordcount_t num_words) {
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words; ++i) {
>>>       > + vli[i] = 0;
>>>       > }
>>>       > - if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
>>>       > - if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > }
>>>       > +#endif /* !asm_clear */
>>>       >
>>>       > -// NOTE(lsm): do only one iteration of reads, cause some systems
>>>       > -// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when
>>>      no data
>>>       > -static void read_conn(struct mg_connection *c) {
>>>       > - long n = -1;
>>>       > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
>>>       > - mg_error(c, "max_recv_buf_size reached");
>>>       > - } else if (c->recv.size <= c->recv.len &&
>>>       > - !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
>>>       > - mg_error(c, "oom");
>>>       > - } else {
>>>       > - char *buf = (char *) &c->recv.buf[c->recv.len];
>>>       > - size_t len = c->recv.size - c->recv.len;
>>>       > - n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf,
>>>      len);
>>>       > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
>>>      c->fd,
>>>       > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>>>       > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
>>>       > - iolog(c, buf, n, true);
>>>       > +/* Constant-time comparison to zero - secure way to compare long
>>>      integers */
>>>       > +/* Returns 1 if vli == 0, 0 otherwise. */
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const
>>>      mg_uecc_word_t *vli,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t bits = 0;
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words; ++i) {
>>>       > + bits |= vli[i];
>>>       > }
>>>       > + return (bits == 0);
>>>       > }
>>>       >
>>>       > -static void write_conn(struct mg_connection *c) {
>>>       > - char *buf = (char *) c->send.buf;
>>>       > - size_t len = c->send.len;
>>>       > - long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c,
>>>      buf, len);
>>>       > - MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id,
>>>      c->fd,
>>>       > - (long) c->send.len, (long) c->send.size, (long) c->recv.len,
>>>       > - (long) c->recv.size, n, MG_SOCKET_ERRNO));
>>>       > - iolog(c, buf, n, false);
>>>       > +/* Returns nonzero if bit 'bit' of vli is set. */
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const
>>>      mg_uecc_word_t *vli,
>>>       > + bitcount_t bit) {
>>>       > + return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
>>>       > + ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
>>>       > }
>>>       >
>>>       > -static void close_conn(struct mg_connection *c) {
>>>       > - if (FD(c) != MG_INVALID_SOCKET) {
>>>       > -#if MG_ENABLE_EPOLL
>>>       > - epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
>>>       > -#endif
>>>       > - closesocket(FD(c));
>>>       > -#if MG_ENABLE_FREERTOS_TCP
>>>       > - FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
>>>       > -#endif
>>>       > +/* Counts the number of words in vli. */
>>>       > +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
>>>       > + const wordcount_t max_words) {
>>>       > + wordcount_t i;
>>>       > + /* Search from the end until we find a non-zero digit.
>>>       > + We do it in reverse because we expect that most digits will be
>>>      nonzero. */
>>>       > + for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
>>>       > }
>>>       > - mg_close_conn(c);
>>>       > +
>>>       > + return (i + 1);
>>>       > }
>>>       >
>>>       > -static void connect_conn(struct mg_connection *c) {
>>>       > - union usa usa;
>>>       > - socklen_t n = sizeof(usa);
>>>       > - // Use getpeername() to test whether we have connected
>>>       > - if (getpeername(FD(c), &usa.sa <http://usa.sa>, &n) == 0) {
>>>       > - c->is_connecting = 0;
>>>       > - mg_call(c, MG_EV_CONNECT, NULL);
>>>       > - MG_EPOLL_MOD(c, 0);
>>>       > - if (c->is_tls_hs) mg_tls_handshake(c);
>>>       > - } else {
>>>       > - mg_error(c, "socket error");
>>>       > +/* Counts the number of bits required to represent vli. */
>>>       > +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const
>>>      mg_uecc_word_t *vli,
>>>       > + const wordcount_t max_words) {
>>>       > + mg_uecc_word_t i;
>>>       > + mg_uecc_word_t digit;
>>>       > +
>>>       > + wordcount_t num_digits = vli_numDigits(vli, max_words);
>>>       > + if (num_digits == 0) {
>>>       > + return 0;
>>>       > }
>>>       > -}
>>>       >
>>>       > -static void setsockopts(struct mg_connection *c) {
>>>       > -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
>>>       > - MG_ARCH == MG_ARCH_TIRTOS
>>>       > - (void) c;
>>>       > -#else
>>>       > - int on = 1;
>>>       > -#if !defined(SOL_TCP)
>>>       > -#define SOL_TCP IPPROTO_TCP
>>>       > -#endif
>>>       > - if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on,
>>>      sizeof(on)) != 0)
>>>       > - (void) 0;
>>>       > - if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
>>>      sizeof(on)) !=
>>>       > - 0)
>>>       > - (void) 0;
>>>       > -#endif
>>>       > + digit = vli[num_digits - 1];
>>>       > + for (i = 0; digit; ++i) {
>>>       > + digit >>= 1;
>>>       > + }
>>>       > +
>>>       > + return (((bitcount_t) ((num_digits - 1) <<
>>>      MG_UECC_WORD_BITS_SHIFT)) +
>>>       > + (bitcount_t) i);
>>>       > }
>>>       >
>>>       > -void mg_connect_resolved(struct mg_connection *c) {
>>>       > - int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
>>>       > - int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has
>>>      resolved IP
>>>       > - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket
>>>       > - c->is_resolving = 0; // Clear resolving flag
>>>       > - if (FD(c) == MG_INVALID_SOCKET) {
>>>       > - mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
>>>       > - } else if (c->is_udp) {
>>>       > - MG_EPOLL_ADD(c);
>>>       > -#if MG_ARCH == MG_ARCH_TIRTOS
>>>       > - union usa usa; // TI-RTOS NDK requires binding to receive on
>>>      UDP sockets
>>>       > - socklen_t slen = tousa(&c->loc, &usa);
>>>       > - if (bind(c->fd, &usa.sa <http://usa.sa>, slen) != 0)
>>>       > - MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
>>>       > -#endif
>>>       > - mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > - mg_call(c, MG_EV_CONNECT, NULL);
>>>       > - } else {
>>>       > - union usa usa;
>>>       > - socklen_t slen = tousa(&c->rem, &usa);
>>>       > - mg_set_non_blocking_mode(FD(c));
>>>       > - setsockopts(c);
>>>       > - MG_EPOLL_ADD(c);
>>>       > - mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > - if ((rc = connect(FD(c), &usa.sa <http://usa.sa>, slen)) == 0) {
>>>       > - mg_call(c, MG_EV_CONNECT, NULL);
>>>       > - } else if (mg_sock_would_block()) {
>>>       > - MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
>>>       > - mg_ntohs(c->rem.port)));
>>>       > - c->is_connecting = 1;
>>>       > - } else {
>>>       > - mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
>>>       > +/* Sets dest = src. */
>>>       > +#if !asm_set
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
>>>       > + const mg_uecc_word_t *src,
>>>       > + wordcount_t num_words) {
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words; ++i) {
>>>       > + dest[i] = src[i];
>>>       > + }
>>>       > +}
>>>       > +#endif /* !asm_set */
>>>       > +
>>>       > +/* Returns sign of left - right. */
>>>       > +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t
>>>      *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + wordcount_t i;
>>>       > + for (i = num_words - 1; i >= 0; --i) {
>>>       > + if (left[i] > right[i]) {
>>>       > + return 1;
>>>       > + } else if (left[i] < right[i]) {
>>>       > + return -1;
>>>       > }
>>>       > }
>>>       > - (void) rc;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
>>>       > - socklen_t *len) {
>>>       > - MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
>>>       > - do {
>>>       > - memset(usa, 0, sizeof(*usa));
>>>       > - s = accept(sock, &usa->sa, len);
>>>       > - } while (s == MG_INVALID_SOCKET && errno == EINTR);
>>>       > - return s;
>>>       > +/* Constant-time comparison function - secure way to compare
>>>      long integers */
>>>       > +/* Returns one if left == right, zero otherwise. */
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t diff = 0;
>>>       > + wordcount_t i;
>>>       > + for (i = num_words - 1; i >= 0; --i) {
>>>       > + diff |= (left[i] ^ right[i]);
>>>       > + }
>>>       > + return (diff == 0);
>>>       > +}
>>>       > +
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Returns sign of left - right, in constant time. */
>>>       > +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t
>>>      *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right,
>>>      num_words);
>>>       > + mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
>>>       > + return (cmpresult_t) (!equal - 2 * neg);
>>>       > +}
>>>       > +
>>>       > +/* Computes vli = vli >> 1. */
>>>       > +#if !asm_rshift1
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t *end = vli;
>>>       > + mg_uecc_word_t carry = 0;
>>>       > +
>>>       > + vli += num_words;
>>>       > + while (vli-- > end) {
>>>       > + mg_uecc_word_t temp = *vli;
>>>       > + *vli = (temp >> 1) | carry;
>>>       > + carry = temp << (MG_UECC_WORD_BITS - 1);
>>>       > + }
>>>       > }
>>>       > +#endif /* !asm_rshift1 */
>>>       > +
>>>       > +/* Computes result = left + right, returning carry. Can modify
>>>      in place. */
>>>       > +#if !asm_add
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t carry = 0;
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words; ++i) {
>>>       > + mg_uecc_word_t sum = left[i] + right[i] + carry;
>>>       > + if (sum != left[i]) {
>>>       > + carry = (sum < left[i]);
>>>       > + }
>>>       > + result[i] = sum;
>>>       > + }
>>>       > + return carry;
>>>       > +}
>>>       > +#endif /* !asm_add */
>>>       > +
>>>       > +/* Computes result = left - right, returning borrow. Can modify
>>>      in place. */
>>>       > +#if !asm_sub
>>>       > +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t borrow = 0;
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words; ++i) {
>>>       > + mg_uecc_word_t diff = left[i] - right[i] - borrow;
>>>       > + if (diff != left[i]) {
>>>       > + borrow = (diff > left[i]);
>>>       > + }
>>>       > + result[i] = diff;
>>>       > + }
>>>       > + return borrow;
>>>       > +}
>>>       > +#endif /* !asm_sub */
>>>       > +
>>>       > +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) || \
>>>       > + (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>      && \
>>>       > + ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
>>>       > +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b,
>>>      mg_uecc_word_t *r0,
>>>       > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
>>>       > +#if MG_UECC_WORD_SIZE == 8
>>>       > + uint64_t a0 = a & 0xffffffff;
>>>       > + uint64_t a1 = a >> 32;
>>>       > + uint64_t b0 = b & 0xffffffff;
>>>       > + uint64_t b1 = b >> 32;
>>>       > +
>>>       > + uint64_t i0 = a0 * b0;
>>>       > + uint64_t i1 = a0 * b1;
>>>       > + uint64_t i2 = a1 * b0;
>>>       > + uint64_t i3 = a1 * b1;
>>>       > +
>>>       > + uint64_t p0, p1;
>>>       > +
>>>       > + i2 += (i0 >> 32);
>>>       > + i2 += i1;
>>>       > + if (i2 < i1) { /* overflow */
>>>       > + i3 += 0x100000000;
>>>       > + }
>>>       >
>>>       > -static void accept_conn(struct mg_mgr *mgr, struct mg_connection
>>>      *lsn) {
>>>       > - struct mg_connection *c = NULL;
>>>       > - union usa usa;
>>>       > - socklen_t sa_len = sizeof(usa);
>>>       > - MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
>>>       > - if (fd == MG_INVALID_SOCKET) {
>>>       > -#if MG_ARCH == MG_ARCH_AZURERTOS
>>>       > - // AzureRTOS, in non-block socket mode can mark listening
>>>      socket readable
>>>       > - // even it is not. See comment for 'select' func implementation in
>>>       > - // nx_bsd.c That's not an error, just should try later
>>>       > - if (MG_SOCKET_ERRNO != EAGAIN)
>>>       > -#endif
>>>       > - MG_ERROR(("%lu accept failed, errno %d", lsn->id,
>>>      MG_SOCKET_ERRNO));
>>>       > -#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
>>>       > - (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
>>>       > - } else if ((long) fd >= FD_SETSIZE) {
>>>       > - MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
>>>       > - closesocket(fd);
>>>       > + p0 = (i0 & 0xffffffff) | (i2 << 32);
>>>       > + p1 = i3 + (i2 >> 32);
>>>       > +
>>>       > + *r0 += p0;
>>>       > + *r1 += (p1 + (*r0 < p0));
>>>       > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
>>>       > +#else
>>>       > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
>>>       > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
>>>      MG_UECC_WORD_BITS) | *r0;
>>>       > + r01 += p;
>>>       > + *r2 += (r01 < p);
>>>       > + *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
>>>       > + *r0 = (mg_uecc_word_t) r01;
>>>       > #endif
>>>       > - } else if ((c = mg_alloc_conn(mgr)) == NULL) {
>>>       > - MG_ERROR(("%lu OOM", lsn->id));
>>>       > - closesocket(fd);
>>>       > - } else {
>>>       > - tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
>>>       > - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
>>>       > - c->fd = S2PTR(fd);
>>>       > - MG_EPOLL_ADD(c);
>>>       > - mg_set_non_blocking_mode(FD(c));
>>>       > - setsockopts(c);
>>>       > - c->is_accepted = 1;
>>>       > - c->is_hexdumping = lsn->is_hexdumping;
>>>       > - c->loc = lsn->loc;
>>>       > - c->pfn = lsn->pfn;
>>>       > - c->pfn_data = lsn->pfn_data;
>>>       > - c->fn = lsn->fn;
>>>       > - c->fn_data = lsn->fn_data;
>>>       > - MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4,
>>>      &c->rem.ip,
>>>       > - mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
>>>       > - mg_call(c, MG_EV_OPEN, NULL);
>>>       > - mg_call(c, MG_EV_ACCEPT, NULL);
>>>       > +}
>>>       > +#endif /* muladd needed */
>>>       > +
>>>       > +#if !asm_mult
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t r0 = 0;
>>>       > + mg_uecc_word_t r1 = 0;
>>>       > + mg_uecc_word_t r2 = 0;
>>>       > + wordcount_t i, k;
>>>       > +
>>>       > + /* Compute each digit of result in sequence, maintaining the
>>>      carries. */
>>>       > + for (k = 0; k < num_words; ++k) {
>>>       > + for (i = 0; i <= k; ++i) {
>>>       > + muladd(left[i], right[k - i], &r0, &r1, &r2);
>>>       > + }
>>>       > + result[k] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + r2 = 0;
>>>       > }
>>>       > + for (k = num_words; k < num_words * 2 - 1; ++k) {
>>>       > + for (i = (wordcount_t) ((k + 1) - num_words); i < num_words;
>>>      ++i) {
>>>       > + muladd(left[i], right[k - i], &r0, &r1, &r2);
>>>       > + }
>>>       > + result[k] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + r2 = 0;
>>>       > + }
>>>       > + result[num_words * 2 - 1] = r0;
>>>       > }
>>>       > +#endif /* !asm_mult */
>>>       >
>>>       > -static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa
>>>      usa[2], bool udp) {
>>>       > - MG_SOCKET_TYPE sock;
>>>       > - socklen_t n = sizeof(usa[0].sin);
>>>       > - bool success = false;
>>>       > +#if MG_UECC_SQUARE_FUNC
>>>       >
>>>       > - sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > - (void) memset(&usa[0], 0, sizeof(usa[0]));
>>>       > - usa[0].sin.sin_family = AF_INET;
>>>       > - *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); //
>>>      127.0.0.1
>>>       > - usa[1] = usa[0];
>>>       > +#if !asm_square
>>>       > +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b,
>>>      mg_uecc_word_t *r0,
>>>       > + mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
>>>       > +#if MG_UECC_WORD_SIZE == 8
>>>       > + uint64_t a0 = a & 0xffffffffull;
>>>       > + uint64_t a1 = a >> 32;
>>>       > + uint64_t b0 = b & 0xffffffffull;
>>>       > + uint64_t b1 = b >> 32;
>>>       >
>>>       > - if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) !=
>>>      MG_INVALID_SOCKET &&
>>>       > - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
>>>       > - bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n)
>>>      == 0 &&
>>>       > - getsockname(sp[0], &usa[0].sa, &n) == 0 &&
>>>       > - getsockname(sp[1], &usa[1].sa, &n) == 0 &&
>>>       > - connect(sp[0], &usa[1].sa, n) == 0 &&
>>>       > - connect(sp[1], &usa[0].sa, n) == 0) {
>>>       > - success = true;
>>>       > - } else if (!udp &&
>>>       > - (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
>>>       > - bind(sock, &usa[0].sa, n) == 0 &&
>>>       > - listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
>>>       > - getsockname(sock, &usa[0].sa, &n) == 0 &&
>>>       > - (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
>>>       > - connect(sp[0], &usa[0].sa, n) == 0 &&
>>>       > - (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
>>>       > - success = true;
>>>       > - }
>>>       > - if (success) {
>>>       > - mg_set_non_blocking_mode(sp[1]);
>>>       > - } else {
>>>       > - if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
>>>       > - if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
>>>       > - sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > + uint64_t i0 = a0 * b0;
>>>       > + uint64_t i1 = a0 * b1;
>>>       > + uint64_t i2 = a1 * b0;
>>>       > + uint64_t i3 = a1 * b1;
>>>       > +
>>>       > + uint64_t p0, p1;
>>>       > +
>>>       > + i2 += (i0 >> 32);
>>>       > + i2 += i1;
>>>       > + if (i2 < i1) { /* overflow */
>>>       > + i3 += 0x100000000ull;
>>>       > }
>>>       > - if (sock != MG_INVALID_SOCKET) closesocket(sock);
>>>       > - return success;
>>>       > +
>>>       > + p0 = (i0 & 0xffffffffull) | (i2 << 32);
>>>       > + p1 = i3 + (i2 >> 32);
>>>       > +
>>>       > + *r2 += (p1 >> 63);
>>>       > + p1 = (p1 << 1) | (p0 >> 63);
>>>       > + p0 <<= 1;
>>>       > +
>>>       > + *r0 += p0;
>>>       > + *r1 += (p1 + (*r0 < p0));
>>>       > + *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
>>>       > +#else
>>>       > + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
>>>       > + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) <<
>>>      MG_UECC_WORD_BITS) | *r0;
>>>       > + *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
>>>       > + p *= 2;
>>>       > + r01 += p;
>>>       > + *r2 += (r01 < p);
>>>       > + *r1 = r01 >> MG_UECC_WORD_BITS;
>>>       > + *r0 = (mg_uecc_word_t) r01;
>>>       > +#endif
>>>       > }
>>>       >
>>>       > -int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void
>>>      *fn_data,
>>>       > - bool udp) {
>>>       > - union usa usa[2];
>>>       > - MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
>>>       > - struct mg_connection *c = NULL;
>>>       > - if (!mg_socketpair(sp, usa, udp)) {
>>>       > - MG_ERROR(("Cannot create socket pair"));
>>>       > - } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) ==
>>>      NULL) {
>>>       > - closesocket(sp[0]);
>>>       > - closesocket(sp[1]);
>>>       > - sp[0] = sp[1] = MG_INVALID_SOCKET;
>>>       > - } else {
>>>       > - tomgaddr(&usa[0], &c->rem, false);
>>>       > - MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long)
>>>      sp[0]));
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t r0 = 0;
>>>       > + mg_uecc_word_t r1 = 0;
>>>       > + mg_uecc_word_t r2 = 0;
>>>       > +
>>>       > + wordcount_t i, k;
>>>       > +
>>>       > + for (k = 0; k < num_words * 2 - 1; ++k) {
>>>       > + mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
>>>       > + for (i = min; i <= k && i <= k - i; ++i) {
>>>       > + if (i < k - i) {
>>>       > + mul2add(left[i], left[k - i], &r0, &r1, &r2);
>>>       > + } else {
>>>       > + muladd(left[i], left[k - i], &r0, &r1, &r2);
>>>       > + }
>>>       > + }
>>>       > + result[k] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + r2 = 0;
>>>       > }
>>>       > - return (int) sp[0];
>>>       > -}
>>>       >
>>>       > -static bool can_read(const struct mg_connection *c) {
>>>       > - return c->is_full == false;
>>>       > + result[num_words * 2 - 1] = r0;
>>>       > }
>>>       > +#endif /* !asm_square */
>>>       >
>>>       > -static bool can_write(const struct mg_connection *c) {
>>>       > - return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
>>>       > -}
>>>       > +#else /* MG_UECC_SQUARE_FUNC */
>>>       >
>>>       > -static bool skip_iotest(const struct mg_connection *c) {
>>>       > - return (c->is_closing || c->is_resolving || FD(c) ==
>>>      MG_INVALID_SOCKET) ||
>>>       > - (can_read(c) == false && can_write(c) == false);
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_vli_mult(result, left, left, num_words);
>>>       > }
>>>       > +#endif /* MG_UECC_ENABLE_VLI_API */
>>>       >
>>>       > -static void mg_iotest(struct mg_mgr *mgr, int ms) {
>>>       > -#if MG_ENABLE_FREERTOS_TCP
>>>       > - struct mg_connection *c;
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > - c->is_readable = c->is_writable = 0;
>>>       > - if (skip_iotest(c)) continue;
>>>       > - if (can_read(c))
>>>       > - FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
>>>       > - if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
>>>       > - }
>>>       > - FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > - EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
>>>       > - c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
>>>       > - c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
>>>       > - FreeRTOS_FD_CLR(c->fd, mgr->ss,
>>>       > - eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
>>>       > +#endif /* MG_UECC_SQUARE_FUNC */
>>>       > +
>>>       > +/* Computes result = (left + right) % mod.
>>>       > + Assumes that left < mod and right < mod, and that result does
>>>      not overlap
>>>       > + mod. */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right,
>>>      num_words);
>>>       > + if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) !=
>>>      1) {
>>>       > + /* result > mod (result = mod + remainder), so subtract mod to get
>>>       > + * remainder. */
>>>       > + mg_uecc_vli_sub(result, result, mod, num_words);
>>>       > }
>>>       > -#elif MG_ENABLE_EPOLL
>>>       > - size_t max = 1;
>>>       > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > - c->is_readable = c->is_writable = 0;
>>>       > - if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
>>>       > - if (can_write(c)) MG_EPOLL_MOD(c, 1);
>>>       > - max++;
>>>       > +}
>>>       > +
>>>       > +/* Computes result = (left - right) % mod.
>>>       > + Assumes that left < mod and right < mod, and that result does
>>>      not overlap
>>>       > + mod. */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right,
>>>      num_words);
>>>       > + if (l_borrow) {
>>>       > + /* In this case, result == -diff == (max int) - diff. Since -x
>>>      % d == d - x,
>>>       > + we can get the correct result from result + mod (with
>>>      overflow). */
>>>       > + mg_uecc_vli_add(result, result, mod, num_words);
>>>       > }
>>>       > - struct epoll_event *evs = (struct epoll_event *) alloca(max *
>>>      sizeof(evs[0]));
>>>       > - int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
>>>       > - for (int i = 0; i < n; i++) {
>>>       > - struct mg_connection *c = (struct mg_connection *)
>>>      evs[i].data.ptr;
>>>       > - if (evs[i].events & EPOLLERR) {
>>>       > - mg_error(c, "socket error");
>>>       > - } else if (c->is_readable == 0) {
>>>       > - bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
>>>       > - bool wr = evs[i].events & EPOLLOUT;
>>>       > - c->is_readable = can_read(c) && rd ? 1U : 0;
>>>       > - c->is_writable = can_write(c) && wr ? 1U : 0;
>>>       > +}
>>>       > +
>>>       > +/* Computes result = product % mod, where product is 2N words
>>>      long. */
>>>       > +/* Currently only designed to work for curve_p or curve_n. */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t *v[2] = {tmp, product};
>>>       > + mg_uecc_word_t index;
>>>       > +
>>>       > + /* Shift mod so its highest set bit is at the maximum position. */
>>>       > + bitcount_t shift = (bitcount_t) ((num_words * 2 *
>>>      MG_UECC_WORD_BITS) -
>>>       > + mg_uecc_vli_numBits(mod, num_words));
>>>       > + wordcount_t word_shift = (wordcount_t) (shift /
>>>      MG_UECC_WORD_BITS);
>>>       > + wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
>>>       > + mg_uecc_word_t carry = 0;
>>>       > + mg_uecc_vli_clear(mod_multiple, word_shift);
>>>       > + if (bit_shift > 0) {
>>>       > + for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
>>>       > + mod_multiple[(mg_uecc_word_t) word_shift + index] =
>>>       > + (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
>>>       > + carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
>>>       > }
>>>       > + } else {
>>>       > + mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
>>>       > }
>>>       > - (void) skip_iotest;
>>>       > -#elif MG_ENABLE_POLL
>>>       > - nfds_t n = 0;
>>>       > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) n++;
>>>       > - struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
>>>       > - memset(fds, 0, n * sizeof(fds[0]));
>>>       > - n = 0;
>>>       > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > - c->is_readable = c->is_writable = 0;
>>>       > - if (skip_iotest(c)) {
>>>       > - // Socket not valid, ignore
>>>       > - } else if (mg_tls_pending(c) > 0) {
>>>       > - ms = 1; // Don't wait if TLS is ready
>>>       > - } else {
>>>       > - fds[n].fd = FD(c);
>>>       > - if (can_read(c)) fds[n].events |= POLLIN;
>>>       > - if (can_write(c)) fds[n].events |= POLLOUT;
>>>       > - n++;
>>>       > +
>>>       > + for (index = 1; shift >= 0; --shift) {
>>>       > + mg_uecc_word_t borrow = 0;
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_words * 2; ++i) {
>>>       > + mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
>>>       > + if (diff != v[index][i]) {
>>>       > + borrow = (diff > v[index][i]);
>>>       > + }
>>>       > + v[1 - index][i] = diff;
>>>       > }
>>>       > + index = !(index ^ borrow); /* Swap the index if there was no
>>>      borrow */
>>>       > + mg_uecc_vli_rshift1(mod_multiple, num_words);
>>>       > + mod_multiple[num_words - 1] |= mod_multiple[num_words]
>>>       > + << (MG_UECC_WORD_BITS - 1);
>>>       > + mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
>>>       > + }
>>>       > + mg_uecc_vli_set(result, v[index], num_words);
>>>       > +}
>>>       > +
>>>       > +/* Computes result = (left * right) % mod. */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_vli_mult(product, left, right, num_words);
>>>       > + mg_uecc_vli_mmod(result, product, mod, num_words);
>>>       > +}
>>>       > +
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_vli_mult(product, left, right, curve->num_words);
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + curve->mmod_fast(result, product);
>>>       > +#else
>>>       > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +#if MG_UECC_SQUARE_FUNC
>>>       > +
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +/* Computes result = left^2 % mod. */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_vli_square(product, left, num_words);
>>>       > + mg_uecc_vli_mmod(result, product, mod, num_words);
>>>       > +}
>>>       > +#endif /* MG_UECC_ENABLE_VLI_API */
>>>       > +
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_vli_square(product, left, curve->num_words);
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + curve->mmod_fast(result, product);
>>>       > +#else
>>>       > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>>>       > +#endif
>>>       > +}
>>>       > +
>>>       > +#else /* MG_UECC_SQUARE_FUNC */
>>>       > +
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_vli_modMult(result, left, left, mod, num_words);
>>>       > +}
>>>       > +#endif /* MG_UECC_ENABLE_VLI_API */
>>>       > +
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t
>>>      *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_vli_modMult_fast(result, left, left, curve);
>>>       > +}
>>>       > +
>>>       > +#endif /* MG_UECC_SQUARE_FUNC */
>>>       > +
>>>       > +#define EVEN(vli) (!(vli[0] & 1))
>>>       > +static void vli_modInv_update(mg_uecc_word_t *uv, const
>>>      mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t carry = 0;
>>>       > + if (!EVEN(uv)) {
>>>       > + carry = mg_uecc_vli_add(uv, uv, mod, num_words);
>>>       > }
>>>       > + mg_uecc_vli_rshift1(uv, num_words);
>>>       > + if (carry) {
>>>       > + uv[num_words - 1] |= HIGH_BIT_SET;
>>>       > + }
>>>       > +}
>>>       >
>>>       > - // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
>>>       > - if (poll(fds, n, ms) < 0) {
>>>       > -#if MG_ARCH == MG_ARCH_WIN32
>>>       > - if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets
>>>       > -#endif
>>>       > - memset(fds, 0, n * sizeof(fds[0]));
>>>       > +/* Computes result = (1 / input) % mod. All VLIs are the same size.
>>>       > + See "From Euclid's GCD to Montgomery Multiplication to the
>>>      Great Divide" */
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *input,
>>>       > + const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
>>>       > + u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
>>>       > + cmpresult_t cmpResult;
>>>       > +
>>>       > + if (mg_uecc_vli_isZero(input, num_words)) {
>>>       > + mg_uecc_vli_clear(result, num_words);
>>>       > + return;
>>>       > }
>>>       > - n = 0;
>>>       > - for (struct mg_connection *c = mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > - if (skip_iotest(c)) {
>>>       > - // Socket not valid, ignore
>>>       > - } else if (mg_tls_pending(c) > 0) {
>>>       > - c->is_readable = 1;
>>>       > +
>>>       > + mg_uecc_vli_set(a, input, num_words);
>>>       > + mg_uecc_vli_set(b, mod, num_words);
>>>       > + mg_uecc_vli_clear(u, num_words);
>>>       > + u[0] = 1;
>>>       > + mg_uecc_vli_clear(v, num_words);
>>>       > + while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) !=
>>>      0) {
>>>       > + if (EVEN(a)) {
>>>       > + mg_uecc_vli_rshift1(a, num_words);
>>>       > + vli_modInv_update(u, mod, num_words);
>>>       > + } else if (EVEN(b)) {
>>>       > + mg_uecc_vli_rshift1(b, num_words);
>>>       > + vli_modInv_update(v, mod, num_words);
>>>       > + } else if (cmpResult > 0) {
>>>       > + mg_uecc_vli_sub(a, a, b, num_words);
>>>       > + mg_uecc_vli_rshift1(a, num_words);
>>>       > + if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
>>>       > + mg_uecc_vli_add(u, u, mod, num_words);
>>>       > + }
>>>       > + mg_uecc_vli_sub(u, u, v, num_words);
>>>       > + vli_modInv_update(u, mod, num_words);
>>>       > } else {
>>>       > - if (fds[n].revents & POLLERR) {
>>>       > - mg_error(c, "socket error");
>>>       > - } else {
>>>       > - c->is_readable =
>>>       > - (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
>>>       > - c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
>>>       > + mg_uecc_vli_sub(b, b, a, num_words);
>>>       > + mg_uecc_vli_rshift1(b, num_words);
>>>       > + if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
>>>       > + mg_uecc_vli_add(v, v, mod, num_words);
>>>       > }
>>>       > - n++;
>>>       > + mg_uecc_vli_sub(v, v, u, num_words);
>>>       > + vli_modInv_update(v, mod, num_words);
>>>       > }
>>>       > }
>>>       > -#else
>>>       > - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero =
>>>      {0, 0};
>>>       > - struct mg_connection *c;
>>>       > - fd_set rset, wset, eset;
>>>       > - MG_SOCKET_TYPE maxfd = 0;
>>>       > - int rc;
>>>       > + mg_uecc_vli_set(result, u, num_words);
>>>       > +}
>>>       >
>>>       > - FD_ZERO(&rset);
>>>       > - FD_ZERO(&wset);
>>>       > - FD_ZERO(&eset);
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > - c->is_readable = c->is_writable = 0;
>>>       > - if (skip_iotest(c)) continue;
>>>       > - FD_SET(FD(c), &eset);
>>>       > - if (can_read(c)) FD_SET(FD(c), &rset);
>>>       > - if (can_write(c)) FD_SET(FD(c), &wset);
>>>       > - if (mg_tls_pending(c) > 0) tv = tv_zero;
>>>       > - if (FD(c) > maxfd) maxfd = FD(c);
>>>       > +/* ------ Point operations ------ */
>>>       > +
>>>       > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>>>      2-clause license. */
>>>       > +
>>>       > +#ifndef _UECC_CURVE_SPECIFIC_H_
>>>       > +#define _UECC_CURVE_SPECIFIC_H_
>>>       > +
>>>       > +#define num_bytes_secp160r1 20
>>>       > +#define num_bytes_secp192r1 24
>>>       > +#define num_bytes_secp224r1 28
>>>       > +#define num_bytes_secp256r1 32
>>>       > +#define num_bytes_secp256k1 32
>>>       > +
>>>       > +#if (MG_UECC_WORD_SIZE == 1)
>>>       > +
>>>       > +#define num_words_secp160r1 20
>>>       > +#define num_words_secp192r1 24
>>>       > +#define num_words_secp224r1 28
>>>       > +#define num_words_secp256r1 32
>>>       > +#define num_words_secp256k1 32
>>>       > +
>>>       > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
>>>       > + 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
>>>       > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
>>>       > +
>>>       > +#elif (MG_UECC_WORD_SIZE == 4)
>>>       > +
>>>       > +#define num_words_secp160r1 5
>>>       > +#define num_words_secp192r1 6
>>>       > +#define num_words_secp224r1 7
>>>       > +#define num_words_secp256r1 8
>>>       > +#define num_words_secp256k1 8
>>>       > +
>>>       > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a,
>>>      0x##h##g##f##e
>>>       > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
>>>       > +
>>>       > +#elif (MG_UECC_WORD_SIZE == 8)
>>>       > +
>>>       > +#define num_words_secp160r1 3
>>>       > +#define num_words_secp192r1 3
>>>       > +#define num_words_secp224r1 4
>>>       > +#define num_words_secp256r1 4
>>>       > +#define num_words_secp256k1 4
>>>       > +
>>>       > +#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h)
>>>      0x##h##g##f##e##d##c##b##a##U
>>>       > +#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
>>>       > +
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
>>>       > + MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
>>>       > +static void double_jacobian_default(mg_uecc_word_t *X1,
>>>      mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
>>>       > + /* t1 = X, t2 = Y, t3 = Z */
>>>       > + mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + if (mg_uecc_vli_isZero(Z1, num_words)) {
>>>       > + return;
>>>       > }
>>>       >
>>>       > - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) <
>>>      0) {
>>>       > -#if MG_ARCH == MG_ARCH_WIN32
>>>       > - if (maxfd == 0) Sleep(ms); // On Windows, select fails if no
>>>      sockets
>>>       > + mg_uecc_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */
>>>       > + mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 =
>>>      A */
>>>       > + mg_uecc_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */
>>>       > + mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
>>>       > + mg_uecc_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */
>>>       > +
>>>       > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1
>>>      + z1^2 */
>>>       > + mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 =
>>>      2*z1^2 */
>>>       > + mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1
>>>      - z1^2 */
>>>       > + mg_uecc_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 -
>>>      z1^4 */
>>>       > +
>>>       > + mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
>>>       > + num_words); /* t3 = 2*(x1^2 - z1^4) */
>>>       > + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
>>>       > + num_words); /* t1 = 3*(x1^2 - z1^4) */
>>>       > + if (mg_uecc_vli_testBit(X1, 0)) {
>>>       > + mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p,
>>>      num_words);
>>>       > + mg_uecc_vli_rshift1(X1, num_words);
>>>       > + X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
>>>       > + } else {
>>>       > + mg_uecc_vli_rshift1(X1, num_words);
>>>       > + }
>>>       > + /* t1 = 3/2*(x1^2 - z1^4) = B */
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */
>>>       > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
>>>      B^2 - A */
>>>       > + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 =
>>>      B^2 - 2A = x3 */
>>>       > + mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A
>>>      - x3 */
>>>       > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A -
>>>      x3) */
>>>       > + mg_uecc_vli_modSub(t4, X1, t4, curve->p,
>>>       > + num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
>>>       > +
>>>       > + mg_uecc_vli_set(X1, Z1, num_words);
>>>       > + mg_uecc_vli_set(Z1, Y1, num_words);
>>>       > + mg_uecc_vli_set(Y1, t4, num_words);
>>>       > +}
>>>       > +
>>>       > +/* Computes result = x^3 + ax + b. result must not overlap x. */
>>>       > +static void x_side_default(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *x,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
>>>       > + mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /*
>>>      r = x^2 - 3 */
>>>       > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3
>>>      - 3x */
>>>       > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
>>>       > + num_words); /* r = x^3 - 3x + b */
>>>       > +}
>>>       > +#endif /* MG_UECC_SUPPORTS_secp... */
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
>>>       > + MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
>>>       > +/* Compute a = sqrt(a) (mod curve_p). */
>>>       > +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve
>>>      curve) {
>>>       > + bitcount_t i;
>>>       > + mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
>>>       > + mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + /* When curve->p == 3 (mod 4), we can compute
>>>       > + sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
>>>       > + mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p +
>>>      1 */
>>>       > + for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
>>>       > + mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
>>>       > + if (mg_uecc_vli_testBit(p1, i)) {
>>>       > + mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
>>>       > + }
>>>       > + }
>>>       > + mg_uecc_vli_set(a, l_result, num_words);
>>>       > +}
>>>       > +#endif /* MG_UECC_SUPPORTS_secp... */
>>>       > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp160r1
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product);
>>>       > +#endif
>>>       > +
>>>       > +static const struct MG_UECC_Curve_t curve_secp160r1 = {
>>>       > + num_words_secp160r1,
>>>       > + num_bytes_secp160r1,
>>>       > + 161, /* num_n_bits */
>>>       > + {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
>>>       > + BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
>>>       > + {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
>>>       > + BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
>>>       > + BYTES_TO_WORDS_4(68, B5, 96, 4A),
>>>       > +
>>>       > + BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
>>>       > + BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
>>>       > + BYTES_TO_WORDS_4(55, 28, A6, 23)},
>>>       > + {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
>>>       > + BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
>>>       > + BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
>>>       > + &double_jacobian_default,
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + &mod_sqrt_default,
>>>       > +#endif
>>>       > + &x_side_default,
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + &vli_mmod_fast_secp160r1
>>>       > +#endif
>>>       > +};
>>>       > +
>>>       > +MG_UECC_Curve mg_uecc_secp160r1(void) {
>>>       > + return &curve_secp160r1;
>>>       > +}
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
>>>       > +/* Computes result = product % curve_p
>>>       > + see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
>>>      <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf> page 354
>>>       > +
>>>       > + Note that this only works if log2(omega) < log2(p) / 2 */
>>>       > +static void omega_mult_secp160r1(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *right);
>>>       > +#if MG_UECC_WORD_SIZE == 8
>>>       > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product) {
>>>       > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
>>>       > + mg_uecc_word_t copy;
>>>       > +
>>>       > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
>>>       > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
>>>       > +
>>>       > + omega_mult_secp160r1(tmp,
>>>       > + product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
>>>       > +
>>>       > + product[num_words_secp160r1 - 1] &= 0xffffffff;
>>>       > + copy = tmp[num_words_secp160r1 - 1];
>>>       > + tmp[num_words_secp160r1 - 1] &= 0xffffffff;
>>>       > + mg_uecc_vli_add(result, product, tmp,
>>>       > + num_words_secp160r1); /* (C, r) = r + q */
>>>       > + mg_uecc_vli_clear(product, num_words_secp160r1);
>>>       > + tmp[num_words_secp160r1 - 1] = copy;
>>>       > + omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1);
>>>      /* Rq*c */
>>>       > + mg_uecc_vli_add(result, result, product,
>>>       > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
>>>       > +
>>>       > + while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
>>>       > + num_words_secp160r1) > 0) {
>>>       > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>>>      num_words_secp160r1);
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +static void omega_mult_secp160r1(uint64_t *result, const
>>>      uint64_t *right) {
>>>       > + uint32_t carry;
>>>       > + unsigned i;
>>>       > +
>>>       > + /* Multiply by (2^31 + 1). */
>>>       > + carry = 0;
>>>       > + for (i = 0; i < num_words_secp160r1; ++i) {
>>>       > + uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
>>>       > + result[i] = (tmp << 31) + tmp + carry;
>>>       > + carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i]
>>>      == tmp));
>>>       > + }
>>>       > + result[i] = carry;
>>>       > +}
>>>       > #else
>>>       > - MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
>>>       > +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product) {
>>>       > + mg_uecc_word_t tmp[2 * num_words_secp160r1];
>>>       > + mg_uecc_word_t carry;
>>>       > +
>>>       > + mg_uecc_vli_clear(tmp, num_words_secp160r1);
>>>       > + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
>>>       > +
>>>       > + omega_mult_secp160r1(tmp,
>>>       > + product + num_words_secp160r1); /* (Rq, q) = q * c */
>>>       > +
>>>       > + carry = mg_uecc_vli_add(result, product, tmp,
>>>       > + num_words_secp160r1); /* (C, r) = r + q */
>>>       > + mg_uecc_vli_clear(product, num_words_secp160r1);
>>>       > + omega_mult_secp160r1(product, tmp + num_words_secp160r1); /*
>>>      Rq*c */
>>>       > + carry += mg_uecc_vli_add(result, result, product,
>>>       > + num_words_secp160r1); /* (C1, r) = r + Rq*c */
>>>       > +
>>>       > + while (carry > 0) {
>>>       > + --carry;
>>>       > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>>>      num_words_secp160r1);
>>>       > + }
>>>       > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
>>>      num_words_secp160r1) >
>>>       > + 0) {
>>>       > + mg_uecc_vli_sub(result, result, curve_secp160r1.p,
>>>      num_words_secp160r1);
>>>       > + }
>>>       > +}
>>>       > #endif
>>>       > - FD_ZERO(&rset);
>>>       > - FD_ZERO(&wset);
>>>       > - FD_ZERO(&eset);
>>>       > +
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +static void omega_mult_secp160r1(uint8_t *result, const uint8_t
>>>      *right) {
>>>       > + uint8_t carry;
>>>       > + uint8_t i;
>>>       > +
>>>       > + /* Multiply by (2^31 + 1). */
>>>       > + mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /*
>>>      2^32 */
>>>       > + mg_uecc_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */
>>>       > + result[3] = right[0] << 7; /* get last bit from shift */
>>>       > +
>>>       > + carry = mg_uecc_vli_add(result, result, right,
>>>       > + num_words_secp160r1); /* 2^31 + 1 */
>>>       > + for (i = num_words_secp160r1; carry; ++i) {
>>>       > + uint16_t sum = (uint16_t) result[i] + carry;
>>>       > + result[i] = (uint8_t) sum;
>>>       > + carry = sum >> 8;
>>>       > }
>>>       > +}
>>>       > +#elif MG_UECC_WORD_SIZE == 4
>>>       > +static void omega_mult_secp160r1(uint32_t *result, const
>>>      uint32_t *right) {
>>>       > + uint32_t carry;
>>>       > + unsigned i;
>>>       >
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > - if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
>>>       > - mg_error(c, "socket error");
>>>       > - } else {
>>>       > - c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>>>      &rset);
>>>       > - c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c),
>>>      &wset);
>>>       > - if (mg_tls_pending(c) > 0) c->is_readable = 1;
>>>       > - }
>>>       > + /* Multiply by (2^31 + 1). */
>>>       > + mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /*
>>>      2^32 */
>>>       > + mg_uecc_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */
>>>       > + result[0] = right[0] << 31; /* get last bit from shift */
>>>       > +
>>>       > + carry = mg_uecc_vli_add(result, result, right,
>>>       > + num_words_secp160r1); /* 2^31 + 1 */
>>>       > + for (i = num_words_secp160r1; carry; ++i) {
>>>       > + uint64_t sum = (uint64_t) result[i] + carry;
>>>       > + result[i] = (uint32_t) sum;
>>>       > + carry = sum >> 32;
>>>       > }
>>>       > +}
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
>>>      !asm_mmod_fast_secp160r1) */
>>>       > +
>>>       > +#endif /* MG_UECC_SUPPORTS_secp160r1 */
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp192r1
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product);
>>>       > #endif
>>>       > +
>>>       > +static const struct MG_UECC_Curve_t curve_secp192r1 = {
>>>       > + num_words_secp192r1,
>>>       > + num_bytes_secp192r1,
>>>       > + 192, /* num_n_bits */
>>>       > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
>>>       > + BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
>>>       > + BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
>>>       > + BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
>>>       > +
>>>       > + BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
>>>       > + BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
>>>       > + BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
>>>       > + {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
>>>       > + BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
>>>       > + BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
>>>       > + &double_jacobian_default,
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + &mod_sqrt_default,
>>>       > +#endif
>>>       > + &x_side_default,
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + &vli_mmod_fast_secp192r1
>>>       > +#endif
>>>       > +};
>>>       > +
>>>       > +MG_UECC_Curve mg_uecc_secp192r1(void) {
>>>       > + return &curve_secp192r1;
>>>       > }
>>>       >
>>>       > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>>>       > - struct mg_connection *c, *tmp;
>>>       > - uint64_t now;
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +/* Computes result = product % curve_p.
>>>       > + See algorithm 5 and 6 from
>>>      http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
>>>      <http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf>
>>>       > + */
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t
>>>      *product) {
>>>       > + uint8_t tmp[num_words_secp192r1];
>>>       > + uint8_t carry;
>>>       > +
>>>       > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>>>       > +
>>>       > + mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
>>>       > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
>>>       > +
>>>       > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] =
>>>      tmp[7] = 0;
>>>       > + tmp[8] = product[24];
>>>       > + tmp[9] = product[25];
>>>       > + tmp[10] = product[26];
>>>       > + tmp[11] = product[27];
>>>       > + tmp[12] = product[28];
>>>       > + tmp[13] = product[29];
>>>       > + tmp[14] = product[30];
>>>       > + tmp[15] = product[31];
>>>       > + tmp[16] = product[32];
>>>       > + tmp[17] = product[33];
>>>       > + tmp[18] = product[34];
>>>       > + tmp[19] = product[35];
>>>       > + tmp[20] = product[36];
>>>       > + tmp[21] = product[37];
>>>       > + tmp[22] = product[38];
>>>       > + tmp[23] = product[39];
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + tmp[0] = tmp[8] = product[40];
>>>       > + tmp[1] = tmp[9] = product[41];
>>>       > + tmp[2] = tmp[10] = product[42];
>>>       > + tmp[3] = tmp[11] = product[43];
>>>       > + tmp[4] = tmp[12] = product[44];
>>>       > + tmp[5] = tmp[13] = product[45];
>>>       > + tmp[6] = tmp[14] = product[46];
>>>       > + tmp[7] = tmp[15] = product[47];
>>>       > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] =
>>>      tmp[22] =
>>>       > + tmp[23] = 0;
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>>>       > + num_words_secp192r1) != 1) {
>>>       > + carry -=
>>>       > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>>>      num_words_secp192r1);
>>>       > + }
>>>       > +}
>>>       > +#elif MG_UECC_WORD_SIZE == 4
>>>       > +static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t
>>>      *product) {
>>>       > + uint32_t tmp[num_words_secp192r1];
>>>       > + int carry;
>>>       >
>>>       > - mg_iotest(mgr, ms);
>>>       > - now = mg_millis();
>>>       > - mg_timer_poll(&mgr->timers, now);
>>>       > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>>>       >
>>>       > - for (c = mgr->conns; c != NULL; c = tmp) {
>>>       > - bool is_resp = c->is_resp;
>>>       > - tmp = c->next;
>>>       > - mg_call(c, MG_EV_POLL, &now);
>>>       > - if (is_resp && !c->is_resp) {
>>>       > - long n = 0;
>>>       > - mg_call(c, MG_EV_READ, &n);
>>>       > - }
>>>       > - MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r'
>>>      : '-',
>>>       > - c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
>>>       > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>>>       > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>>>       > - if (c->is_resolving || c->is_closing) {
>>>       > - // Do nothing
>>>       > - } else if (c->is_listening && c->is_udp == 0) {
>>>       > - if (c->is_readable) accept_conn(mgr, c);
>>>       > - } else if (c->is_connecting) {
>>>       > - if (c->is_readable || c->is_writable) connect_conn(c);
>>>       > - } else if (c->is_tls_hs) {
>>>       > - if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
>>>       > - } else {
>>>       > - if (c->is_readable) read_conn(c);
>>>       > - if (c->is_writable) write_conn(c);
>>>       > - }
>>>       > + mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
>>>       > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
>>>       >
>>>       > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>>>       > - if (c->is_closing) close_conn(c);
>>>       > + tmp[0] = tmp[1] = 0;
>>>       > + tmp[2] = product[6];
>>>       > + tmp[3] = product[7];
>>>       > + tmp[4] = product[8];
>>>       > + tmp[5] = product[9];
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + tmp[0] = tmp[2] = product[10];
>>>       > + tmp[1] = tmp[3] = product[11];
>>>       > + tmp[4] = tmp[5] = 0;
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>>>       > + num_words_secp192r1) != 1) {
>>>       > + carry -=
>>>       > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>>>      num_words_secp192r1);
>>>       > + }
>>>       > +}
>>>       > +#else
>>>       > +static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t
>>>      *product) {
>>>       > + uint64_t tmp[num_words_secp192r1];
>>>       > + int carry;
>>>       > +
>>>       > + mg_uecc_vli_set(result, product, num_words_secp192r1);
>>>       > +
>>>       > + mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
>>>       > + carry = (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + tmp[0] = 0;
>>>       > + tmp[1] = product[3];
>>>       > + tmp[2] = product[4];
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + tmp[0] = tmp[1] = product[5];
>>>       > + tmp[2] = 0;
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp192r1);
>>>       > +
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
>>>       > + num_words_secp192r1) != 1) {
>>>       > + carry -=
>>>       > + mg_uecc_vli_sub(result, result, curve_secp192r1.p,
>>>      num_words_secp192r1);
>>>       > }
>>>       > }
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
>>>       > +
>>>       > +#endif /* MG_UECC_SUPPORTS_secp192r1 */
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp224r1
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
>>>      curve);
>>>       > +#endif
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product);
>>>       > #endif
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/ssi.c"
>>>       > +static const struct MG_UECC_Curve_t curve_secp224r1 = {
>>>       > + num_words_secp224r1,
>>>       > + num_bytes_secp224r1,
>>>       > + 224, /* num_n_bits */
>>>       > + {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
>>>       > + BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_4(FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
>>>       > + BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
>>>       > + BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
>>>       > + BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
>>>       > +
>>>       > + BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
>>>       > + BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
>>>       > + BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
>>>       > + BYTES_TO_WORDS_4(88, 63, 37, BD)},
>>>       > + {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
>>>       > + BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
>>>       > + BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
>>>       > + BYTES_TO_WORDS_4(85, 0A, 05, B4)},
>>>       > + &double_jacobian_default,
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + &mod_sqrt_secp224r1,
>>>       > #endif
>>>       > + &x_side_default,
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + &vli_mmod_fast_secp224r1
>>>       > +#endif
>>>       > +};
>>>       > +
>>>       > +MG_UECC_Curve mg_uecc_secp224r1(void) {
>>>       > + return &curve_secp224r1;
>>>       > +}
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +/* Routine 3.2.4 RS; from
>>>      http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1,
>>>      mg_uecc_word_t *e1,
>>>       > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
>>>       > + const mg_uecc_word_t *e0,
>>>       > + const mg_uecc_word_t *f0) {
>>>       > + mg_uecc_word_t t[num_words_secp224r1];
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <--
>>>      d0 ^ 2 */
>>>       > + mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1
>>>      <-- d0 * e0 */
>>>       > + mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* d1 <-- t + f0 */
>>>       > + mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* e1 <-- e1 + e1 */
>>>       > + mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1
>>>      <-- t * f0 */
>>>       > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* f1 <-- f1 + f1 */
>>>       > + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* f1 <-- f1 + f1 */
>>>       > +}
>>>       > +
>>>       > +/* Routine 3.2.5 RSS; from
>>>      http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1,
>>>      mg_uecc_word_t *e1,
>>>       > + mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
>>>       > + const mg_uecc_word_t *e0,
>>>       > + const mg_uecc_word_t *f0,
>>>       > + const bitcount_t j) {
>>>       > + bitcount_t i;
>>>       > +
>>>       > + mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
>>>       > + mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
>>>       > + mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
>>>       > + for (i = 1; i <= j; i++) {
>>>       > + mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS
>>>      (d1,e1,f1,d1,e1,f1) */
>>>       > + }
>>>       > +}
>>>       > +
>>>       > +/* Routine 3.2.6 RM; from
>>>      http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2,
>>>      mg_uecc_word_t *e2,
>>>       > + mg_uecc_word_t *f2, const mg_uecc_word_t *c,
>>>       > + const mg_uecc_word_t *d0,
>>>       > + const mg_uecc_word_t *e0,
>>>       > + const mg_uecc_word_t *d1,
>>>       > + const mg_uecc_word_t *e1) {
>>>       > + mg_uecc_word_t t1[num_words_secp224r1];
>>>       > + mg_uecc_word_t t2[num_words_secp224r1];
>>>       > +
>>>       > + mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1
>>>      <-- e0 * e1 */
>>>       > + mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1
>>>      <-- t1 * c */
>>>       > + /* t1 <-- p - t1 */
>>>       > + mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2
>>>      <-- d0 * d1 */
>>>       > + mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* t2 <-- t2 + t1 */
>>>       > + mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1
>>>      <-- d0 * e1 */
>>>       > + mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2
>>>      <-- d1 * e0 */
>>>       > + mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* e2 <-- e2 + t1 */
>>>       > + mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <--
>>>      e2^2 */
>>>       > + mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2
>>>      <-- f2 * c */
>>>       > + /* f2 <-- p - f2 */
>>>       > + mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
>>>       > +}
>>>       > +
>>>       > +/* Routine 3.2.7 RP; from
>>>      http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1,
>>>      mg_uecc_word_t *e1,
>>>       > + mg_uecc_word_t *f1, const mg_uecc_word_t *c,
>>>       > + const mg_uecc_word_t *r) {
>>>       > + wordcount_t i;
>>>       > + wordcount_t pow2i = 1;
>>>       > + mg_uecc_word_t d0[num_words_secp224r1];
>>>       > + mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
>>>       > + mg_uecc_word_t f0[num_words_secp224r1];
>>>       > +
>>>       > + mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
>>>       > + /* f0 <-- p - c */
>>>       > + mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + for (i = 0; i <= 6; i++) {
>>>       > + mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
>>>       > + pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
>>>       > + mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
>>>       > + e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
>>>       > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
>>>       > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
>>>       > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
>>>       > + pow2i *= 2;
>>>       > + }
>>>       > +}
>>>       >
>>>       > +/* Compute a = sqrt(a) (mod curve_p). */
>>>       > +/* Routine 3.2.8 mp_mod_sqrt_224; from
>>>       > + * http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve
>>>      curve) {
>>>       > + (void) curve;
>>>       > + bitcount_t i;
>>>       > + mg_uecc_word_t e1[num_words_secp224r1];
>>>       > + mg_uecc_word_t f1[num_words_secp224r1];
>>>       > + mg_uecc_word_t d0[num_words_secp224r1];
>>>       > + mg_uecc_word_t e0[num_words_secp224r1];
>>>       > + mg_uecc_word_t f0[num_words_secp224r1];
>>>       > + mg_uecc_word_t d1[num_words_secp224r1];
>>>       > +
>>>       > + /* s = a; using constant instead of random value */
>>>       > + mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c,
>>>      s) */
>>>       > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
>>>       > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
>>>       > + for (i = 1; i <= 95; i++) {
>>>       > + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
>>>       > + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
>>>       > + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
>>>       > + mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
>>>       > + f0); /* RS (d1, e1, f1, d0, e0, f0) */
>>>       > + if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 ==
>>>      0 */
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
>>>       > + num_words_secp224r1); /* f1 <-- 1 / e0 */
>>>       > + mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <--
>>>      d0 / e0 */
>>>       > +}
>>>       > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +/* Computes result = product % curve_p
>>>       > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t
>>>      *product) {
>>>       > + uint8_t tmp[num_words_secp224r1];
>>>       > + int8_t carry;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
>>>       > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
>>>       > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>>>       > + tmp[12] = product[28];
>>>       > + tmp[13] = product[29];
>>>       > + tmp[14] = product[30];
>>>       > + tmp[15] = product[31];
>>>       > + tmp[16] = product[32];
>>>       > + tmp[17] = product[33];
>>>       > + tmp[18] = product[34];
>>>       > + tmp[19] = product[35];
>>>       > + tmp[20] = product[36];
>>>       > + tmp[21] = product[37];
>>>       > + tmp[22] = product[38];
>>>       > + tmp[23] = product[39];
>>>       > + tmp[24] = product[40];
>>>       > + tmp[25] = product[41];
>>>       > + tmp[26] = product[42];
>>>       > + tmp[27] = product[43];
>>>       > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[12] = product[44];
>>>       > + tmp[13] = product[45];
>>>       > + tmp[14] = product[46];
>>>       > + tmp[15] = product[47];
>>>       > + tmp[16] = product[48];
>>>       > + tmp[17] = product[49];
>>>       > + tmp[18] = product[50];
>>>       > + tmp[19] = product[51];
>>>       > + tmp[20] = product[52];
>>>       > + tmp[21] = product[53];
>>>       > + tmp[22] = product[54];
>>>       > + tmp[23] = product[55];
>>>       > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = product[28];
>>>       > + tmp[1] = product[29];
>>>       > + tmp[2] = product[30];
>>>       > + tmp[3] = product[31];
>>>       > + tmp[4] = product[32];
>>>       > + tmp[5] = product[33];
>>>       > + tmp[6] = product[34];
>>>       > + tmp[7] = product[35];
>>>       > + tmp[8] = product[36];
>>>       > + tmp[9] = product[37];
>>>       > + tmp[10] = product[38];
>>>       > + tmp[11] = product[39];
>>>       > + tmp[12] = product[40];
>>>       > + tmp[13] = product[41];
>>>       > + tmp[14] = product[42];
>>>       > + tmp[15] = product[43];
>>>       > + tmp[16] = product[44];
>>>       > + tmp[17] = product[45];
>>>       > + tmp[18] = product[46];
>>>       > + tmp[19] = product[47];
>>>       > + tmp[20] = product[48];
>>>       > + tmp[21] = product[49];
>>>       > + tmp[22] = product[50];
>>>       > + tmp[23] = product[51];
>>>       > + tmp[24] = product[52];
>>>       > + tmp[25] = product[53];
>>>       > + tmp[26] = product[54];
>>>       > + tmp[27] = product[55];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = product[44];
>>>       > + tmp[1] = product[45];
>>>       > + tmp[2] = product[46];
>>>       > + tmp[3] = product[47];
>>>       > + tmp[4] = product[48];
>>>       > + tmp[5] = product[49];
>>>       > + tmp[6] = product[50];
>>>       > + tmp[7] = product[51];
>>>       > + tmp[8] = product[52];
>>>       > + tmp[9] = product[53];
>>>       > + tmp[10] = product[54];
>>>       > + tmp[11] = product[55];
>>>       > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>>>       > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>>>       > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>>>       > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>>>       > + num_words_secp224r1) != 1) {
>>>       > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +#elif MG_UECC_WORD_SIZE == 4
>>>       > +static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t
>>>      *product) {
>>>       > + uint32_t tmp[num_words_secp224r1];
>>>       > + int carry;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = tmp[1] = tmp[2] = 0;
>>>       > + tmp[3] = product[7];
>>>       > + tmp[4] = product[8];
>>>       > + tmp[5] = product[9];
>>>       > + tmp[6] = product[10];
>>>       > + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[3] = product[11];
>>>       > + tmp[4] = product[12];
>>>       > + tmp[5] = product[13];
>>>       > + tmp[6] = 0;
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = product[7];
>>>       > + tmp[1] = product[8];
>>>       > + tmp[2] = product[9];
>>>       > + tmp[3] = product[10];
>>>       > + tmp[4] = product[11];
>>>       > + tmp[5] = product[12];
>>>       > + tmp[6] = product[13];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = product[11];
>>>       > + tmp[1] = product[12];
>>>       > + tmp[2] = product[13];
>>>       > + tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>>>       > + num_words_secp224r1) != 1) {
>>>       > + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +#else
>>>       > +static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t
>>>      *product) {
>>>       > + uint64_t tmp[num_words_secp224r1];
>>>       > + int carry = 0;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp224r1);
>>>       > + result[num_words_secp224r1 - 1] &= 0xffffffff;
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = 0;
>>>       > + tmp[1] = product[3] & 0xffffffff00000000ull;
>>>       > + tmp[2] = product[4];
>>>       > + tmp[3] = product[5] & 0xffffffff;
>>>       > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[1] = product[5] & 0xffffffff00000000ull;
>>>       > + tmp[2] = product[6];
>>>       > + tmp[3] = 0;
>>>       > + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = (product[3] >> 32) | (product[4] << 32);
>>>       > + tmp[1] = (product[4] >> 32) | (product[5] << 32);
>>>       > + tmp[2] = (product[5] >> 32) | (product[6] << 32);
>>>       > + tmp[3] = product[6] >> 32;
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
>>>       > + tmp[1] = product[6] >> 32;
>>>       > + tmp[2] = tmp[3] = 0;
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp224r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
>>>       > + num_words_secp224r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
>>>       > + num_words_secp224r1) != 1) {
>>>       > + mg_uecc_vli_sub(result, result, curve_secp224r1.p,
>>>      num_words_secp224r1);
>>>       > + }
>>>       > + }
>>>       > +}
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
>>>       >
>>>       > +#endif /* MG_UECC_SUPPORTS_secp224r1 */
>>>       >
>>>       > +#if MG_UECC_SUPPORTS_secp256r1
>>>       >
>>>       > -#ifndef MG_MAX_SSI_DEPTH
>>>       > -#define MG_MAX_SSI_DEPTH 5
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product);
>>>       > #endif
>>>       >
>>>       > -#ifndef MG_SSI_BUFSIZ
>>>       > -#define MG_SSI_BUFSIZ 1024
>>>       > +static const struct MG_UECC_Curve_t curve_secp256r1 = {
>>>       > + num_words_secp256r1,
>>>       > + num_bytes_secp256r1,
>>>       > + 256, /* num_n_bits */
>>>       > + {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
>>>       > + BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
>>>       > + BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
>>>       > + BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
>>>       > + BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
>>>       > +
>>>       > + BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
>>>       > + BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
>>>       > + BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
>>>       > + BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
>>>       > + {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
>>>       > + BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
>>>       > + BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
>>>       > + BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
>>>       > + &double_jacobian_default,
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + &mod_sqrt_default,
>>>       > +#endif
>>>       > + &x_side_default,
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + &vli_mmod_fast_secp256r1
>>>       > #endif
>>>       > +};
>>>       >
>>>       > -#if MG_ENABLE_SSI
>>>       > -static char *mg_ssi(const char *path, const char *root, int
>>>      depth) {
>>>       > - struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
>>>       > - FILE *fp = fopen(path, "rb");
>>>       > - if (fp != NULL) {
>>>       > - char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
>>>       > - int ch, intag = 0;
>>>       > - size_t len = 0;
>>>       > - buf[0] = arg[0] = '\0';
>>>       > - while ((ch = fgetc(fp)) != EOF) {
>>>       > - if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2]
>>>      == '-') {
>>>       > - buf[len++] = (char) (ch & 0xff);
>>>       > - buf[len] = '\0';
>>>       > - if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
>>>       > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
>>>       > - *p = (char *) path + strlen(path), *data;
>>>       > - while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
>>>       > - mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path,
>>>      arg);
>>>       > - if (depth < MG_MAX_SSI_DEPTH &&
>>>       > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>>>       > - mg_iobuf_add(&b, b.len, data, strlen(data));
>>>       > - free(data);
>>>       > - } else {
>>>       > - MG_ERROR(("%s: file=%s error or too deep", path, arg));
>>>       > - }
>>>       > - } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
>>>       > - char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
>>>       > - mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
>>>       > - if (depth < MG_MAX_SSI_DEPTH &&
>>>       > - (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
>>>       > - mg_iobuf_add(&b, b.len, data, strlen(data));
>>>       > - free(data);
>>>       > - } else {
>>>       > - MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
>>>       > - }
>>>       > - } else {
>>>       > - // Unknown SSI tag
>>>       > - MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
>>>       > - mg_iobuf_add(&b, b.len, buf, len);
>>>       > - }
>>>       > - intag = 0;
>>>       > - len = 0;
>>>       > - } else if (ch == '<') {
>>>       > - intag = 1;
>>>       > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>>>       > - len = 0;
>>>       > - buf[len++] = (char) (ch & 0xff);
>>>       > - } else if (intag) {
>>>       > - if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
>>>       > - intag = 0;
>>>       > - } else if (len >= sizeof(buf) - 2) {
>>>       > - MG_ERROR(("%s: SSI tag is too large", path));
>>>       > - len = 0;
>>>       > - }
>>>       > - buf[len++] = (char) (ch & 0xff);
>>>       > - } else {
>>>       > - buf[len++] = (char) (ch & 0xff);
>>>       > - if (len >= sizeof(buf)) {
>>>       > - mg_iobuf_add(&b, b.len, buf, len);
>>>       > - len = 0;
>>>       > - }
>>>       > - }
>>>       > +MG_UECC_Curve mg_uecc_secp256r1(void) {
>>>       > + return &curve_secp256r1;
>>>       > +}
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
>>>       > +/* Computes result = product % curve_p
>>>       > + from http://www.nsa.gov/ia/_files/nist-routines.pdf
>>>      <http://www.nsa.gov/ia/_files/nist-routines.pdf> */
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t
>>>      *product) {
>>>       > + uint8_t tmp[num_words_secp256r1];
>>>       > + int8_t carry;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
>>>       > + tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
>>>       > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>>>       > + tmp[12] = product[44];
>>>       > + tmp[13] = product[45];
>>>       > + tmp[14] = product[46];
>>>       > + tmp[15] = product[47];
>>>       > + tmp[16] = product[48];
>>>       > + tmp[17] = product[49];
>>>       > + tmp[18] = product[50];
>>>       > + tmp[19] = product[51];
>>>       > + tmp[20] = product[52];
>>>       > + tmp[21] = product[53];
>>>       > + tmp[22] = product[54];
>>>       > + tmp[23] = product[55];
>>>       > + tmp[24] = product[56];
>>>       > + tmp[25] = product[57];
>>>       > + tmp[26] = product[58];
>>>       > + tmp[27] = product[59];
>>>       > + tmp[28] = product[60];
>>>       > + tmp[29] = product[61];
>>>       > + tmp[30] = product[62];
>>>       > + tmp[31] = product[63];
>>>       > + carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[12] = product[48];
>>>       > + tmp[13] = product[49];
>>>       > + tmp[14] = product[50];
>>>       > + tmp[15] = product[51];
>>>       > + tmp[16] = product[52];
>>>       > + tmp[17] = product[53];
>>>       > + tmp[18] = product[54];
>>>       > + tmp[19] = product[55];
>>>       > + tmp[20] = product[56];
>>>       > + tmp[21] = product[57];
>>>       > + tmp[22] = product[58];
>>>       > + tmp[23] = product[59];
>>>       > + tmp[24] = product[60];
>>>       > + tmp[25] = product[61];
>>>       > + tmp[26] = product[62];
>>>       > + tmp[27] = product[63];
>>>       > + tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
>>>       > + carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s3 */
>>>       > + tmp[0] = product[32];
>>>       > + tmp[1] = product[33];
>>>       > + tmp[2] = product[34];
>>>       > + tmp[3] = product[35];
>>>       > + tmp[4] = product[36];
>>>       > + tmp[5] = product[37];
>>>       > + tmp[6] = product[38];
>>>       > + tmp[7] = product[39];
>>>       > + tmp[8] = product[40];
>>>       > + tmp[9] = product[41];
>>>       > + tmp[10] = product[42];
>>>       > + tmp[11] = product[43];
>>>       > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>>>       > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>>>       > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>>>       > + tmp[24] = product[56];
>>>       > + tmp[25] = product[57];
>>>       > + tmp[26] = product[58];
>>>       > + tmp[27] = product[59];
>>>       > + tmp[28] = product[60];
>>>       > + tmp[29] = product[61];
>>>       > + tmp[30] = product[62];
>>>       > + tmp[31] = product[63];
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s4 */
>>>       > + tmp[0] = product[36];
>>>       > + tmp[1] = product[37];
>>>       > + tmp[2] = product[38];
>>>       > + tmp[3] = product[39];
>>>       > + tmp[4] = product[40];
>>>       > + tmp[5] = product[41];
>>>       > + tmp[6] = product[42];
>>>       > + tmp[7] = product[43];
>>>       > + tmp[8] = product[44];
>>>       > + tmp[9] = product[45];
>>>       > + tmp[10] = product[46];
>>>       > + tmp[11] = product[47];
>>>       > + tmp[12] = product[52];
>>>       > + tmp[13] = product[53];
>>>       > + tmp[14] = product[54];
>>>       > + tmp[15] = product[55];
>>>       > + tmp[16] = product[56];
>>>       > + tmp[17] = product[57];
>>>       > + tmp[18] = product[58];
>>>       > + tmp[19] = product[59];
>>>       > + tmp[20] = product[60];
>>>       > + tmp[21] = product[61];
>>>       > + tmp[22] = product[62];
>>>       > + tmp[23] = product[63];
>>>       > + tmp[24] = product[52];
>>>       > + tmp[25] = product[53];
>>>       > + tmp[26] = product[54];
>>>       > + tmp[27] = product[55];
>>>       > + tmp[28] = product[32];
>>>       > + tmp[29] = product[33];
>>>       > + tmp[30] = product[34];
>>>       > + tmp[31] = product[35];
>>>       > + carry += mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = product[44];
>>>       > + tmp[1] = product[45];
>>>       > + tmp[2] = product[46];
>>>       > + tmp[3] = product[47];
>>>       > + tmp[4] = product[48];
>>>       > + tmp[5] = product[49];
>>>       > + tmp[6] = product[50];
>>>       > + tmp[7] = product[51];
>>>       > + tmp[8] = product[52];
>>>       > + tmp[9] = product[53];
>>>       > + tmp[10] = product[54];
>>>       > + tmp[11] = product[55];
>>>       > + tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
>>>       > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>>>       > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>>>       > + tmp[24] = product[32];
>>>       > + tmp[25] = product[33];
>>>       > + tmp[26] = product[34];
>>>       > + tmp[27] = product[35];
>>>       > + tmp[28] = product[40];
>>>       > + tmp[29] = product[41];
>>>       > + tmp[30] = product[42];
>>>       > + tmp[31] = product[43];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = product[48];
>>>       > + tmp[1] = product[49];
>>>       > + tmp[2] = product[50];
>>>       > + tmp[3] = product[51];
>>>       > + tmp[4] = product[52];
>>>       > + tmp[5] = product[53];
>>>       > + tmp[6] = product[54];
>>>       > + tmp[7] = product[55];
>>>       > + tmp[8] = product[56];
>>>       > + tmp[9] = product[57];
>>>       > + tmp[10] = product[58];
>>>       > + tmp[11] = product[59];
>>>       > + tmp[12] = product[60];
>>>       > + tmp[13] = product[61];
>>>       > + tmp[14] = product[62];
>>>       > + tmp[15] = product[63];
>>>       > + tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
>>>       > + tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
>>>       > + tmp[24] = product[36];
>>>       > + tmp[25] = product[37];
>>>       > + tmp[26] = product[38];
>>>       > + tmp[27] = product[39];
>>>       > + tmp[28] = product[44];
>>>       > + tmp[29] = product[45];
>>>       > + tmp[30] = product[46];
>>>       > + tmp[31] = product[47];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d3 */
>>>       > + tmp[0] = product[52];
>>>       > + tmp[1] = product[53];
>>>       > + tmp[2] = product[54];
>>>       > + tmp[3] = product[55];
>>>       > + tmp[4] = product[56];
>>>       > + tmp[5] = product[57];
>>>       > + tmp[6] = product[58];
>>>       > + tmp[7] = product[59];
>>>       > + tmp[8] = product[60];
>>>       > + tmp[9] = product[61];
>>>       > + tmp[10] = product[62];
>>>       > + tmp[11] = product[63];
>>>       > + tmp[12] = product[32];
>>>       > + tmp[13] = product[33];
>>>       > + tmp[14] = product[34];
>>>       > + tmp[15] = product[35];
>>>       > + tmp[16] = product[36];
>>>       > + tmp[17] = product[37];
>>>       > + tmp[18] = product[38];
>>>       > + tmp[19] = product[39];
>>>       > + tmp[20] = product[40];
>>>       > + tmp[21] = product[41];
>>>       > + tmp[22] = product[42];
>>>       > + tmp[23] = product[43];
>>>       > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>>>       > + tmp[28] = product[48];
>>>       > + tmp[29] = product[49];
>>>       > + tmp[30] = product[50];
>>>       > + tmp[31] = product[51];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d4 */
>>>       > + tmp[0] = product[56];
>>>       > + tmp[1] = product[57];
>>>       > + tmp[2] = product[58];
>>>       > + tmp[3] = product[59];
>>>       > + tmp[4] = product[60];
>>>       > + tmp[5] = product[61];
>>>       > + tmp[6] = product[62];
>>>       > + tmp[7] = product[63];
>>>       > + tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
>>>       > + tmp[12] = product[36];
>>>       > + tmp[13] = product[37];
>>>       > + tmp[14] = product[38];
>>>       > + tmp[15] = product[39];
>>>       > + tmp[16] = product[40];
>>>       > + tmp[17] = product[41];
>>>       > + tmp[18] = product[42];
>>>       > + tmp[19] = product[43];
>>>       > + tmp[20] = product[44];
>>>       > + tmp[21] = product[45];
>>>       > + tmp[22] = product[46];
>>>       > + tmp[23] = product[47];
>>>       > + tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
>>>       > + tmp[28] = product[52];
>>>       > + tmp[29] = product[53];
>>>       > + tmp[30] = product[54];
>>>       > + tmp[31] = product[55];
>>>       > + carry -= mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>>>       > + num_words_secp256r1) != 1) {
>>>       > + carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > }
>>>       > - if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
>>>       > - if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1); // nul-terminate
>>>       > - fclose(fp);
>>>       > }
>>>       > - (void) depth;
>>>       > - (void) root;
>>>       > - return (char *) b.buf;
>>>       > }
>>>       > -
>>>       > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>>>       > - const char *fullpath) {
>>>       > - const char *headers = "Content-Type: text/html;
>>>      charset=utf-8\r\n";
>>>       > - char *data = mg_ssi(fullpath, root, 0);
>>>       > - mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
>>>       > - free(data);
>>>       > +#elif MG_UECC_WORD_SIZE == 4
>>>       > +static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t
>>>      *product) {
>>>       > + uint32_t tmp[num_words_secp256r1];
>>>       > + int carry;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = tmp[1] = tmp[2] = 0;
>>>       > + tmp[3] = product[11];
>>>       > + tmp[4] = product[12];
>>>       > + tmp[5] = product[13];
>>>       > + tmp[6] = product[14];
>>>       > + tmp[7] = product[15];
>>>       > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[3] = product[12];
>>>       > + tmp[4] = product[13];
>>>       > + tmp[5] = product[14];
>>>       > + tmp[6] = product[15];
>>>       > + tmp[7] = 0;
>>>       > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
>>>      num_words_secp256r1);
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s3 */
>>>       > + tmp[0] = product[8];
>>>       > + tmp[1] = product[9];
>>>       > + tmp[2] = product[10];
>>>       > + tmp[3] = tmp[4] = tmp[5] = 0;
>>>       > + tmp[6] = product[14];
>>>       > + tmp[7] = product[15];
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s4 */
>>>       > + tmp[0] = product[9];
>>>       > + tmp[1] = product[10];
>>>       > + tmp[2] = product[11];
>>>       > + tmp[3] = product[13];
>>>       > + tmp[4] = product[14];
>>>       > + tmp[5] = product[15];
>>>       > + tmp[6] = product[13];
>>>       > + tmp[7] = product[8];
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = product[11];
>>>       > + tmp[1] = product[12];
>>>       > + tmp[2] = product[13];
>>>       > + tmp[3] = tmp[4] = tmp[5] = 0;
>>>       > + tmp[6] = product[8];
>>>       > + tmp[7] = product[10];
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = product[12];
>>>       > + tmp[1] = product[13];
>>>       > + tmp[2] = product[14];
>>>       > + tmp[3] = product[15];
>>>       > + tmp[4] = tmp[5] = 0;
>>>       > + tmp[6] = product[9];
>>>       > + tmp[7] = product[11];
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d3 */
>>>       > + tmp[0] = product[13];
>>>       > + tmp[1] = product[14];
>>>       > + tmp[2] = product[15];
>>>       > + tmp[3] = product[8];
>>>       > + tmp[4] = product[9];
>>>       > + tmp[5] = product[10];
>>>       > + tmp[6] = 0;
>>>       > + tmp[7] = product[12];
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d4 */
>>>       > + tmp[0] = product[14];
>>>       > + tmp[1] = product[15];
>>>       > + tmp[2] = 0;
>>>       > + tmp[3] = product[9];
>>>       > + tmp[4] = product[10];
>>>       > + tmp[5] = product[11];
>>>       > + tmp[6] = 0;
>>>       > + tmp[7] = product[13];
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>>>       > + num_words_secp256r1) != 1) {
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > + }
>>>       > + }
>>>       > }
>>>       > #else
>>>       > -void mg_http_serve_ssi(struct mg_connection *c, const char *root,
>>>       > - const char *fullpath) {
>>>       > - mg_http_reply(c, 501, NULL, "SSI not enabled");
>>>       > - (void) root, (void) fullpath;
>>>       > +static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t
>>>      *product) {
>>>       > + uint64_t tmp[num_words_secp256r1];
>>>       > + int carry;
>>>       > +
>>>       > + /* t */
>>>       > + mg_uecc_vli_set(result, product, num_words_secp256r1);
>>>       > +
>>>       > + /* s1 */
>>>       > + tmp[0] = 0;
>>>       > + tmp[1] = product[5] & 0xffffffff00000000U;
>>>       > + tmp[2] = product[6];
>>>       > + tmp[3] = product[7];
>>>       > + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s2 */
>>>       > + tmp[1] = product[6] << 32;
>>>       > + tmp[2] = (product[6] >> 32) | (product[7] << 32);
>>>       > + tmp[3] = product[7] >> 32;
>>>       > + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp,
>>>      num_words_secp256r1);
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s3 */
>>>       > + tmp[0] = product[4];
>>>       > + tmp[1] = product[5] & 0xffffffff;
>>>       > + tmp[2] = 0;
>>>       > + tmp[3] = product[7];
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* s4 */
>>>       > + tmp[0] = (product[4] >> 32) | (product[5] << 32);
>>>       > + tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
>>>       > + tmp[2] = product[7];
>>>       > + tmp[3] = (product[6] >> 32) | (product[4] << 32);
>>>       > + carry += (int) mg_uecc_vli_add(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d1 */
>>>       > + tmp[0] = (product[5] >> 32) | (product[6] << 32);
>>>       > + tmp[1] = (product[6] >> 32);
>>>       > + tmp[2] = 0;
>>>       > + tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d2 */
>>>       > + tmp[0] = product[6];
>>>       > + tmp[1] = product[7];
>>>       > + tmp[2] = 0;
>>>       > + tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d3 */
>>>       > + tmp[0] = (product[6] >> 32) | (product[7] << 32);
>>>       > + tmp[1] = (product[7] >> 32) | (product[4] << 32);
>>>       > + tmp[2] = (product[4] >> 32) | (product[5] << 32);
>>>       > + tmp[3] = (product[6] << 32);
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + /* d4 */
>>>       > + tmp[0] = product[7];
>>>       > + tmp[1] = product[4] & 0xffffffff00000000U;
>>>       > + tmp[2] = product[5];
>>>       > + tmp[3] = product[6] & 0xffffffff00000000U;
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, tmp,
>>>      num_words_secp256r1);
>>>       > +
>>>       > + if (carry < 0) {
>>>       > + do {
>>>       > + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > + } while (carry < 0);
>>>       > + } else {
>>>       > + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
>>>       > + num_words_secp256r1) != 1) {
>>>       > + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
>>>       > + num_words_secp256r1);
>>>       > + }
>>>       > + }
>>>       > }
>>>       > -#endif
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&
>>>      !asm_mmod_fast_secp256r1) */
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/str.c"
>>>       > +#endif /* MG_UECC_SUPPORTS_secp256r1 */
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp256k1
>>>       > +
>>>       > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
>>>      mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *Z1, MG_UECC_Curve curve);
>>>       > +static void x_side_secp256k1(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *x,
>>>       > + MG_UECC_Curve curve);
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product);
>>>       > #endif
>>>       >
>>>       > +static const struct MG_UECC_Curve_t curve_secp256k1 = {
>>>       > + num_words_secp256k1,
>>>       > + num_bytes_secp256k1,
>>>       > + 256, /* num_n_bits */
>>>       > + {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
>>>       > + BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
>>>       > + BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
>>>       > + BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
>>>       > + {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
>>>       > + BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
>>>       > + BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
>>>       > + BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
>>>       > +
>>>       > + BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
>>>       > + BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
>>>       > + BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
>>>       > + BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
>>>       > + {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
>>>       > + BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
>>>       > + &double_jacobian_secp256k1,
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > + &mod_sqrt_default,
>>>       > +#endif
>>>       > + &x_side_secp256k1,
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + &vli_mmod_fast_secp256k1
>>>       > +#endif
>>>       > +};
>>>       >
>>>       > -struct mg_str mg_str_s(const char *s) {
>>>       > - struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
>>>       > - return str;
>>>       > +MG_UECC_Curve mg_uecc_secp256k1(void) {
>>>       > + return &curve_secp256k1;
>>>       > }
>>>       >
>>>       > -struct mg_str mg_str_n(const char *s, size_t n) {
>>>       > - struct mg_str str = {s, n};
>>>       > - return str;
>>>       > -}
>>>       > +/* Double in place */
>>>       > +static void double_jacobian_secp256k1(mg_uecc_word_t *X1,
>>>      mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
>>>       > + /* t1 = X, t2 = Y, t3 = Z */
>>>       > + mg_uecc_word_t t4[num_words_secp256k1];
>>>       > + mg_uecc_word_t t5[num_words_secp256k1];
>>>       >
>>>       > -int mg_lower(const char *s) {
>>>       > - int c = *s;
>>>       > - if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
>>>       > - return c;
>>>       > -}
>>>       > + if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
>>>       > + return;
>>>       > + }
>>>       >
>>>       > -int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
>>>       > - int diff = 0;
>>>       > - if (len > 0) do {
>>>       > - diff = mg_lower(s1++) - mg_lower(s2++);
>>>       > - } while (diff == 0 && s1[-1] != '\0' && --len > 0);
>>>       > - return diff;
>>>       > + mg_uecc_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */
>>>       > + mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 =
>>>      A */
>>>       > + mg_uecc_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */
>>>       > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */
>>>       > + mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
>>>       > +
>>>       > + mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
>>>       > + num_words_secp256k1); /* t2 = 2*x1^2 */
>>>       > + mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
>>>       > + num_words_secp256k1); /* t2 = 3*x1^2 */
>>>       > + if (mg_uecc_vli_testBit(Y1, 0)) {
>>>       > + mg_uecc_word_t carry =
>>>       > + mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
>>>       > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
>>>       > + Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
>>>       > + } else {
>>>       > + mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
>>>       > + }
>>>       > + /* t2 = 3/2*(x1^2) = B */
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
>>>       > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
>>>       > + num_words_secp256k1); /* t1 = B^2 - A */
>>>       > + mg_uecc_vli_modSub(X1, X1, t4, curve->p,
>>>       > + num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
>>>       > +
>>>       > + mg_uecc_vli_modSub(t4, t4, X1, curve->p,
>>>       > + num_words_secp256k1); /* t4 = A - x3 */
>>>       > + mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A -
>>>      x3) */
>>>       > + mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
>>>       > + num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
>>>       > +}
>>>       > +
>>>       > +/* Computes result = x^3 + b. result must not overlap x. */
>>>       > +static void x_side_secp256k1(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *x,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */
>>>       > + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
>>>       > + mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
>>>       > + num_words_secp256k1); /* r = x^3 + b */
>>>       > +}
>>>       > +
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
>>>       > +static void omega_mult_secp256k1(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *right);
>>>       > +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
>>>       > + mg_uecc_word_t *product) {
>>>       > + mg_uecc_word_t tmp[2 * num_words_secp256k1];
>>>       > + mg_uecc_word_t carry;
>>>       > +
>>>       > + mg_uecc_vli_clear(tmp, num_words_secp256k1);
>>>       > + mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
>>>       > +
>>>       > + omega_mult_secp256k1(tmp,
>>>       > + product + num_words_secp256k1); /* (Rq, q) = q * c */
>>>       > +
>>>       > + carry = mg_uecc_vli_add(result, product, tmp,
>>>       > + num_words_secp256k1); /* (C, r) = r + q */
>>>       > + mg_uecc_vli_clear(product, num_words_secp256k1);
>>>       > + omega_mult_secp256k1(product, tmp + num_words_secp256k1); /*
>>>      Rq*c */
>>>       > + carry += mg_uecc_vli_add(result, result, product,
>>>       > + num_words_secp256k1); /* (C1, r) = r + Rq*c */
>>>       > +
>>>       > + while (carry > 0) {
>>>       > + --carry;
>>>       > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
>>>      num_words_secp256k1);
>>>       > + }
>>>       > + if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p,
>>>      num_words_secp256k1) >
>>>       > + 0) {
>>>       > + mg_uecc_vli_sub(result, result, curve_secp256k1.p,
>>>      num_words_secp256k1);
>>>       > + }
>>>       > }
>>>       >
>>>       > -int mg_casecmp(const char *s1, const char *s2) {
>>>       > - return mg_ncasecmp(s1, s2, (size_t) ~0);
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +static void omega_mult_secp256k1(uint8_t *result, const uint8_t
>>>      *right) {
>>>       > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>>>       > + mg_uecc_word_t r0 = 0;
>>>       > + mg_uecc_word_t r1 = 0;
>>>       > + mg_uecc_word_t r2 = 0;
>>>       > + wordcount_t k;
>>>       > +
>>>       > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>>>       > + muladd(0xD1, right[0], &r0, &r1, &r2);
>>>       > + result[0] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + /* r2 is still 0 */
>>>       > +
>>>       > + for (k = 1; k < num_words_secp256k1; ++k) {
>>>       > + muladd(0x03, right[k - 1], &r0, &r1, &r2);
>>>       > + muladd(0xD1, right[k], &r0, &r1, &r2);
>>>       > + result[k] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + r2 = 0;
>>>       > + }
>>>       > + muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
>>>       > + result[num_words_secp256k1] = r0;
>>>       > + result[num_words_secp256k1 + 1] = r1;
>>>       > + /* add the 2^32 multiple */
>>>       > + result[4 + num_words_secp256k1] =
>>>       > + mg_uecc_vli_add(result + 4, result + 4, right,
>>>      num_words_secp256k1);
>>>       > +}
>>>       > +#elif MG_UECC_WORD_SIZE == 4
>>>       > +static void omega_mult_secp256k1(uint32_t *result, const
>>>      uint32_t *right) {
>>>       > + /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>>>       > + uint32_t carry = 0;
>>>       > + wordcount_t k;
>>>       > +
>>>       > + for (k = 0; k < num_words_secp256k1; ++k) {
>>>       > + uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
>>>       > + result[k] = (uint32_t) p;
>>>       > + carry = p >> 32;
>>>       > + }
>>>       > + result[num_words_secp256k1] = carry;
>>>       > + /* add the 2^32 multiple */
>>>       > + result[1 + num_words_secp256k1] =
>>>       > + mg_uecc_vli_add(result + 1, result + 1, right,
>>>      num_words_secp256k1);
>>>       > }
>>>       > -
>>>       > -int mg_vcmp(const struct mg_str *s1, const char *s2) {
>>>       > - size_t n2 = strlen(s2), n1 = s1->len;
>>>       > - int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
>>>       > - if (r == 0) return (int) (n1 - n2);
>>>       > - return r;
>>>       > +#else
>>>       > +static void omega_mult_secp256k1(uint64_t *result, const
>>>      uint64_t *right) {
>>>       > + mg_uecc_word_t r0 = 0;
>>>       > + mg_uecc_word_t r1 = 0;
>>>       > + mg_uecc_word_t r2 = 0;
>>>       > + wordcount_t k;
>>>       > +
>>>       > + /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
>>>       > + for (k = 0; k < num_words_secp256k1; ++k) {
>>>       > + muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
>>>       > + result[k] = r0;
>>>       > + r0 = r1;
>>>       > + r1 = r2;
>>>       > + r2 = 0;
>>>       > + }
>>>       > + result[num_words_secp256k1] = r0;
>>>       > }
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && &&
>>>      !asm_mmod_fast_secp256k1) */
>>>       >
>>>       > -int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
>>>       > - size_t n2 = strlen(str2), n1 = str1->len;
>>>       > - int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
>>>       > - if (r == 0) return (int) (n1 - n2);
>>>       > - return r;
>>>       > +#endif /* MG_UECC_SUPPORTS_secp256k1 */
>>>       > +
>>>       > +#endif /* _UECC_CURVE_SPECIFIC_H_ */
>>>       > +
>>>       > +/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
>>>       > +#define EccPoint_isZero(point, curve) \
>>>       > + mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words *
>>>      2))
>>>       > +
>>>       > +/* Point multiplication algorithm using Montgomery's ladder with
>>>      co-Z
>>>       > +coordinates. From http://eprint.iacr.org/2011/338.pdf
>>>      <http://eprint.iacr.org/2011/338.pdf>
>>>       > +*/
>>>       > +
>>>       > +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
>>>       > +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>>>       > + const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(t1, Z, curve); /* z^2 */
>>>       > + mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
>>>       > + mg_uecc_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */
>>>       > + mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
>>>       > }
>>>       >
>>>       > -struct mg_str mg_strdup(const struct mg_str s) {
>>>       > - struct mg_str r = {NULL, 0};
>>>       > - if (s.len > 0 && s.ptr != NULL) {
>>>       > - char *sc = (char *) calloc(1, s.len + 1);
>>>       > - if (sc != NULL) {
>>>       > - memcpy(sc, s.ptr, s.len);
>>>       > - sc[s.len] = '\0';
>>>       > - r.ptr = sc;
>>>       > - r.len = s.len;
>>>       > - }
>>>       > +/* P = (x1, y1) => 2P, (x2, y2) => P' */
>>>       > +static void XYcZ_initial_double(mg_uecc_word_t *X1,
>>>      mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
>>>       > + const mg_uecc_word_t *const initial_Z,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > + if (initial_Z) {
>>>       > + mg_uecc_vli_set(z, initial_Z, num_words);
>>>       > + } else {
>>>       > + mg_uecc_vli_clear(z, num_words);
>>>       > + z[0] = 1;
>>>       > }
>>>       > - return r;
>>>       > -}
>>>       >
>>>       > -int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
>>>       > - size_t i = 0;
>>>       > - while (i < str1.len && i < str2.len) {
>>>       > - int c1 = str1.ptr[i];
>>>       > - int c2 = str2.ptr[i];
>>>       > - if (c1 < c2) return -1;
>>>       > - if (c1 > c2) return 1;
>>>       > - i++;
>>>       > + mg_uecc_vli_set(X2, X1, num_words);
>>>       > + mg_uecc_vli_set(Y2, Y1, num_words);
>>>       > +
>>>       > + apply_z(X1, Y1, z, curve);
>>>       > + curve->double_jacobian(X1, Y1, z, curve);
>>>       > + apply_z(X2, Y2, z, curve);
>>>       > +}
>>>       > +
>>>       > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
>>>       > + Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
>>>       > + or P => P', Q => P + Q
>>>       > +*/
>>>       > +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>>>      mg_uecc_word_t *X2,
>>>       > + mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
>>>       > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
>>>       > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
>>>      - x1 */
>>>       > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
>>>      = A */
>>>       > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
>>>       > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
>>>       > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
>>>      - y1 */
>>>       > + mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2
>>>      = D */
>>>       > +
>>>       > + mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D
>>>      - B */
>>>       > + mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D
>>>      - B - C = x3 */
>>>       > + mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C
>>>      - B */
>>>       > + mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */
>>>       > + mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B
>>>      - x3 */
>>>       > + mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 -
>>>      y1)*(B - x3) */
>>>       > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
>>>       > +
>>>       > + mg_uecc_vli_set(X2, t5, num_words);
>>>       > +}
>>>       > +
>>>       > +/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
>>>       > + Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
>>>       > + or P => P - Q, Q => P + Q
>>>       > +*/
>>>       > +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
>>>       > + mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
>>>       > + MG_UECC_Curve curve) {
>>>       > + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
>>>       > + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
>>>       > + mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2
>>>      - x1 */
>>>       > + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2
>>>      = A */
>>>       > + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
>>>       > + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
>>>       > + mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2
>>>      + y1 */
>>>       > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2
>>>      - y1 */
>>>       > +
>>>       > + mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C
>>>      - B */
>>>       > + mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C -
>>>      B) = E */
>>>       > + mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B
>>>      + C */
>>>       > + mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2
>>>      = D */
>>>       > + mg_uecc_vli_modSub(X2, X2, t6, curve->p,
>>>       > + num_words); /* t3 = D - (B + C) = x3 */
>>>       > +
>>>       > + mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B
>>>      - x3 */
>>>       > + mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 -
>>>      y1)*(B - x3) */
>>>       > + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
>>>       > + num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2
>>>      = F */
>>>       > + mg_uecc_vli_modSub(t7, t7, t6, curve->p,
>>>       > + num_words); /* t7 = F - (B + C) = x3' */
>>>       > + mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 =
>>>      x3' - B */
>>>       > + mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 =
>>>      (y2+y1)*(x3' - B) */
>>>       > + mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
>>>       > + num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
>>>       > +
>>>       > + mg_uecc_vli_set(X1, t7, num_words);
>>>       > +}
>>>       > +
>>>       > +/* result may overlap point. */
>>>       > +static void EccPoint_mult(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *point,
>>>       > + const mg_uecc_word_t *scalar,
>>>       > + const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
>>>       > + MG_UECC_Curve curve) {
>>>       > + /* R0 and R1 */
>>>       > + mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>>>       > + bitcount_t i;
>>>       > + mg_uecc_word_t nb;
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + mg_uecc_vli_set(Rx[1], point, num_words);
>>>       > + mg_uecc_vli_set(Ry[1], point + num_words, num_words);
>>>       > +
>>>       > + XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
>>>       > +
>>>       > + for (i = num_bits - 2; i > 0; --i) {
>>>       > + nb = !mg_uecc_vli_testBit(scalar, i);
>>>       > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
>>>       > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
>>>       > + }
>>>       > +
>>>       > + nb = !mg_uecc_vli_testBit(scalar, 0);
>>>       > + XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
>>>       > +
>>>       > + /* Find final 1/Z value. */
>>>       > + mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1
>>>      - X0 */
>>>       > + mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1
>>>      - X0) */
>>>       > + mg_uecc_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1
>>>      - X0) */
>>>       > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb
>>>      * (X1 - X0)) */
>>>       > + /* yP / (xP * Yb * (X1 - X0)) */
>>>       > + mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
>>>       > + mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
>>>       > + curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
>>>       > + /* End 1/Z calculation */
>>>       > +
>>>       > + XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
>>>       > + apply_z(Rx[0], Ry[0], z, curve);
>>>       > +
>>>       > + mg_uecc_vli_set(result, Rx[0], num_words);
>>>       > + mg_uecc_vli_set(result + num_words, Ry[0], num_words);
>>>       > +}
>>>       > +
>>>       > +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
>>>       > + mg_uecc_word_t *k0, mg_uecc_word_t *k1,
>>>       > + MG_UECC_Curve curve) {
>>>       > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>>>       > + bitcount_t num_n_bits = curve->num_n_bits;
>>>       > + mg_uecc_word_t carry =
>>>       > + mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
>>>       > + (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE *
>>>      8) &&
>>>       > + mg_uecc_vli_testBit(k0, num_n_bits));
>>>       > + mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
>>>       > + return carry;
>>>       > +}
>>>       > +
>>>       > +/* Generates a random integer in the range 0 < random < top.
>>>       > + Both random and top have num_words words. */
>>>       > +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t
>>>      *random,
>>>       > + const mg_uecc_word_t *top,
>>>       > + wordcount_t num_words) {
>>>       > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
>>>       > + mg_uecc_word_t tries;
>>>       > + bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
>>>       > +
>>>       > + if (!g_rng_function) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>>>       > + if (!g_rng_function((uint8_t *) random,
>>>       > + (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
>>>       > + return 0;
>>>       > + }
>>>       > + random[num_words - 1] &=
>>>       > + mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 -
>>>      num_bits));
>>>       > + if (!mg_uecc_vli_isZero(random, num_words) &&
>>>       > + mg_uecc_vli_cmp(top, random, num_words) == 1) {
>>>       > + return 1;
>>>       > + }
>>>       > }
>>>       > - if (i < str1.len) return 1;
>>>       > - if (i < str2.len) return -1;
>>>       > return 0;
>>>       > }
>>>       >
>>>       > -const char *mg_strstr(const struct mg_str haystack,
>>>       > - const struct mg_str needle) {
>>>       > - size_t i;
>>>       > - if (needle.len > haystack.len) return NULL;
>>>       > - for (i = 0; i <= haystack.len - needle.len; i++) {
>>>       > - if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
>>>       > - return haystack.ptr + i;
>>>       > +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t
>>>      *result,
>>>       > + mg_uecc_word_t *private_key,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
>>>       > + mg_uecc_word_t *initial_Z = 0;
>>>       > + mg_uecc_word_t carry;
>>>       > +
>>>       > + /* Regularize the bitcount for the private key so that
>>>      attackers cannot use a
>>>       > + side channel attack to learn the number of leading zeros. */
>>>       > + carry = regularize_k(private_key, tmp1, tmp2, curve);
>>>       > +
>>>       > + /* If an RNG function was specified, try to get a random
>>>      initial Z value to
>>>       > + improve protection against side-channel attacks. */
>>>       > + if (g_rng_function) {
>>>       > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
>>>      curve->num_words)) {
>>>       > + return 0;
>>>       > }
>>>       > + initial_Z = p2[carry];
>>>       > }
>>>       > - return NULL;
>>>       > + EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
>>>       > + (bitcount_t) (curve->num_n_bits + 1), curve);
>>>       > +
>>>       > + if (EccPoint_isZero(result, curve)) {
>>>       > + return 0;
>>>       > + }
>>>       > + return 1;
>>>       > }
>>>       >
>>>       > -static bool is_space(int c) {
>>>       > - return c == ' ' || c == '\r' || c == '\n' || c == '\t';
>>>       > +#if MG_UECC_WORD_SIZE == 1
>>>       > +
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
>>>      int num_bytes,
>>>       > + const uint8_t *native) {
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < num_bytes; ++i) {
>>>       > + bytes[i] = native[(num_bytes - 1) - i];
>>>       > + }
>>>       > }
>>>       >
>>>       > -struct mg_str mg_strstrip(struct mg_str s) {
>>>       > - while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
>>>       > - while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
>>>       > - return s;
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
>>>       > + const uint8_t *bytes,
>>>       > + int num_bytes) {
>>>       > + mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
>>>       > }
>>>       >
>>>       > -bool mg_match(struct mg_str s, struct mg_str p, struct mg_str
>>>      *caps) {
>>>       > - size_t i = 0, j = 0, ni = 0, nj = 0;
>>>       > - if (caps) caps->ptr = NULL, caps->len = 0;
>>>       > - while (i < p.len || j < s.len) {
>>>       > - if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] ==
>>>      p.ptr[i])) {
>>>       > - if (caps == NULL) {
>>>       > - } else if (p.ptr[i] == '?') {
>>>       > - caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap
>>>       > - caps++, caps->ptr = NULL, caps->len = 0; // Init next cap
>>>       > - } else if (caps->ptr != NULL && caps->len == 0) {
>>>       > - caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize
>>>      current cap
>>>       > - caps++, caps->len = 0, caps->ptr = NULL; // Init next cap
>>>       > - }
>>>       > - i++, j++;
>>>       > - } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
>>>       > - if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];
>>>      // Init cap
>>>       > - ni = i++, nj = j + 1;
>>>       > - } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' ||
>>>      s.ptr[j] != '/')) {
>>>       > - i = ni, j = nj;
>>>       > - if (caps && caps->ptr == NULL && caps->len == 0) {
>>>       > - caps--, caps->len = 0; // Restart previous cap
>>>       > - }
>>>       > - } else {
>>>       > - return false;
>>>       > - }
>>>       > - }
>>>       > - if (caps && caps->ptr && caps->len == 0) {
>>>       > - caps->len = (size_t) (&s.ptr[j] - caps->ptr);
>>>       > +#else
>>>       > +
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes,
>>>      int num_bytes,
>>>       > + const mg_uecc_word_t *native) {
>>>       > + int i;
>>>       > + for (i = 0; i < num_bytes; ++i) {
>>>       > + unsigned b = (unsigned) (num_bytes - 1 - i);
>>>       > + bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
>>>       > + (8 * (b % MG_UECC_WORD_SIZE)));
>>>       > }
>>>       > - return true;
>>>       > }
>>>       >
>>>       > -bool mg_globmatch(const char *s1, size_t n1, const char *s2,
>>>      size_t n2) {
>>>       > - return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
>>>       > +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t
>>>      *native,
>>>       > + const uint8_t *bytes,
>>>       > + int num_bytes) {
>>>       > + int i;
>>>       > + mg_uecc_vli_clear(native,
>>>       > + (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
>>>       > + MG_UECC_WORD_SIZE));
>>>       > + for (i = 0; i < num_bytes; ++i) {
>>>       > + unsigned b = (unsigned) (num_bytes - 1 - i);
>>>       > + native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
>>>       > + << (8 * (b % MG_UECC_WORD_SIZE));
>>>       > + }
>>>       > }
>>>       >
>>>       > -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t
>>>      *koff,
>>>       > - size_t *klen, size_t *voff, size_t *vlen, char delim) {
>>>       > - size_t kvlen, kl;
>>>       > - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;)
>>>      kvlen++;
>>>       > - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
>>>       > - if (koff != NULL) *koff = ofs;
>>>       > - if (klen != NULL) *klen = kl;
>>>       > - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
>>>       > - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
>>>       > - ofs += kvlen + 1;
>>>       > - return ofs > n ? n : ofs;
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +
>>>       > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
>>>       > + MG_UECC_Curve curve) {
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
>>>       > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>>>       > +#else
>>>       > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>>>       > +#endif
>>>       > + mg_uecc_word_t tries;
>>>       > +
>>>       > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>>>       > + if (!mg_uecc_generate_random_int(_private, curve->n,
>>>       > + BITS_TO_WORDS(curve->num_n_bits))) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + if (EccPoint_compute_public_key(_public, _private, curve)) {
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_nativeToBytes(private_key,
>>>      BITS_TO_BYTES(curve->num_n_bits),
>>>       > + _private);
>>>       > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
>>>       > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>>>      curve->num_bytes,
>>>       > + _public + curve->num_words);
>>>       > +#endif
>>>       > + return 1;
>>>       > + }
>>>       > + }
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
>>>      *v, char sep) {
>>>       > - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
>>>       > - if (s->ptr == NULL || s->len == 0) return 0;
>>>       > - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
>>>       > - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
>>>       > - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
>>>       > - *s = mg_str_n(s->ptr + off, s->len - off);
>>>       > - return off > 0;
>>>       > +int mg_uecc_shared_secret(const uint8_t *public_key, const
>>>      uint8_t *private_key,
>>>       > + uint8_t *secret, MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>>>       > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>>>       > +
>>>       > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t *p2[2] = {_private, tmp};
>>>       > + mg_uecc_word_t *initial_Z = 0;
>>>       > + mg_uecc_word_t carry;
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > + wordcount_t num_bytes = curve->num_bytes;
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) _private, private_key, num_bytes);
>>>       > + bcopy((uint8_t *) _public, public_key, num_bytes * 2);
>>>       > +#else
>>>       > + mg_uecc_vli_bytesToNative(_private, private_key,
>>>       > + BITS_TO_BYTES(curve->num_n_bits));
>>>       > + mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
>>>       > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
>>>      num_bytes,
>>>       > + num_bytes);
>>>       > +#endif
>>>       > +
>>>       > + /* Regularize the bitcount for the private key so that
>>>      attackers cannot use a
>>>       > + side channel attack to learn the number of leading zeros. */
>>>       > + carry = regularize_k(_private, _private, tmp, curve);
>>>       > +
>>>       > + /* If an RNG function was specified, try to get a random
>>>      initial Z value to
>>>       > + improve protection against side-channel attacks. */
>>>       > + if (g_rng_function) {
>>>       > + if (!mg_uecc_generate_random_int(p2[carry], curve->p,
>>>      num_words)) {
>>>       > + return 0;
>>>       > + }
>>>       > + initial_Z = p2[carry];
>>>       > + }
>>>       > +
>>>       > + EccPoint_mult(_public, _public, p2[!carry], initial_Z,
>>>       > + (bitcount_t) (curve->num_n_bits + 1), curve);
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
>>>       > +#else
>>>       > + mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
>>>       > +#endif
>>>       > + return !EccPoint_isZero(_public, curve);
>>>       > }
>>>       >
>>>       > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
>>>      mg_str *v) {
>>>       > - return mg_split(s, k, v, ',');
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
>>>      *compressed,
>>>       > + MG_UECC_Curve curve) {
>>>       > + wordcount_t i;
>>>       > + for (i = 0; i < curve->num_bytes; ++i) {
>>>       > + compressed[i + 1] = public_key[i];
>>>       > + }
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
>>>       > +#else
>>>       > + compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
>>>       > +#endif
>>>       > }
>>>       >
>>>       > -char *mg_hex(const void *buf, size_t len, char *to) {
>>>       > - const unsigned char *p = (const unsigned char *) buf;
>>>       > - const char *hex = "0123456789abcdef";
>>>       > - size_t i = 0;
>>>       > - for (; len--; p++) {
>>>       > - to[i++] = hex[p[0] >> 4];
>>>       > - to[i++] = hex[p[0] & 0x0f];
>>>       > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
>>>      *public_key,
>>>       > + MG_UECC_Curve curve) {
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
>>>       > +#else
>>>       > + mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
>>>       > +#endif
>>>       > + mg_uecc_word_t *y = point + curve->num_words;
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy(public_key, compressed + 1, curve->num_bytes);
>>>       > +#else
>>>       > + mg_uecc_vli_bytesToNative(point, compressed + 1,
>>>      curve->num_bytes);
>>>       > +#endif
>>>       > + curve->x_side(y, point, curve);
>>>       > + curve->mod_sqrt(y, curve);
>>>       > +
>>>       > + if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
>>>       > + mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
>>>       > }
>>>       > - to[i] = '\0';
>>>       > - return to;
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
>>>       > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>>>      curve->num_bytes, y);
>>>       > +#endif
>>>       > }
>>>       > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>>>       > +
>>>       > +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t
>>>      *point,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > +
>>>       > + /* The point at infinity is invalid. */
>>>       > + if (EccPoint_isZero(point, curve)) {
>>>       > + return 0;
>>>       > + }
>>>       >
>>>       > -static unsigned char mg_unhex_nimble(unsigned char c) {
>>>       > - return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
>>>       > - : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
>>>       > - : (unsigned char) (c - 'W');
>>>       > + /* x and y must be smaller than p. */
>>>       > + if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
>>>       > + mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words)
>>>      != 1) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
>>>       > + curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
>>>       > +
>>>       > + /* Make sure that y^2 == x^3 + ax + b */
>>>       > + return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
>>>       > }
>>>       >
>>>       > -unsigned long mg_unhexn(const char *s, size_t len) {
>>>       > - unsigned long i = 0, v = 0;
>>>       > - for (i = 0; i < len; i++) v <<= 4, v |=
>>>      mg_unhex_nimble(((uint8_t *) s)[i]);
>>>       > - return v;
>>>       > +int mg_uecc_valid_public_key(const uint8_t *public_key,
>>>      MG_UECC_Curve curve) {
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>>>       > +#else
>>>       > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>>>       > +#endif
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
>>>       > + mg_uecc_vli_bytesToNative(_public + curve->num_words,
>>>       > + public_key + curve->num_bytes, curve->num_bytes);
>>>       > +#endif
>>>       > + return mg_uecc_valid_point(_public, curve);
>>>       > }
>>>       >
>>>       > -void mg_unhex(const char *buf, size_t len, unsigned char *to) {
>>>       > - size_t i;
>>>       > - for (i = 0; i < len; i += 2) {
>>>       > - to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
>>>       > +int mg_uecc_compute_public_key(const uint8_t *private_key,
>>>      uint8_t *public_key,
>>>       > + MG_UECC_Curve curve) {
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
>>>       > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>>>       > +#else
>>>       > + mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>>>       > +#endif
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_bytesToNative(_private, private_key,
>>>       > + BITS_TO_BYTES(curve->num_n_bits));
>>>       > +#endif
>>>       > +
>>>       > + /* Make sure the private key is in the range [1, n-1]. */
>>>       > + if (mg_uecc_vli_isZero(_private,
>>>      BITS_TO_WORDS(curve->num_n_bits))) {
>>>       > + return 0;
>>>       > }
>>>       > -}
>>>       >
>>>       > -uint64_t mg_tou64(struct mg_str str) {
>>>       > - uint64_t result = 0;
>>>       > - size_t i = 0;
>>>       > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
>>>      '\t')) i++;
>>>       > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>>>       > - result *= 10;
>>>       > - result += (unsigned) (str.ptr[i] - '0');
>>>       > - i++;
>>>       > + if (mg_uecc_vli_cmp(curve->n, _private,
>>>      BITS_TO_WORDS(curve->num_n_bits)) !=
>>>       > + 1) {
>>>       > + return 0;
>>>       > }
>>>       > - return result;
>>>       > +
>>>       > + /* Compute public key. */
>>>       > + if (!EccPoint_compute_public_key(_public, _private, curve)) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
>>>       > + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes,
>>>      curve->num_bytes,
>>>       > + _public + curve->num_words);
>>>       > +#endif
>>>       > + return 1;
>>>       > }
>>>       >
>>>       > -int64_t mg_to64(struct mg_str str) {
>>>       > - int64_t result = 0, neg = 1, max = 922337203685477570 /*
>>>      INT64_MAX/10-10 */;
>>>       > - size_t i = 0;
>>>       > - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] ==
>>>      '\t')) i++;
>>>       > - if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
>>>       > - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
>>>       > - if (result > max) return 0;
>>>       > - result *= 10;
>>>       > - result += (str.ptr[i] - '0');
>>>       > - i++;
>>>       > +/* -------- ECDSA code -------- */
>>>       > +
>>>       > +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
>>>       > + unsigned bits_size, MG_UECC_Curve curve) {
>>>       > + unsigned num_n_bytes = (unsigned)
>>>      BITS_TO_BYTES(curve->num_n_bits);
>>>       > + unsigned num_n_words = (unsigned)
>>>      BITS_TO_WORDS(curve->num_n_bits);
>>>       > + int shift;
>>>       > + mg_uecc_word_t carry;
>>>       > + mg_uecc_word_t *ptr;
>>>       > +
>>>       > + if (bits_size > num_n_bytes) {
>>>       > + bits_size = num_n_bytes;
>>>       > + }
>>>       > +
>>>       > + mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) native, bits, bits_size);
>>>       > +#else
>>>       > + mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
>>>       > +#endif
>>>       > + if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
>>>       > + return;
>>>       > + }
>>>       > + shift = (int) bits_size * 8 - curve->num_n_bits;
>>>       > + carry = 0;
>>>       > + ptr = native + num_n_words;
>>>       > + while (ptr-- > native) {
>>>       > + mg_uecc_word_t temp = *ptr;
>>>       > + *ptr = (temp >> shift) | carry;
>>>       > + carry = temp << (MG_UECC_WORD_BITS - shift);
>>>       > + }
>>>       > +
>>>       > + /* Reduce mod curve_n */
>>>       > + if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t)
>>>      num_n_words) !=
>>>       > + 1) {
>>>       > + mg_uecc_vli_sub(native, native, curve->n, (wordcount_t)
>>>      num_n_words);
>>>       > }
>>>       > - return result * neg;
>>>       > }
>>>       >
>>>       > -char *mg_remove_double_dots(char *s) {
>>>       > - char *saved = s, *p = s;
>>>       > - while (*s != '\0') {
>>>       > - *p++ = *s++;
>>>       > - if (s[-1] == '/' || s[-1] == '\\') {
>>>       > - while (s[0] != '\0') {
>>>       > - if (s[0] == '/' || s[0] == '\\') {
>>>       > - s++;
>>>       > - } else if (s[0] == '.' && s[1] == '.' &&
>>>       > - (s[2] == '/' || s[2] == '\\')) {
>>>       > - s += 2;
>>>       > - } else {
>>>       > - break;
>>>       > - }
>>>       > - }
>>>       > +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
>>>       > + const uint8_t *message_hash,
>>>       > + unsigned hash_size, mg_uecc_word_t *k,
>>>       > + uint8_t *signature,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t s[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t *k2[2] = {tmp, s};
>>>       > + mg_uecc_word_t *initial_Z = 0;
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
>>>       > +#else
>>>       > + mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
>>>       > +#endif
>>>       > + mg_uecc_word_t carry;
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>>>       > + bitcount_t num_n_bits = curve->num_n_bits;
>>>       > +
>>>       > + /* Make sure 0 < k < curve_n */
>>>       > + if (mg_uecc_vli_isZero(k, num_words) ||
>>>       > + mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + carry = regularize_k(k, tmp, s, curve);
>>>       > + /* If an RNG function was specified, try to get a random
>>>      initial Z value to
>>>       > + improve protection against side-channel attacks. */
>>>       > + if (g_rng_function) {
>>>       > + if (!mg_uecc_generate_random_int(k2[carry], curve->p,
>>>      num_words)) {
>>>       > + return 0;
>>>       > }
>>>       > + initial_Z = k2[carry];
>>>       > + }
>>>       > + EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
>>>       > + (bitcount_t) (num_n_bits + 1), curve);
>>>       > + if (mg_uecc_vli_isZero(p, num_words)) {
>>>       > + return 0;
>>>       > }
>>>       > - *p = '\0';
>>>       > - return saved;
>>>       > -}
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/timer.c"
>>>       > + /* If an RNG function was specified, get a random number
>>>       > + to prevent side channel analysis of k. */
>>>       > + if (!g_rng_function) {
>>>       > + mg_uecc_vli_clear(tmp, num_n_words);
>>>       > + tmp[0] = 1;
>>>       > + } else if (!mg_uecc_generate_random_int(tmp, curve->n,
>>>      num_n_words)) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + /* Prevent side channel analysis of mg_uecc_vli_modInv() to
>>>      determine
>>>       > + bits of k / the private key by premultiplying by a random
>>>      number */
>>>       > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' =
>>>      rand * k */
>>>       > + mg_uecc_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */
>>>       > + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1
>>>      / k */
>>>       > +
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
>>>       > + mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /*
>>>      store r */
>>>       > #endif
>>>       >
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) tmp, private_key,
>>>      BITS_TO_BYTES(curve->num_n_bits));
>>>       > +#else
>>>       > + mg_uecc_vli_bytesToNative(tmp, private_key,
>>>       > + BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
>>>       > +#endif
>>>       >
>>>       > + s[num_n_words - 1] = 0;
>>>       > + mg_uecc_vli_set(s, p, num_words);
>>>       > + mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s =
>>>      r*d */
>>>       >
>>>       > -#define MG_TIMER_CALLED 4
>>>       > + bits2int(tmp, message_hash, hash_size, curve);
>>>       > + mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e
>>>      + r*d */
>>>       > + mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e
>>>      + r*d) / k */
>>>       > + if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t)
>>>      curve->num_bytes * 8) {
>>>       > + return 0;
>>>       > + }
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
>>>       > + curve->num_bytes);
>>>       > +#else
>>>       > + mg_uecc_vli_nativeToBytes(signature + curve->num_bytes,
>>>      curve->num_bytes, s);
>>>       > +#endif
>>>       > + return 1;
>>>       > +}
>>>       >
>>>       > -void mg_timer_init(struct mg_timer **head, struct mg_timer *t,
>>>      uint64_t ms,
>>>       > - unsigned flags, void (*fn)(void *), void *arg) {
>>>       > - t->id = 0, t->period_ms = ms, t->expire = 0;
>>>       > - t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
>>>       > - *head = t;
>>>       > +#if 0
>>>       > +/* For testing - sign with an explicitly specified k value */
>>>       > +int mg_uecc_sign_with_k(const uint8_t *private_key, const
>>>      uint8_t *message_hash,
>>>       > + unsigned hash_size, const uint8_t *k, uint8_t *signature,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
>>>       > + bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits),
>>>      curve);
>>>       > + return mg_uecc_sign_with_k_internal(private_key, message_hash,
>>>      hash_size, k2,
>>>       > + signature, curve);
>>>       > }
>>>       > +#endif
>>>       >
>>>       > -void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
>>>       > - while (*head && *head != t) head = &(*head)->next;
>>>       > - if (*head) *head = t->next;
>>>       > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
>>>      *message_hash,
>>>       > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t k[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tries;
>>>       > +
>>>       > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>>>       > + if (!mg_uecc_generate_random_int(k, curve->n,
>>>       > + BITS_TO_WORDS(curve->num_n_bits))) {
>>>       > + return 0;
>>>       > + }
>>>       > +
>>>       > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
>>>      hash_size, k,
>>>       > + signature, curve)) {
>>>       > + return 1;
>>>       > + }
>>>       > + }
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -// t: expiration time, prd: period, now: current time. Return
>>>      true if expired
>>>       > -bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
>>>       > - if (now + prd < *t) *t = 0; // Time wrapped? Reset timer
>>>       > - if (*t == 0) *t = now + prd; // Firt poll? Set expiration
>>>       > - if (*t > now) return false; // Not expired yet, return
>>>       > - *t = (now - *t) > prd ? now + prd : *t + prd; // Next
>>>      expiration time
>>>       > - return true; // Expired, return true
>>>       > +/* Compute an HMAC using K as a key (as in RFC 6979). Note that
>>>      K is always
>>>       > + the same size as the hash result size. */
>>>       > +static void HMAC_init(const MG_UECC_HashContext *hash_context,
>>>       > + const uint8_t *K) {
>>>       > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
>>>       > + unsigned i;
>>>       > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
>>>      0x36;
>>>       > + for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
>>>       > +
>>>       > + hash_context->init_hash(hash_context);
>>>       > + hash_context->update_hash(hash_context, pad,
>>>      hash_context->block_size);
>>>       > }
>>>       >
>>>       > -void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
>>>       > - struct mg_timer *t, *tmp;
>>>       > - for (t = *head; t != NULL; t = tmp) {
>>>       > - bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
>>>       > - !(t->flags & MG_TIMER_CALLED); // Handle MG_TIMER_NOW only once
>>>       > - bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
>>>       > - tmp = t->next;
>>>       > - if (!once && !expired) continue;
>>>       > - if ((t->flags & MG_TIMER_REPEAT) || !(t->flags &
>>>      MG_TIMER_CALLED)) {
>>>       > - t->fn(t->arg);
>>>       > +static void HMAC_update(const MG_UECC_HashContext *hash_context,
>>>       > + const uint8_t *message, unsigned message_size) {
>>>       > + hash_context->update_hash(hash_context, message, message_size);
>>>       > +}
>>>       > +
>>>       > +static void HMAC_finish(const MG_UECC_HashContext *hash_context,
>>>       > + const uint8_t *K, uint8_t *result) {
>>>       > + uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
>>>       > + unsigned i;
>>>       > + for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^
>>>      0x5c;
>>>       > + for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
>>>       > +
>>>       > + hash_context->finish_hash(hash_context, result);
>>>       > +
>>>       > + hash_context->init_hash(hash_context);
>>>       > + hash_context->update_hash(hash_context, pad,
>>>      hash_context->block_size);
>>>       > + hash_context->update_hash(hash_context, result,
>>>      hash_context->result_size);
>>>       > + hash_context->finish_hash(hash_context, result);
>>>       > +}
>>>       > +
>>>       > +/* V = HMAC_K(V) */
>>>       > +static void update_V(const MG_UECC_HashContext *hash_context,
>>>      uint8_t *K,
>>>       > + uint8_t *V) {
>>>       > + HMAC_init(hash_context, K);
>>>       > + HMAC_update(hash_context, V, hash_context->result_size);
>>>       > + HMAC_finish(hash_context, K, V);
>>>       > +}
>>>       > +
>>>       > +/* Deterministic signing, similar to RFC 6979. Differences are:
>>>       > + * We just use H(m) directly rather than bits2octets(H(m))
>>>       > + (it is not reduced modulo curve_n).
>>>       > + * We generate a value for k (aka T) directly rather than
>>>      converting
>>>       > + endianness.
>>>       > +
>>>       > + Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped
>>>      0x00 or 0x01) /
>>>       > + <HMAC pad> */
>>>       > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
>>>       > + const uint8_t *message_hash, unsigned hash_size,
>>>       > + const MG_UECC_HashContext *hash_context,
>>>       > + uint8_t *signature, MG_UECC_Curve curve) {
>>>       > + uint8_t *K = hash_context->tmp;
>>>       > + uint8_t *V = K + hash_context->result_size;
>>>       > + wordcount_t num_bytes = curve->num_bytes;
>>>       > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>>>       > + bitcount_t num_n_bits = curve->num_n_bits;
>>>       > + mg_uecc_word_t tries;
>>>       > + unsigned i;
>>>       > + for (i = 0; i < hash_context->result_size; ++i) {
>>>       > + V[i] = 0x01;
>>>       > + K[i] = 0;
>>>       > + }
>>>       > +
>>>       > + /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
>>>       > + HMAC_init(hash_context, K);
>>>       > + V[hash_context->result_size] = 0x00;
>>>       > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>>>       > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
>>>       > + HMAC_update(hash_context, message_hash, hash_size);
>>>       > + HMAC_finish(hash_context, K, K);
>>>       > +
>>>       > + update_V(hash_context, K, V);
>>>       > +
>>>       > + /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
>>>       > + HMAC_init(hash_context, K);
>>>       > + V[hash_context->result_size] = 0x01;
>>>       > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>>>       > + HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
>>>       > + HMAC_update(hash_context, message_hash, hash_size);
>>>       > + HMAC_finish(hash_context, K, K);
>>>       > +
>>>       > + update_V(hash_context, K, V);
>>>       > +
>>>       > + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
>>>       > + mg_uecc_word_t T[MG_UECC_MAX_WORDS];
>>>       > + uint8_t *T_ptr = (uint8_t *) T;
>>>       > + wordcount_t T_bytes = 0;
>>>       > + for (;;) {
>>>       > + update_V(hash_context, K, V);
>>>       > + for (i = 0; i < hash_context->result_size; ++i) {
>>>       > + T_ptr[T_bytes++] = V[i];
>>>       > + if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
>>>       > + goto filled;
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > + filled:
>>>       > + if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 >
>>>      num_n_bits) {
>>>       > + mg_uecc_word_t mask = (mg_uecc_word_t) -1;
>>>       > + T[num_n_words - 1] &=
>>>       > + mask >>
>>>       > + ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
>>>       > + }
>>>       > +
>>>       > + if (mg_uecc_sign_with_k_internal(private_key, message_hash,
>>>      hash_size, T,
>>>       > + signature, curve)) {
>>>       > + return 1;
>>>       > }
>>>       > - t->flags |= MG_TIMER_CALLED;
>>>       > +
>>>       > + /* K = HMAC_K(V || 0x00) */
>>>       > + HMAC_init(hash_context, K);
>>>       > + V[hash_context->result_size] = 0x00;
>>>       > + HMAC_update(hash_context, V, hash_context->result_size + 1);
>>>       > + HMAC_finish(hash_context, K, K);
>>>       > +
>>>       > + update_V(hash_context, K, V);
>>>       > }
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/tls_dummy.c"
>>>       > +static bitcount_t smax(bitcount_t a, bitcount_t b) {
>>>       > + return (a > b ? a : b);
>>>       > +}
>>>       > +
>>>       > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
>>>      *message_hash,
>>>       > + unsigned hash_size, const uint8_t *signature,
>>>       > + MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t z[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
>>>       > + mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
>>>       > + const mg_uecc_word_t *points[4];
>>>       > + const mg_uecc_word_t *point;
>>>       > + bitcount_t num_bits;
>>>       > + bitcount_t i;
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
>>>       > +#else
>>>       > + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
>>>       > #endif
>>>       > + mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
>>>       > + wordcount_t num_words = curve->num_words;
>>>       > + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
>>>       >
>>>       > + rx[num_n_words - 1] = 0;
>>>       > + r[num_n_words - 1] = 0;
>>>       > + s[num_n_words - 1] = 0;
>>>       >
>>>       > -#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL &&
>>>      !MG_ENABLE_CUSTOM_TLS
>>>       > -void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > - (void) opts;
>>>       > - mg_error(c, "TLS is not enabled");
>>>       > -}
>>>       > -void mg_tls_handshake(struct mg_connection *c) {
>>>       > - (void) c;
>>>       > -}
>>>       > -void mg_tls_free(struct mg_connection *c) {
>>>       > - (void) c;
>>>       > -}
>>>       > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>>>       > -}
>>>       > -long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > - return c == NULL || buf == NULL || len == 0 ? 0 : -1;
>>>       > -}
>>>       > -size_t mg_tls_pending(struct mg_connection *c) {
>>>       > - (void) c;
>>>       > - return 0;
>>>       > -}
>>>       > +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > + bcopy((uint8_t *) r, signature, curve->num_bytes);
>>>       > + bcopy((uint8_t *) s, signature + curve->num_bytes,
>>>      curve->num_bytes);
>>>       > +#else
>>>       > + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
>>>       > + mg_uecc_vli_bytesToNative(_public + num_words, public_key +
>>>      curve->num_bytes,
>>>       > + curve->num_bytes);
>>>       > + mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
>>>       > + mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes,
>>>      curve->num_bytes);
>>>       > #endif
>>>       >
>>>       > -#ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/tls_mbed.c"
>>>       > -#endif
>>>       > + /* r, s must not be 0. */
>>>       > + if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s,
>>>      num_words)) {
>>>       > + return 0;
>>>       > + }
>>>       >
>>>       > + /* r, s must be < n. */
>>>       > + if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
>>>       > + mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
>>>       > + return 0;
>>>       > + }
>>>       >
>>>       > + /* Calculate u1 and u2. */
>>>       > + mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
>>>       > + u1[num_n_words - 1] = 0;
>>>       > + bits2int(u1, message_hash, hash_size, curve);
>>>       > + mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 =
>>>      e/s */
>>>       > + mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 =
>>>      r/s */
>>>       > +
>>>       > + /* Calculate sum = G + Q. */
>>>       > + mg_uecc_vli_set(sum, _public, num_words);
>>>       > + mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
>>>       > + mg_uecc_vli_set(tx, curve->G, num_words);
>>>       > + mg_uecc_vli_set(ty, curve->G + num_words, num_words);
>>>       > + mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2
>>>      - x1 */
>>>       > + XYcZ_add(tx, ty, sum, sum + num_words, curve);
>>>       > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
>>>       > + apply_z(sum, sum + num_words, z, curve);
>>>       > +
>>>       > + /* Use Shamir's trick to calculate u1*G + u2*Q */
>>>       > + points[0] = 0;
>>>       > + points[1] = curve->G;
>>>       > + points[2] = _public;
>>>       > + points[3] = sum;
>>>       > + num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
>>>       > + mg_uecc_vli_numBits(u2, num_n_words));
>>>       > + point =
>>>       > + points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
>>>       > + ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
>>>       > + mg_uecc_vli_set(rx, point, num_words);
>>>       > + mg_uecc_vli_set(ry, point + num_words, num_words);
>>>       > + mg_uecc_vli_clear(z, num_words);
>>>       > + z[0] = 1;
>>>       > +
>>>       > + for (i = num_bits - 2; i >= 0; --i) {
>>>       > + mg_uecc_word_t index;
>>>       > + curve->double_jacobian(rx, ry, z, curve);
>>>       > +
>>>       > + index = (!!mg_uecc_vli_testBit(u1, i)) |
>>>       > + (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
>>>       > + point = points[index];
>>>       > + if (point) {
>>>       > + mg_uecc_vli_set(tx, point, num_words);
>>>       > + mg_uecc_vli_set(ty, point + num_words, num_words);
>>>       > + apply_z(tx, ty, z, curve);
>>>       > + mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2
>>>      - x1 */
>>>       > + XYcZ_add(tx, ty, rx, ry, curve);
>>>       > + mg_uecc_vli_modMult_fast(z, z, tz, curve);
>>>       > + }
>>>       > + }
>>>       >
>>>       > + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
>>>       > + apply_z(rx, ry, z, curve);
>>>       >
>>>       > -#if MG_ENABLE_MBEDTLS
>>>       > + /* v = x1 (mod n) */
>>>       > + if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
>>>       > + mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
>>>       > + }
>>>       >
>>>       > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>>>      0x03000000
>>>       > -#define MGRNG , rng_get, NULL
>>>       > -#else
>>>       > -#define MGRNG
>>>       > -#endif
>>>       > + /* Accept only if v == r. */
>>>       > + return (int) (mg_uecc_vli_equal(rx, r, num_words));
>>>       > +}
>>>       >
>>>       > -void mg_tls_free(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - if (tls != NULL) {
>>>       > - free(tls->cafile);
>>>       > - mbedtls_ssl_free(&tls->ssl);
>>>       > - mbedtls_pk_free(&tls->pk);
>>>       > - mbedtls_x509_crt_free(&tls->ca);
>>>       > - mbedtls_x509_crt_free(&tls->cert);
>>>       > - mbedtls_ssl_config_free(&tls->conf);
>>>       > - free(tls);
>>>       > - c->tls = NULL;
>>>       > - }
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +
>>>       > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
>>>       > + return curve->num_words;
>>>       > }
>>>       >
>>>       > -static int mg_net_send(void *ctx, const unsigned char *buf,
>>>      size_t len) {
>>>       > - long n = mg_io_send((struct mg_connection *) ctx, buf, len);
>>>       > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>>>       > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>>>       > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>>>       > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
>>>       > - return (int) n;
>>>       > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
>>>       > + return curve->num_bytes;
>>>       > }
>>>       >
>>>       > -static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
>>>       > - long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
>>>       > - MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
>>>       > - if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
>>>       > - if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
>>>       > - if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
>>>       > - return (int) n;
>>>       > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
>>>       > + return curve->num_bytes * 8;
>>>       > }
>>>       >
>>>       > -void mg_tls_handshake(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - int rc = mbedtls_ssl_handshake(&tls->ssl);
>>>       > - if (rc == 0) { // Success
>>>       > - MG_DEBUG(("%lu success", c->id));
>>>       > - c->is_tls_hs = 0;
>>>       > - mg_call(c, MG_EV_TLS_HS, NULL);
>>>       > - } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
>>>       > - rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
>>>       > - MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id,
>>>      c->is_connecting,
>>>       > - c->is_tls_hs, rc, -rc));
>>>       > - } else {
>>>       > - mg_error(c, "TLS handshake: -%#x", -rc); // Error
>>>       > - }
>>>       > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
>>>       > + return BITS_TO_WORDS(curve->num_n_bits);
>>>       > }
>>>       >
>>>       > -static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
>>>       > - mg_random(buf, len);
>>>       > - (void) ctx;
>>>       > - return 0;
>>>       > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
>>>       > + return BITS_TO_BYTES(curve->num_n_bits);
>>>       > }
>>>       >
>>>       > -static void debug_cb(void *c, int lev, const char *s, int n,
>>>      const char *s2) {
>>>       > - n = (int) strlen(s2) - 1;
>>>       > - MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id,
>>>      lev, n, s2));
>>>       > - (void) s;
>>>       > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
>>>       > + return curve->num_n_bits;
>>>       > }
>>>       >
>>>       > -#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >=
>>>      0x03000000
>>>       > -static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
>>>       > - (void) p_rng;
>>>       > - mg_random(buf, len);
>>>       > - return 0;
>>>       > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
>>>       > + return curve->p;
>>>       > }
>>>       > -#endif
>>>       >
>>>       > -static struct mg_str mg_loadfile(struct mg_fs *fs, const char
>>>      *path) {
>>>       > - size_t n = 0;
>>>       > - if (path[0] == '-') return mg_str(path);
>>>       > - char *p = mg_file_read(fs, path, &n);
>>>       > - return mg_str_n(p, n);
>>>       > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
>>>       > + return curve->n;
>>>       > }
>>>       >
>>>       > -void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > - struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
>>>       > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>>>       > - int rc = 0;
>>>       > - c->tls = tls;
>>>       > - if (c->tls == NULL) {
>>>       > - mg_error(c, "TLS OOM");
>>>       > - goto fail;
>>>       > - }
>>>       > - MG_DEBUG(("%lu Setting TLS", c->id));
>>>       > - mbedtls_ssl_init(&tls->ssl);
>>>       > - mbedtls_ssl_config_init(&tls->conf);
>>>       > - mbedtls_x509_crt_init(&tls->ca);
>>>       > - mbedtls_x509_crt_init(&tls->cert);
>>>       > - mbedtls_pk_init(&tls->pk);
>>>       > - mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
>>>       > -#if defined(MG_MBEDTLS_DEBUG_LEVEL)
>>>       > - mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
>>>       > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
>>>       > + return curve->G;
>>>       > +}
>>>       > +
>>>       > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
>>>       > + return curve->b;
>>>       > +}
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
>>>       > + curve->mod_sqrt(a, curve);
>>>       > +}
>>>       > #endif
>>>       > - if ((rc = mbedtls_ssl_config_defaults(
>>>       > - &tls->conf,
>>>       > - c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
>>>       > - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
>>>       > - mg_error(c, "tls defaults %#x", -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
>>>       > - if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
>>>       > - mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
>>>       > - } else if (opts->ca != NULL && opts->ca[0] != '\0') {
>>>       > -#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
>>>       > - tls->cafile = strdup(opts->ca);
>>>       > - rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile,
>>>      NULL);
>>>       > - if (rc != 0) {
>>>       > - mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > +
>>>       > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
>>>      mg_uecc_word_t *product,
>>>       > + MG_UECC_Curve curve) {
>>>       > +#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
>>>       > + curve->mmod_fast(result, product);
>>>       > #else
>>>       > - struct mg_str s = mg_loadfile(fs, opts->ca);
>>>       > - rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len
>>>      + 1);
>>>       > - if (opts->ca[0] != '-') free((char *) s.ptr);
>>>       > - if (rc != 0) {
>>>       > - mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
>>>       > + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
>>>       > #endif
>>>       > - if (opts->srvname.len > 0) {
>>>       > - char *x = mg_mprintf("%.*s", (int) opts->srvname.len,
>>>      opts->srvname.ptr);
>>>       > - mbedtls_ssl_set_hostname(&tls->ssl, x);
>>>       > - free(x);
>>>       > - }
>>>       > - mbedtls_ssl_conf_authmode(&tls->conf,
>>>      MBEDTLS_SSL_VERIFY_REQUIRED);
>>>       > - }
>>>       > - if (opts->cert != NULL && opts->cert[0] != '\0') {
>>>       > - struct mg_str s = mg_loadfile(fs, opts->cert);
>>>       > - const char *key = opts->certkey == NULL ? opts->cert :
>>>      opts->certkey;
>>>       > - rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr,
>>>      s.len + 1);
>>>       > - if (opts->cert[0] != '-') free((char *) s.ptr);
>>>       > - if (rc != 0) {
>>>       > - mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - s = mg_loadfile(fs, key);
>>>       > - rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len +
>>>      1, NULL,
>>>       > - 0 MGRNG);
>>>       > - if (key[0] != '-') free((char *) s.ptr);
>>>       > - if (rc != 0) {
>>>       > - mg_error(c, "tls key(%s) %#x", key, -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
>>>       > - if (rc != 0) {
>>>       > - mg_error(c, "own cert %#x", -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - }
>>>       > - if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
>>>       > - mg_error(c, "setup err %#x", -rc);
>>>       > - goto fail;
>>>       > - }
>>>       > - c->tls = tls;
>>>       > - c->is_tls = 1;
>>>       > - c->is_tls_hs = 1;
>>>       > - mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
>>>       > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>>>      0) {
>>>       > - mg_tls_handshake(c);
>>>       > - }
>>>       > - return;
>>>       > -fail:
>>>       > - mg_tls_free(c);
>>>       > }
>>>       >
>>>       > -size_t mg_tls_pending(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
>>>       > -}
>>>       > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *point,
>>>       > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
>>>       > + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
>>>       > + mg_uecc_word_t *p2[2] = {tmp1, tmp2};
>>>       > + mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
>>>       >
>>>       > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
>>>       > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>>>      MBEDTLS_ERR_SSL_WANT_WRITE)
>>>       > - return MG_IO_WAIT;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > + EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits +
>>>      1, curve);
>>>       > }
>>>       >
>>>       > -long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
>>>       > - if (n == MBEDTLS_ERR_SSL_WANT_READ || n ==
>>>      MBEDTLS_ERR_SSL_WANT_WRITE)
>>>       > - return MG_IO_WAIT;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > -}
>>>       > -#endif
>>>       > +#endif /* MG_UECC_ENABLE_VLI_API */
>>>       > +#endif // MG_TLS_BUILTIN
>>>       > +// End of uecc BSD-2
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "src/tls_openssl.c"
>>>       > +#line 1 "src/tls_x25519.c"
>>>       > #endif
>>>       > +/**
>>>       > + * Adapted from STROBE: https://strobe.sourceforge.io/
>>>      <https://strobe.sourceforge.io/>
>>>       > + * Copyright (c) 2015-2016 Cryptography Research, Inc.
>>>       > + * Author: Mike Hamburg
>>>       > + * License: MIT License
>>>       > + */
>>>       >
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_OPENSSL
>>>       > -static int mg_tls_err(struct mg_tls *tls, int res) {
>>>       > - int err = SSL_get_error(tls->ssl, res);
>>>       > - // We've just fetched the last error from the queue.
>>>       > - // Now we need to clear the error queue. If we do not, then the
>>>      following
>>>       > - // can happen (actually reported):
>>>       > - // - A new connection is accept()-ed with cert error (e.g.
>>>      self-signed cert)
>>>       > - // - Since all accept()-ed connections share listener's context,
>>>       > - // - *ALL* SSL accepted connection report read error on the
>>>      next poll cycle.
>>>       > - // Thus a single errored connection can close all the rest,
>>>      unrelated ones.
>>>       > - // Clearing the error keeps the shared SSL_CTX in an OK state.
>>>       > +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
>>>       >
>>>       > - if (err != 0) ERR_print_errors_fp(stderr);
>>>       > - ERR_clear_error();
>>>       > - if (err == SSL_ERROR_WANT_READ) return 0;
>>>       > - if (err == SSL_ERROR_WANT_WRITE) return 0;
>>>       > - return err;
>>>       > -}
>>>       > +#define X25519_WBITS 32
>>>       >
>>>       > -void mg_tls_init(struct mg_connection *c, const struct
>>>      mg_tls_opts *opts) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
>>>       > - const char *id = "mongoose";
>>>       > - static unsigned char s_initialised = 0;
>>>       > - int rc;
>>>       > +typedef uint32_t limb_t;
>>>       > +typedef uint64_t dlimb_t;
>>>       > +typedef int64_t sdlimb_t;
>>>       >
>>>       > - if (tls == NULL) {
>>>       > - mg_error(c, "TLS OOM");
>>>       > - goto fail;
>>>       > - }
>>>       > +#define NLIMBS (256 / X25519_WBITS)
>>>       > +typedef limb_t mg_fe[NLIMBS];
>>>       >
>>>       > - if (!s_initialised) {
>>>       > - SSL_library_init();
>>>       > - s_initialised++;
>>>       > - }
>>>       > - MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
>>>       > - opts->ca == NULL ? "null" : opts->ca,
>>>       > - opts->cert == NULL ? "null" : opts->cert,
>>>       > - opts->certkey == NULL ? "null" : opts->certkey));
>>>       > - tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
>>>       > - : SSL_CTX_new(SSLv23_server_method());
>>>       > - if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
>>>       > - mg_error(c, "SSL_new");
>>>       > - goto fail;
>>>       > - }
>>>       > - SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
>>>       > - (unsigned) strlen(id));
>>>       > - // Disable deprecated protocols
>>>       > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
>>>       > - SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
>>>       > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
>>>       > - SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
>>>       > -#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
>>>       > - SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
>>>       > -#endif
>>>       > -#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
>>>       > - SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
>>>       > -#endif
>>>       > +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand,
>>>      limb_t mier) {
>>>       > + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
>>>       > + *carry = (limb_t) (tmp >> X25519_WBITS);
>>>       > + return (limb_t) tmp;
>>>       > +}
>>>       >
>>>       > - if (opts->ca != NULL && opts->ca[0] != '\0') {
>>>       > - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER |
>>>      SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
>>>       > - NULL);
>>>       > - if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca,
>>>      NULL)) != 1) {
>>>       > - mg_error(c, "load('%s') %d err %d", opts->ca, rc,
>>>      mg_tls_err(tls, rc));
>>>       > - goto fail;
>>>       > - }
>>>       > - }
>>>       > - if (opts->cert != NULL && opts->cert[0] != '\0') {
>>>       > - const char *key = opts->certkey;
>>>       > - if (key == NULL) key = opts->cert;
>>>       > - if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) !=
>>>      1) {
>>>       > - mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
>>>       > - goto fail;
>>>       > - } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) !=
>>>      1) {
>>>       > - mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
>>>       > - goto fail;
>>>       > -#if OPENSSL_VERSION_NUMBER > 0x10100000L
>>>       > - } else if ((rc = SSL_use_certificate_chain_file(tls->ssl,
>>>      opts->cert)) !=
>>>       > - 1) {
>>>       > - mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
>>>       > - goto fail;
>>>       > -#endif
>>>       > - } else {
>>>       > - SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
>>>       > -#if OPENSSL_VERSION_NUMBER > 0x10002000L
>>>       > - SSL_set_ecdh_auto(tls->ssl, 1);
>>>       > -#endif
>>>       > - }
>>>       > - }
>>>       > - if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl,
>>>      opts->ciphers);
>>>       > -#if OPENSSL_VERSION_NUMBER >= 0x10100000L
>>>       > - if (opts->srvname.len > 0) {
>>>       > - char *s = mg_mprintf("%.*s", (int) opts->srvname.len,
>>>      opts->srvname.ptr);
>>>       > - SSL_set1_host(tls->ssl, s);
>>>       > - free(s);
>>>       > - }
>>>       > -#endif
>>>       > - c->tls = tls;
>>>       > - c->is_tls = 1;
>>>       > - c->is_tls_hs = 1;
>>>       > - if (c->is_client && c->is_resolving == 0 && c->is_connecting ==
>>>      0) {
>>>       > - mg_tls_handshake(c);
>>>       > - }
>>>       > - MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" :
>>>      "client"));
>>>       > - return;
>>>       > -fail:
>>>       > - c->is_closing = 1;
>>>       > - free(tls);
>>>       > +// These functions are implemented in terms of umaal on ARM
>>>       > +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
>>>       > + dlimb_t total = (dlimb_t) *carry + acc + mand;
>>>       > + *carry = (limb_t) (total >> X25519_WBITS);
>>>       > + return (limb_t) total;
>>>       > +}
>>>       > +
>>>       > +static limb_t adc0(limb_t *carry, limb_t acc) {
>>>       > + dlimb_t total = (dlimb_t) *carry + acc;
>>>       > + *carry = (limb_t) (total >> X25519_WBITS);
>>>       > + return (limb_t) total;
>>>       > +}
>>>       > +
>>>       > +// - Precondition: carry is small.
>>>       > +// - Invariant: result of propagate is < 2^255 + 1 word
>>>       > +// - In particular, always less than 2p.
>>>       > +// - Also, output x >= min(x,19)
>>>       > +static void propagate(mg_fe x, limb_t over) {
>>>       > + unsigned i;
>>>       > + limb_t carry;
>>>       > + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
>>>       > + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
>>>       > +
>>>       > + carry = over * 19;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + x[i] = adc0(&carry, x[i]);
>>>       > + }
>>>       > }
>>>       >
>>>       > -void mg_tls_handshake(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - int rc;
>>>       > - SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
>>>       > - rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
>>>       > - if (rc == 1) {
>>>       > - MG_DEBUG(("%lu success", c->id));
>>>       > - c->is_tls_hs = 0;
>>>       > - mg_call(c, MG_EV_TLS_HS, NULL);
>>>       > - } else {
>>>       > - int code = mg_tls_err(tls, rc);
>>>       > - if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
>>>       > +static void add(mg_fe out, const mg_fe a, const mg_fe b) {
>>>       > + unsigned i;
>>>       > + limb_t carry = 0;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + out[i] = adc(&carry, a[i], b[i]);
>>>       > }
>>>       > + propagate(out, carry);
>>>       > }
>>>       >
>>>       > -void mg_tls_free(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - if (tls == NULL) return;
>>>       > - SSL_free(tls->ssl);
>>>       > - SSL_CTX_free(tls->ctx);
>>>       > - free(tls);
>>>       > - c->tls = NULL;
>>>       > +static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
>>>       > + unsigned i;
>>>       > + sdlimb_t carry = -38;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + carry = carry + a[i] - b[i];
>>>       > + out[i] = (limb_t) carry;
>>>       > + carry >>= X25519_WBITS;
>>>       > + }
>>>       > + propagate(out, (limb_t) (1 + carry));
>>>       > +}
>>>       > +
>>>       > +// `b` can contain less than 8 limbs, thus we use `limb_t *`
>>>      instead of `mg_fe`
>>>       > +// to avoid build warnings
>>>       > +static void mul(mg_fe out, const mg_fe a, const limb_t *b,
>>>      unsigned nb) {
>>>       > + limb_t accum[2 * NLIMBS] = {0};
>>>       > + unsigned i, j;
>>>       > +
>>>       > + limb_t carry2;
>>>       > + for (i = 0; i < nb; i++) {
>>>       > + limb_t mand = b[i];
>>>       > + carry2 = 0;
>>>       > + for (j = 0; j < NLIMBS; j++) {
>>>       > + limb_t tmp; // "a" may be misaligned
>>>       > + memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy
>>>       > + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
>>>       > + }
>>>       > + accum[i + j] = carry2;
>>>       > + }
>>>       > +
>>>       > + carry2 = 0;
>>>       > + for (j = 0; j < NLIMBS; j++) {
>>>       > + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
>>>       > + }
>>>       > + propagate(out, carry2);
>>>       > }
>>>       >
>>>       > -size_t mg_tls_pending(struct mg_connection *c) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
>>>       > +static void sqr(mg_fe out, const mg_fe a) {
>>>       > + mul(out, a, a, NLIMBS);
>>>       > +}
>>>       > +static void mul1(mg_fe out, const mg_fe a) {
>>>       > + mul(out, a, out, NLIMBS);
>>>       > +}
>>>       > +static void sqr1(mg_fe a) {
>>>       > + mul1(a, a);
>>>       > }
>>>       >
>>>       > -long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - int n = SSL_read(tls->ssl, buf, (int) len);
>>>       > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
>>>       > + limb_t doswap) {
>>>       > + unsigned i;
>>>       > + for (i = 0; i < 2 * NLIMBS; i++) {
>>>       > + limb_t xor_ab = (a[i] ^ b[i]) & doswap;
>>>       > + a[i] ^= xor_ab;
>>>       > + b[i] ^= xor_ab;
>>>       > + }
>>>       > }
>>>       >
>>>       > -long mg_tls_send(struct mg_connection *c, const void *buf,
>>>      size_t len) {
>>>       > - struct mg_tls *tls = (struct mg_tls *) c->tls;
>>>       > - int n = SSL_write(tls->ssl, buf, (int) len);
>>>       > - if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
>>>       > - if (n <= 0) return MG_IO_ERR;
>>>       > - return n;
>>>       > +// Canonicalize a field element x, reducing it to the least
>>>      residue which is
>>>       > +// congruent to it mod 2^255-19
>>>       > +// - Precondition: x < 2^255 + 1 word
>>>       > +static limb_t canon(mg_fe x) {
>>>       > + // First, add 19.
>>>       > + unsigned i;
>>>       > + limb_t carry0 = 19;
>>>       > + limb_t res;
>>>       > + sdlimb_t carry;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + x[i] = adc0(&carry0, x[i]);
>>>       > + }
>>>       > + propagate(x, carry0);
>>>       > +
>>>       > + // Here, 19 <= x2 < 2^255
>>>       > + // - This is because we added 19, so before propagate it can't
>>>      be less
>>>       > + // than 19. After propagate, it still can't be less than 19,
>>>      because if
>>>       > + // propagate does anything it adds 19.
>>>       > + // - We know that the high bit must be clear, because either
>>>      the input was ~
>>>       > + // 2^255 + one word + 19 (in which case it propagates to at
>>>      most 2 words) or
>>>       > + // it was < 2^255. So now, if we subtract 19, we will get back
>>>      to something in
>>>       > + // [0,2^255-19).
>>>       > + carry = -19;
>>>       > + res = 0;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + carry += x[i];
>>>       > + res |= x[i] = (limb_t) carry;
>>>       > + carry >>= X25519_WBITS;
>>>       > + }
>>>       > + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
>>>       > +}
>>>       > +
>>>       > +static const limb_t a24[1] = {121665};
>>>       > +
>>>       > +static void ladder_part1(mg_fe xs[5]) {
>>>       > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
>>>      = xs[4];
>>>       > + add(t1, x2, z2); // t1 = A
>>>       > + sub(z2, x2, z2); // z2 = B
>>>       > + add(x2, x3, z3); // x2 = C
>>>       > + sub(z3, x3, z3); // z3 = D
>>>       > + mul1(z3, t1); // z3 = DA
>>>       > + mul1(x2, z2); // x3 = BC
>>>       > + add(x3, z3, x2); // x3 = DA+CB
>>>       > + sub(z3, z3, x2); // z3 = DA-CB
>>>       > + sqr1(t1); // t1 = AA
>>>       > + sqr1(z2); // z2 = BB
>>>       > + sub(x2, t1, z2); // x2 = E = AA-BB
>>>       > + mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24
>>>       > + add(z2, z2, t1); // z2 = E*a24 + AA
>>>       > +}
>>>       > +
>>>       > +static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
>>>       > + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1
>>>      = xs[4];
>>>       > + sqr1(z3); // z3 = (DA-CB)^2
>>>       > + mul1(z3, x1); // z3 = x1 * (DA-CB)^2
>>>       > + sqr1(x3); // x3 = (DA+CB)^2
>>>       > + mul1(z2, x2); // z2 = AA*(E*a24+AA)
>>>       > + sub(x2, t1, x2); // x2 = BB again
>>>       > + mul1(x2, t1); // x2 = AA*BB
>>>       > +}
>>>       > +
>>>       > +static void x25519_core(mg_fe xs[5], const uint8_t
>>>      scalar[X25519_BYTES],
>>>       > + const uint8_t *x1, int clamp) {
>>>       > + int i;
>>>       > + mg_fe x1_limbs;
>>>       > + limb_t swap = 0;
>>>       > + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
>>>       > + memset(xs, 0, 4 * sizeof(mg_fe));
>>>       > + x2[0] = z3[0] = 1;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + x3[i] = x1_limbs[i] =
>>>       > + MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
>>>       > + }
>>>       > +
>>>       > + for (i = 255; i >= 0; i--) {
>>>       > + uint8_t bytei = scalar[i / 8];
>>>       > + limb_t doswap;
>>>       > + if (clamp) {
>>>       > + if (i / 8 == 0) {
>>>       > + bytei &= (uint8_t) ~7U;
>>>       > + } else if (i / 8 == X25519_BYTES - 1) {
>>>       > + bytei &= 0x7F;
>>>       > + bytei |= 0x40;
>>>       > + }
>>>       > + }
>>>       > + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
>>>       > + condswap(x2, x3, swap ^ doswap);
>>>       > + swap = doswap;
>>>       > +
>>>       > + ladder_part1(xs);
>>>       > + ladder_part2(xs, (const limb_t *) x1_limbs);
>>>       > + }
>>>       > + condswap(x2, x3, swap);
>>>       > +}
>>>       > +
>>>       > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
>>>      scalar[X25519_BYTES],
>>>       > + const uint8_t x1[X25519_BYTES], int clamp) {
>>>       > + int i, ret;
>>>       > + mg_fe xs[5], out_limbs;
>>>       > + limb_t *x2, *z2, *z3, *prev;
>>>       > + static const struct {
>>>       > + uint8_t a, c, n;
>>>       > + } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3,
>>>      1, 1},
>>>       > + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
>>>       > + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}};
>>>       > + x25519_core(xs, scalar, x1, clamp);
>>>       > +
>>>       > + // Precomputed inversion chain
>>>       > + x2 = xs[0];
>>>       > + z2 = xs[1];
>>>       > + z3 = xs[3];
>>>       > +
>>>       > + prev = z2;
>>>       > + for (i = 0; i < 13; i++) {
>>>       > + int j;
>>>       > + limb_t *a = xs[steps[i].a];
>>>       > + for (j = steps[i].n; j > 0; j--) {
>>>       > + sqr(a, prev);
>>>       > + prev = a;
>>>       > + }
>>>       > + mul1(a, xs[steps[i].c]);
>>>       > + }
>>>       > +
>>>       > + // Here prev = z3
>>>       > + // x2 /= z2
>>>       > + mul(out_limbs, x2, z3, NLIMBS);
>>>       > + ret = (int) canon(out_limbs);
>>>       > + if (!clamp) ret = 0;
>>>       > + for (i = 0; i < NLIMBS; i++) {
>>>       > + uint32_t n = out_limbs[i];
>>>       > + out[i * 4] = (uint8_t) (n & 0xff);
>>>       > + out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
>>>       > + out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
>>>       > + out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
>>>       > + }
>>>       > + return ret;
>>>       > }
>>>       > -#endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > #line 1 "src/url.c"
>>>       > @@ -5382,7 +14797,7 @@ struct url {
>>>       > int mg_url_is_ssl(const char *url) {
>>>       > return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6)
>>>      == 0 ||
>>>       > strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
>>>       > - strncmp(url, "tls:", 4) == 0;
>>>       > + strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
>>>       > }
>>>       >
>>>       > static struct url urlparse(const char *url) {
>>>       > @@ -5464,6 +14879,14 @@ struct mg_str mg_url_pass(const char *url) {
>>>       > #endif
>>>       >
>>>       >
>>>       > +// Not using memset for zeroing memory, cause it can be dropped
>>>      by compiler
>>>       > +// See https://github.com/cesanta/mongoose/pull/1265
>>>      <https://github.com/cesanta/mongoose/pull/1265>
>>>       > +void mg_bzero(volatile unsigned char *buf, size_t len) {
>>>       > + if (buf != NULL) {
>>>       > + while (len--) *buf++ = 0;
>>>       > + }
>>>       > +}
>>>       > +
>>>       > #if MG_ENABLE_CUSTOM_RANDOM
>>>       > #else
>>>       > void mg_random(void *buf, size_t len) {
>>>       > @@ -5512,11 +14935,16 @@ uint16_t mg_ntohs(uint16_t net) {
>>>       > }
>>>       >
>>>       > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
>>>       > - int i;
>>>       > + static const uint32_t crclut[16] = {
>>>       > + // table for polynomial 0xEDB88320 (reflected)
>>>       > + 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190,
>>>      0x6B6B51F4,
>>>       > + 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8,
>>>      0xCB61B38C,
>>>       > + 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
>>>       > crc = ~crc;
>>>       > while (len--) {
>>>       > - crc ^= *(unsigned char *) buf++;
>>>       > - for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320
>>>      : crc >> 1;
>>>       > + uint8_t b = *(uint8_t *) buf++;
>>>       > + crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
>>>       > + crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
>>>       > }
>>>       > return ~crc;
>>>       > }
>>>       > @@ -5539,18 +14967,36 @@ static int parse_net(const char *spec,
>>>      uint32_t *net, uint32_t *mask) {
>>>       > return len;
>>>       > }
>>>       >
>>>       > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
>>>       > - struct mg_str k, v;
>>>       > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
>>>       > + struct mg_str entry;
>>>       > int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set,
>>>      deny by default
>>>       > - while (mg_commalist(&acl, &k, &v)) {
>>>       > - uint32_t net, mask;
>>>       > - if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
>>>       > - if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
>>>       > - if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
>>>       > + uint32_t remote_ip4;
>>>       > + if (remote_ip->is_ip6) {
>>>       > + return -1; // TODO(): handle IPv6 ACL and addresses
>>>       > + } else { // IPv4
>>>       > + memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
>>>       > + while (mg_span(acl, &entry, &acl, ',')) {
>>>       > + uint32_t net, mask;
>>>       > + if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
>>>       > + if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
>>>       > + if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
>>>       > + }
>>>       > }
>>>       > return allowed == '+';
>>>       > }
>>>       >
>>>       > +bool mg_path_is_sane(const struct mg_str path) {
>>>       > + const char *s = path.buf;
>>>       > + size_t n = path.len;
>>>       > + if (path.buf[0] == '.' && path.buf[1] == '.') return false; //
>>>      Starts with ..
>>>       > + for (; s[0] != '\0' && n > 0; s++, n--) {
>>>       > + if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir?
>>>       > + if (s[1] == '.' && s[2] == '.') return false; // Starts with ..
>>>       > + }
>>>       > + }
>>>       > + return true;
>>>       > +}
>>>       > +
>>>       > #if MG_ENABLE_CUSTOM_MILLIS
>>>       > #else
>>>       > uint64_t mg_millis(void) {
>>>       > @@ -5558,9 +15004,8 @@ uint64_t mg_millis(void) {
>>>       > return GetTickCount();
>>>       > #elif MG_ARCH == MG_ARCH_RP2040
>>>       > return time_us_64() / 1000;
>>>       > -#elif MG_ARCH == MG_ARCH_ESP32
>>>       > - return esp_timer_get_time() / 1000;
>>>       > -#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
>>>       > +#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
>>>       > + MG_ARCH == MG_ARCH_FREERTOS
>>>       > return xTaskGetTickCount() * portTICK_PERIOD_MS;
>>>       > #elif MG_ARCH == MG_ARCH_AZURERTOS
>>>       > return tx_time_get() * (1000 /* MS per SEC */ /
>>>      TX_TIMER_TICKS_PER_SECOND);
>>>       > @@ -5568,6 +15013,12 @@ uint64_t mg_millis(void) {
>>>       > return (uint64_t) Clock_getTicks();
>>>       > #elif MG_ARCH == MG_ARCH_ZEPHYR
>>>       > return (uint64_t) k_uptime_get();
>>>       > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
>>>       > + return (uint64_t) rt_time_get();
>>>       > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
>>>       > + return (uint64_t) ((osKernelGetTickCount() * 1000) /
>>>      osKernelGetTickFreq());
>>>       > +#elif MG_ARCH == MG_ARCH_RTTHREAD
>>>       > + return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
>>>       > #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
>>>       > // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on
>>>      linux
>>>       > // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on
>>>      linux
>>>       > @@ -5595,7 +15046,6 @@ uint64_t mg_millis(void) {
>>>       > }
>>>       > #endif
>>>       >
>>>       > -
>>>       > #ifdef MG_ENABLE_LINES
>>>       > #line 1 "src/ws.c"
>>>       > #endif
>>>       > @@ -5609,6 +15059,7 @@ uint64_t mg_millis(void) {
>>>       >
>>>       >
>>>       >
>>>       > +
>>>       > struct ws_msg {
>>>       > uint8_t flags;
>>>       > size_t header_len;
>>>       > @@ -5640,10 +15091,10 @@ static void ws_handshake(struct
>>>      mg_connection *c, const struct mg_str *wskey,
>>>       >
>>>       > mg_sha1_ctx sha_ctx;
>>>       > mg_sha1_init(&sha_ctx);
>>>       > - mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr,
>>>      wskey->len);
>>>       > + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf,
>>>      wskey->len);
>>>       > mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
>>>       > mg_sha1_final(sha, &sha_ctx);
>>>       > - mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
>>>       > + mg_base64_encode(sha, sizeof(sha), (char *) b64_sha,
>>>      sizeof(b64_sha));
>>>       > mg_xprintf(mg_pfn_iobuf, &c->send,
>>>       > "HTTP/1.1 101 Switching Protocols\r\n"
>>>       > "Upgrade: websocket\r\n"
>>>       > @@ -5653,7 +15104,7 @@ static void ws_handshake(struct
>>>      mg_connection *c, const struct mg_str *wskey,
>>>       > if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
>>>       > if (wsproto != NULL) {
>>>       > mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
>>>       > - wsproto->ptr);
>>>       > + wsproto->buf);
>>>       > }
>>>       > mg_send(c, "\r\n", 2);
>>>       > }
>>>       > @@ -5746,12 +15197,15 @@ static bool
>>>      mg_ws_client_handshake(struct mg_connection *c) {
>>>       > mg_error(c, "not http"); // Some just, not an HTTP request
>>>       > } else if (n > 0) {
>>>       > if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
>>>       > - mg_error(c, "handshake error");
>>>       > + mg_error(c, "ws handshake error");
>>>       > } else {
>>>       > struct mg_http_message hm;
>>>       > - mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
>>>       > - c->is_websocket = 1;
>>>       > - mg_call(c, MG_EV_WS_OPEN, &hm);
>>>       > + if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
>>>       > + c->is_websocket = 1;
>>>       > + mg_call(c, MG_EV_WS_OPEN, &hm);
>>>       > + } else {
>>>       > + mg_error(c, "ws handshake error");
>>>       > + }
>>>       > }
>>>       > mg_iobuf_del(&c->recv, 0, (size_t) n);
>>>       > } else {
>>>       > @@ -5760,8 +15214,7 @@ static bool mg_ws_client_handshake(struct
>>>      mg_connection *c) {
>>>       > return false; // Continue event handler
>>>       > }
>>>       >
>>>       > -static void mg_ws_cb(struct mg_connection *c, int ev, void
>>>      *ev_data,
>>>       > - void *fn_data) {
>>>       > +static void mg_ws_cb(struct mg_connection *c, int ev, void
>>>      *ev_data) {
>>>       > struct ws_msg msg;
>>>       > size_t ofs = (size_t) c->pfn_data;
>>>       >
>>>       > @@ -5775,7 +15228,7 @@ static void mg_ws_cb(struct mg_connection
>>>      *c, int ev, void *ev_data,
>>>       > size_t len = msg.header_len + msg.data_len;
>>>       > uint8_t final = msg.flags & 128, op = msg.flags & 15;
>>>       > // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
>>>       > - // (int) m.data.len, (int) m.data.len, m.data.ptr));
>>>       > + // (int) m.data.len, (int) m.data.len, m.data.buf));
>>>       > switch (op) {
>>>       > case WEBSOCKET_OP_CONTINUE:
>>>       > mg_call(c, MG_EV_WS_CTL, &m);
>>>       > @@ -5796,7 +15249,7 @@ static void mg_ws_cb(struct mg_connection
>>>      *c, int ev, void *ev_data,
>>>       > MG_DEBUG(("%lu WS CLOSE", c->id));
>>>       > mg_call(c, MG_EV_WS_CTL, &m);
>>>       > // Echo the payload of the received CLOSE message back to the sender
>>>       > - mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
>>>       > + mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
>>>       > c->is_draining = 1;
>>>       > break;
>>>       > default:
>>>       > @@ -5827,7 +15280,6 @@ static void mg_ws_cb(struct mg_connection
>>>      *c, int ev, void *ev_data,
>>>       > }
>>>       > }
>>>       > }
>>>       > - (void) fn_data;
>>>       > (void) ev_data;
>>>       > }
>>>       >
>>>       > @@ -5839,7 +15291,7 @@ struct mg_connection *mg_ws_connect(struct
>>>      mg_mgr *mgr, const char *url,
>>>       > char nonce[16], key[30];
>>>       > struct mg_str host = mg_url_host(url);
>>>       > mg_random(nonce, sizeof(nonce));
>>>       > - mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
>>>       > + mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key,
>>>      sizeof(key));
>>>       > mg_xprintf(mg_pfn_iobuf, &c->send,
>>>       > "GET %s HTTP/1.1\r\n"
>>>       > "Upgrade: websocket\r\n"
>>>       > @@ -5847,7 +15299,7 @@ struct mg_connection *mg_ws_connect(struct
>>>      mg_mgr *mgr, const char *url,
>>>       > "Connection: Upgrade\r\n"
>>>       > "Sec-WebSocket-Version: 13\r\n"
>>>       > "Sec-WebSocket-Key: %s\r\n",
>>>       > - mg_url_uri(url), (int) host.len, host.ptr, key);
>>>       > + mg_url_uri(url), (int) host.len, host.buf, key);
>>>       > if (fmt != NULL) {
>>>       > va_list ap;
>>>       > va_start(ap, fmt);
>>>       > @@ -5896,1591 +15348,2247 @@ size_t mg_ws_wrap(struct
>>>      mg_connection *c, size_t len, int op) {
>>>       > }
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "mip/driver_stm32.c"
>>>       > +#line 1 "src/drivers/cmsis.c"
>>>       > #endif
>>>       > +// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
>>>      <https://arm-software.github.io/CMSIS_5/Driver/html/index.html>
>>>       >
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
>>>      MG_ENABLE_DRIVER_CMSIS
>>>       >
>>>       > -#if MG_ENABLE_MIP && \
>>>       > - (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
>>>       > -struct stm32_eth {
>>>       > - volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
>>>      MACMIIDR, MACFCR,
>>>       > - MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
>>>      MACDBGR, MACSR,
>>>       > - MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
>>>      MACA3HR,
>>>       > - MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
>>>       > - RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
>>>       > - RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
>>>       > - RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
>>>      PTPTSLUR,
>>>       > - PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
>>>      RESERVED9[564],
>>>       > - DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
>>>      DMAIER,
>>>       > - DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
>>>       > - DMACHRBAR;
>>>       > -};
>>>       > -#undef ETH
>>>       > -#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
>>>       >
>>>       > -#undef BIT
>>>       > -#define BIT(x) ((uint32_t) 1 << (x))
>>>       > -#define ETH_PKT_SIZE 1540 // Max frame size
>>>       > -#define ETH_DESC_CNT 4 // Descriptors count
>>>       > -#define ETH_DS 4 // Descriptor size (words)
>>>       >
>>>       > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>>>      ethernet buffers
>>>       > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>>>      ethernet buffers
>>>       > -static struct mip_if *s_ifp; // MIP interface
>>>       > -enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 }; // PHY constants
>>>       >
>>>       > -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > - ETH->MACMIIAR &= (7 << 2);
>>>       > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
>>>       > - ETH->MACMIIAR |= BIT(0);
>>>       > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
>>>       > - return ETH->MACMIIDR;
>>>       > -}
>>>       >
>>>       > -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t
>>>      val) {
>>>       > - ETH->MACMIIDR = val;
>>>       > - ETH->MACMIIAR &= (7 << 2);
>>>       > - ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
>>>      6) | BIT(1);
>>>       > - ETH->MACMIIAR |= BIT(0);
>>>       > - while (ETH->MACMIIAR & BIT(0)) (void) 0;
>>>       > -}
>>>       > +extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
>>>       > +extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
>>>       >
>>>       > -static uint32_t get_hclk(void) {
>>>       > - struct rcc {
>>>       > - volatile uint32_t CR, PLLCFGR, CFGR;
>>>       > - } *rcc = (struct rcc *) 0x40023800;
>>>       > - uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
>>>      8MHz */;
>>>       > +static struct mg_tcpip_if *s_ifp;
>>>       >
>>>       > - if (rcc->CFGR & (1 << 2)) {
>>>       > - clk = hse;
>>>       > - } else if (rcc->CFGR & (1 << 3)) {
>>>       > - uint32_t vco, m, n, p;
>>>       > - m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
>>>       > - n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
>>>       > - p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
>>>       > - clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
>>>       > - vco = (uint32_t) ((uint64_t) clk * n / m);
>>>       > - clk = vco / p;
>>>       > - } else {
>>>       > - clk = hsi;
>>>       > +static void mac_cb(uint32_t);
>>>       > +static bool cmsis_init(struct mg_tcpip_if *);
>>>       > +static bool cmsis_up(struct mg_tcpip_if *);
>>>       > +static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
>>>       > +static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
>>>       > +
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init,
>>>      cmsis_tx, NULL,
>>>       > + cmsis_up};
>>>       > +
>>>       > +static bool cmsis_init(struct mg_tcpip_if *ifp) {
>>>       > + ARM_ETH_MAC_ADDR addr;
>>>       > + s_ifp = ifp;
>>>       > +
>>>       > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>>>       > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
>>>       > + ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
>>>       > + if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
>>>       > + if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) !=
>>>      ARM_DRIVER_OK)
>>>       > + return false;
>>>       > + if (cap.event_rx_frame == 0) // polled mode driver
>>>       > + mg_tcpip_driver_cmsis.rx = cmsis_rx;
>>>       > + mac->PowerControl(ARM_POWER_FULL);
>>>       > + if (cap.mac_address) { // driver provides MAC address
>>>       > + mac->GetMacAddress(&addr);
>>>       > + memcpy(ifp->mac, &addr, sizeof(ifp->mac));
>>>       > + } else { // we provide MAC address
>>>       > + memcpy(&addr, ifp->mac, sizeof(addr));
>>>       > + mac->SetMacAddress(&addr);
>>>       > }
>>>       > - uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
>>>       > - if (hpre < 8) return clk;
>>>       > + phy->PowerControl(ARM_POWER_FULL);
>>>       > + phy->SetInterface(cap.media_interface);
>>>       > + phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
>>>       > + return true;
>>>       > +}
>>>       >
>>>       > - uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>>>       > - return ((uint32_t) clk) >> ahbptab[hpre - 8];
>>>       > +static size_t cmsis_tx(const void *buf, size_t len, struct
>>>      mg_tcpip_if *ifp) {
>>>       > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>>>       > + if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
>>>       > + ifp->nerr++;
>>>       > + return 0;
>>>       > + }
>>>       > + ifp->nsent++;
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
>>>      as per 802.3,
>>>       > -// it must not exceed 2.5MHz As the AHB clock can be (and
>>>      usually is) derived
>>>       > -// from the HSI (internal RC), and it can go above specs, the
>>>      datasheets
>>>       > -// specify a range of frequencies and activate one of a series
>>>      of dividers to
>>>       > -// keep the MDC clock safely below 2.5MHz. We guess a divider
>>>      setting based on
>>>       > -// HCLK with a +5% drift. If the user uses a different clock
>>>      from our
>>>       > -// defaults, needs to set the macros on top Valid for
>>>      STM32F74xxx/75xxx
>>>       > -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
>>>      drift)
>>>       > -static int guess_mdc_cr(void) {
>>>       > - uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
>>>       > - uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
>>>      dividers
>>>       > - uint32_t hclk = get_hclk(); // Guess system HCLK
>>>       > - int result = -1; // Invalid CR value
>>>       > - if (hclk < 25000000) {
>>>       > - MG_ERROR(("HCLK too low"));
>>>       > - } else {
>>>       > - for (int i = 0; i < 6; i++) {
>>>       > - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > - result = crs[i];
>>>       > - break;
>>>       > +static bool cmsis_up(struct mg_tcpip_if *ifp) {
>>>       > + ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
>>>       > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>>>       > + bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0; //
>>>      link state
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // just went up
>>>       > + ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
>>>       > + mac->Control(ARM_ETH_MAC_CONFIGURE,
>>>       > + (st.speed << ARM_ETH_MAC_SPEED_Pos) |
>>>       > + (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
>>>       > + ARM_ETH_MAC_ADDRESS_BROADCAST);
>>>       > + MG_DEBUG(("Link is %uM %s-duplex",
>>>       > + (st.speed == 2) ? 1000
>>>       > + : st.speed ? 100
>>>       > + : 10,
>>>       > + st.duplex ? "full" : "half"));
>>>       > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
>>>       > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
>>>       > + } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) { //
>>>      just went down
>>>       > + mac->Control(ARM_ETH_MAC_FLUSH,
>>>       > + ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
>>>       > + mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
>>>       > + mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
>>>       > + }
>>>       > + return up;
>>>       > +}
>>>       > +
>>>       > +static void mac_cb(uint32_t ev) {
>>>       > + if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
>>>       > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>>>       > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
>>>       > + if (len >= 60 && len <= 1518) { // proper frame
>>>       > + char *p;
>>>       > + if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) { //
>>>      have room
>>>       > + if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) { // copy
>>>      succeeds
>>>       > + mg_queue_add(&s_ifp->recv_queue, len);
>>>       > + s_ifp->nrecv++;
>>>       > }
>>>       > + return;
>>>       > }
>>>       > - if (result < 0) MG_ERROR(("HCLK too high"));
>>>       > + s_ifp->ndrop++;
>>>       > }
>>>       > - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>>>       > - return result;
>>>       > + mac->ReadFrame(NULL, 0); // otherwise, discard
>>>       > +}
>>>       > +
>>>       > +static size_t cmsis_rx(void *buf, size_t buflen, struct
>>>      mg_tcpip_if *ifp) {
>>>       > + ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
>>>       > + uint32_t len = mac->GetRxFrameSize(); // CRC already stripped
>>>       > + if (len >= 60 && len <= 1518 &&
>>>       > + ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
>>>       > + return len;
>>>       > + if (len > 0) mac->ReadFrame(NULL, 0); // discard bad frames
>>>       > + (void) ifp;
>>>       > + return 0;
>>>       > }
>>>       >
>>>       > -static bool mip_driver_stm32_init(struct mip_if *ifp) {
>>>       > - struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data
>>>      *) ifp->driver_data;
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/imxrt.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
>>>      MG_ENABLE_DRIVER_IMXRT
>>>       > +struct imxrt_enet {
>>>       > + volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR,
>>>      RESERVED2[3],
>>>       > + ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC,
>>>      RESERVED5[7], RCR,
>>>       > + RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0,
>>>      TXIC1, TXIC2,
>>>       > + RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR,
>>>      GALR,
>>>       > + RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL,
>>>      RSEM,
>>>       > + RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC,
>>>      RACC,
>>>       > + RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
>>>       > + RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
>>>       > + RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127,
>>>      RMON_T_P128TO255,
>>>       > + RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048,
>>>      RMON_T_GTE2048,
>>>       > + RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL,
>>>      IEEE_T_MCOL,
>>>       > + IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR,
>>>      IEEE_T_CSERR,
>>>       > + IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3],
>>>      RMON_R_PACKETS,
>>>       > + RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
>>>       > + RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
>>>       > + RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511,
>>>      RMON_R_P512TO1023,
>>>       > + RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
>>>       > + IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR,
>>>      IEEE_R_FDXFC,
>>>       > + IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER,
>>>      ATCOR, ATINC,
>>>       > + ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1,
>>>      TCSR2, TCCR2,
>>>       > + TCSR3;
>>>       > +};
>>>       > +
>>>       > +#undef ENET
>>>       > +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
>>>       > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
>>>       > +#define ETH_DESC_CNT 5 // Descriptors count
>>>       > +#else
>>>       > +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#endif
>>>       > +
>>>       > +#define ETH_PKT_SIZE 1536 // Max frame size, 64-bit aligned
>>>       > +
>>>       > +struct enet_desc {
>>>       > + uint16_t length; // Data length
>>>       > + uint16_t control; // Control and status
>>>       > + uint32_t *buffer; // Data ptr
>>>       > +};
>>>       > +
>>>       > +// TODO(): handle these in a portable compiler-independent
>>>      CMSIS-friendly way
>>>       > +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
>>>       > +
>>>       > +// Descriptors: in non-cached area (TODO(scaprile)),
>>>      (37.5.1.22.2 37.5.1.23.2)
>>>       > +// Buffers: 64-byte aligned (37.3.14)
>>>       > +static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT]
>>>      MG_64BYTE_ALIGNED;
>>>       > +static volatile struct enet_desc s_txdesc[ETH_DESC_CNT]
>>>      MG_64BYTE_ALIGNED;
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>>>      MG_64BYTE_ALIGNED;
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>>>      MG_64BYTE_ALIGNED;
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +
>>>       > +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
>>>       > + ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18)
>>>      | (2 << 16);
>>>       > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
>>>       > + return ENET->MMFR & 0xffff;
>>>       > +}
>>>       > +
>>>       > +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + ENET->EIR |= MG_BIT(23); // MII interrupt clear
>>>       > + ENET->MMFR =
>>>       > + (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16)
>>>      | val;
>>>       > + while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
>>>       > +}
>>>       > +
>>>       > +// MDC clock is generated from IPS Bus clock (ipg_clk); as per
>>>      802.3,
>>>       > +// it must not exceed 2.5MHz
>>>       > +// The PHY receives the PLL6-generated 50MHz clock
>>>       > +static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_imxrt_data *d =
>>>       > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
>>>       > s_ifp = ifp;
>>>       >
>>>       > // Init RX descriptors
>>>       > for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > - s_rxdesc[i][0] = BIT(31); // Own
>>>       > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
>>>      chained
>>>       > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>>>      data buffer
>>>       > - s_rxdesc[i][3] =
>>>       > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + s_rxdesc[i].control = MG_BIT(15); // Own (E)
>>>       > + s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i]; // Point to data
>>>      buffer
>>>       > }
>>>       > + s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
>>>      descriptor
>>>       >
>>>       > // Init TX descriptors
>>>       > for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>>>       > - s_txdesc[i][3] =
>>>       > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + // s_txdesc[i].control = MG_BIT(10); // Own (TC)
>>>       > + s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
>>>       > }
>>>       > -
>>>       > - ETH->DMABMR |= BIT(0); // Software reset
>>>       > - while ((ETH->DMABMR & BIT(0)) != 0) (void) 0; // Wait until done
>>>       > -
>>>       > - // Set MDC clock divider. If user told us the value, use it.
>>>      Otherwise, guess
>>>       > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>>>      d->mdc_cr;
>>>       > - ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
>>>       > -
>>>       > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
>>>      not use
>>>       > - // hardware checksum. Therefore, descriptor size is 4, not 8
>>>       > - // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
>>>       > - ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT
>>>       > - ETH->MACFCR = BIT(7); // Disable zero quarta pause
>>>       > - // ETH->MACFFR = BIT(31); // Receive all
>>>       > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY
>>>       > - eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12)); // Set autonegotiation
>>>       > - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
>>>       > - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
>>>       > - ETH->DMAIER = BIT(6) | BIT(16); // RIE, NISE
>>>       > - ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
>>>      Duplex, Fast
>>>       > - ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST,
>>>      TSF, RSF
>>>       > -
>>>       > - // MAC address filtering
>>>       > - ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>>>       > - ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>>>       > - ((uint32_t) ifp->mac[2] << 16) |
>>>       > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>>>       > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
>>>       > + s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13); // Wrap last
>>>      descriptor
>>>       > +
>>>       > + ENET->ECR = MG_BIT(0); // Software reset, disable
>>>       > + while ((ENET->ECR & MG_BIT(0))) (void) 0; // Wait until done
>>>       > +
>>>       > + // Set MDC clock divider. If user told us the value, use it.
>>>       > + // TODO(): Otherwise, guess (currently assuming max freq)
>>>       > + int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
>>>       > + ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1); // HOLDTIME 2 clks
>>>       > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
>>>       > + mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC
>>>      clocks PHY
>>>       > + // Select RMII mode, 100M, keep CRC, set max rx length, disable
>>>      loop
>>>       > + ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
>>>       > + // ENET->RCR |= MG_BIT(3); // Receive all
>>>       > + ENET->TCR = MG_BIT(2); // Full-duplex
>>>       > + ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
>>>       > + ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
>>>       > + ENET->MRBR[0] = ETH_PKT_SIZE; // Same size for RX/TX buffers
>>>       > + // MAC address filtering (bytes in reversed order)
>>>       > + ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t)
>>>      ifp->mac[5] << 16U;
>>>       > + ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
>>>       > + ((uint32_t) ifp->mac[1] << 16U) |
>>>       > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
>>>       > + ENET->ECR = MG_BIT(8) | MG_BIT(1); // Little-endian CPU, Enable
>>>       > + ENET->EIMR = MG_BIT(25); // Set interrupt mask
>>>       > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
>>>       > + ENET->TDAR = MG_BIT(24); // Transmit Descriptors have changed
>>>       > + // ENET->OPD = 0x10014;
>>>       > return true;
>>>       > }
>>>       >
>>>       > -static uint32_t s_txno;
>>>       > -static size_t mip_driver_stm32_tx(const void *buf, size_t len,
>>>      struct mip_if *ifp) {
>>>       > - if (len > sizeof(s_txbuf[s_txno])) {
>>>       > +// Transmit frame
>>>       > +static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + static int s_txno; // Current descriptor index
>>>       > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>>>       > MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > - len = 0; // Frame is too big
>>>       > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
>>>       > - MG_ERROR(("No free descriptors"));
>>>       > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>>>      ETH->DMASR);
>>>       > - len = 0; // All descriptors are busy, fail
>>>       > + len = (size_t) -1; // fail
>>>       > + } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No descriptors available"));
>>>       > + len = 0; // retry later
>>>       > } else {
>>>       > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>>>       > - s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30); //
>>>      Chain,FS,LS
>>>       > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno].length = (uint16_t) len; // Set data len
>>>       > + // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
>>>       > + s_txdesc[s_txno].control |=
>>>       > + (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
>>>       > + ENET->TDAR = MG_BIT(24); // Descriptor ring updated
>>>       > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > }
>>>       > - ETH->DMASR = BIT(2) | BIT(5); // Clear any prior TBUS/TUS
>>>       > - ETH->DMATPDR = 0; // and resume
>>>       > - return len;
>>>       > (void) ifp;
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -static bool mip_driver_stm32_up(struct mip_if *ifp) {
>>>       > - uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
>>>       > - (void) ifp;
>>>       > - return bsr & BIT(2) ? 1 : 0;
>>>       > +static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_imxrt_data *d =
>>>       > + (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {enet_read_phy, enet_write_phy};
>>>       > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + // tmp = reg with flags set to the most likely situation: 100M
>>>      full-duplex
>>>       > + // if(link is slow or half) set flags otherwise
>>>       > + // reg = tmp
>>>       > + uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex
>>>       > + uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M
>>>       > + if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9); // 10M
>>>       > + if (full_duplex == false) tcr &= ~MG_BIT(2); // Half-duplex
>>>       > + ENET->TCR = tcr; // IRQ handler does not fiddle with these
>>>      registers
>>>       > + ENET->RCR = rcr;
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
>>>       > + tcr & MG_BIT(2) ? "full" : "half"));
>>>       > + }
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -void ETH_IRQHandler(void);
>>>       > +void ENET_IRQHandler(void);
>>>       > static uint32_t s_rxno;
>>>       > -void ETH_IRQHandler(void) {
>>>       > - qp_mark(QP_IRQTRIGGERED, 0);
>>>       > - if (ETH->DMASR & BIT(6)) { // Frame received, loop
>>>       > - ETH->DMASR = BIT(16) | BIT(6); // Clear flag
>>>       > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
>>>       > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
>>>      BIT(9))) &&
>>>       > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
>>>       > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
>>>       > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>>>       > - // ETH->DMASR);
>>>       > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > - }
>>>       > - s_rxdesc[s_rxno][0] = BIT(31);
>>>       > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > +void ENET_IRQHandler(void) {
>>>       > + ENET->EIR = MG_BIT(25); // Ack IRQ
>>>       > + // Frame received, loop
>>>       > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > + uint32_t r = s_rxdesc[s_rxno].control;
>>>       > + if (r & MG_BIT(15)) break; // exit when done
>>>       > + // skip partial/errored frames (Table 37-32)
>>>       > + if ((r & MG_BIT(11)) &&
>>>       > + !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) |
>>>      MG_BIT(0)))) {
>>>       > + size_t len = s_rxdesc[s_rxno].length;
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > }
>>>       > + s_rxdesc[s_rxno].control |= MG_BIT(15);
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > }
>>>       > - ETH->DMASR = BIT(7); // Clear possible RBUS while processing
>>>       > - ETH->DMARPDR = 0; // and resume RX
>>>       > + ENET->RDAR = MG_BIT(24); // Receive Descriptors have changed
>>>       > + // If b24 == 0, descriptors were exhausted and probably frames
>>>      were dropped
>>>       > }
>>>       >
>>>       > -struct mip_driver mip_driver_stm32 = {
>>>       > - mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx,
>>>      mip_driver_stm32_up};
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_imxrt =
>>>      {mg_tcpip_driver_imxrt_init,
>>>       > + mg_tcpip_driver_imxrt_tx, NULL,
>>>       > + mg_tcpip_driver_imxrt_up};
>>>       > +
>>>       > #endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "mip/driver_tm4c.c"
>>>       > +#line 1 "src/drivers/phy.c"
>>>       > #endif
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>>>      MG_ENABLE_DRIVER_TM4C
>>>       > -struct tm4c_emac {
>>>       > - volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
>>>      EMACHASHTBLL,
>>>       > - EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
>>>      EMACSTATUS,
>>>       > - EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
>>>      EMACADDR0H,
>>>       > - EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
>>>      EMACADDR3H,
>>>       > - EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
>>>       > - EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
>>>       > - EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
>>>      RESERVED6[4],
>>>       > - EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
>>>      EMACRXCNTCRCERR,
>>>       > - EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
>>>       > - EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
>>>      EMACSUBSECINC,
>>>       > - EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
>>>       > - EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
>>>       > - RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
>>>       > - EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
>>>      EMACTXDLADDR,
>>>       > - EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
>>>       > - RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
>>>      EMACHOSRXBA,
>>>       > - RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
>>>       > - EMACEPHYIM, EMACEPHYIMSC;
>>>       > +enum { // ID1 ID2
>>>       > + MG_PHY_KSZ8x = 0x22, // 0022 1561 - KSZ8081RNB
>>>       > + MG_PHY_DP83x = 0x2000, // 2000 a140 - TI DP83825I
>>>       > + MG_PHY_DP83867 = 0xa231, // 2000 a231 - TI DP83867I
>>>       > + MG_PHY_LAN87x = 0x7, // 0007 c0fx - LAN8720
>>>       > + MG_PHY_RTL8201 = 0x1C // 001c c816 - RTL8201
>>>       > +};
>>>       > +
>>>       > +enum {
>>>       > + MG_PHY_REG_BCR = 0,
>>>       > + MG_PHY_REG_BSR = 1,
>>>       > + MG_PHY_REG_ID1 = 2,
>>>       > + MG_PHY_REG_ID2 = 3,
>>>       > + MG_PHY_DP83x_REG_PHYSTS = 16,
>>>       > + MG_PHY_DP83867_REG_PHYSTS = 17,
>>>       > + MG_PHY_DP83x_REG_RCSR = 23,
>>>       > + MG_PHY_DP83x_REG_LEDCR = 24,
>>>       > + MG_PHY_KSZ8x_REG_PC1R = 30,
>>>       > + MG_PHY_KSZ8x_REG_PC2R = 31,
>>>       > + MG_PHY_LAN87x_REG_SCSR = 31,
>>>       > + MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
>>>       > + MG_PHY_RTL8201_REG_PAGESEL = 31
>>>       > +};
>>>       > +
>>>       > +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
>>>       > + switch (id1) {
>>>       > + case MG_PHY_DP83x:
>>>       > + switch (id2) {
>>>       > + case MG_PHY_DP83867:
>>>       > + return "DP83867";
>>>       > + default:
>>>       > + return "DP83x";
>>>       > + }
>>>       > + case MG_PHY_KSZ8x:
>>>       > + return "KSZ8x";
>>>       > + case MG_PHY_LAN87x:
>>>       > + return "LAN87x";
>>>       > + case MG_PHY_RTL8201:
>>>       > + return "RTL8201";
>>>       > + default:
>>>       > + return "unknown";
>>>       > + }
>>>       > + (void) id2;
>>>       > +}
>>>       > +
>>>       > +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t
>>>      config) {
>>>       > + uint16_t id1, id2;
>>>       > + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15)); // Reset PHY
>>>       > + while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15))
>>>      (void) 0;
>>>       > + // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw
>>>      says otherwise
>>>       > +
>>>       > + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
>>>       > + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
>>>       > + MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2,
>>>      mg_phy_id_to_str(id1, id2)));
>>>       > +
>>>       > + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
>>>       > + phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to
>>>      IO_MUX_CFG (0x0170)
>>>       > + phy->write_reg(phy_addr, 0x0e, 0x170);
>>>       > + phy->write_reg(phy_addr, 0x0d, 0x401f);
>>>       > + phy->write_reg(phy_addr, 0x0e, 0x10d);
>>>       > + }
>>>       > +
>>>       > + if (config & MG_PHY_CLOCKS_MAC) {
>>>       > + // Use PHY crystal oscillator (preserve defaults)
>>>       > + // nothing to do
>>>       > + } else { // MAC clocks PHY, PHY has no xtal
>>>       > + // Enable 50 MHz external ref clock at XI (preserve defaults)
>>>       > + if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
>>>       > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) |
>>>      MG_BIT(0));
>>>       > + } else if (id1 == MG_PHY_KSZ8x) {
>>>       > + phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
>>>       > + MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
>>>       > + } else if (id1 == MG_PHY_LAN87x) {
>>>       > + // nothing to do
>>>       > + } else if (id1 == MG_PHY_RTL8201) {
>>>       > + // assume PHY has been hardware strapped properly
>>>       > +#if 0
>>>       > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7); //
>>>      Select page 7
>>>       > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
>>>       > + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0); //
>>>      Select page 0
>>>       > +#endif
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
>>>       > + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
>>>       > + MG_BIT(9) | MG_BIT(7)); // LED status, active high
>>>       > + } // Other PHYs do not support this feature
>>>       > +}
>>>       > +
>>>       > +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool
>>>      *full_duplex,
>>>       > + uint8_t *speed) {
>>>       > + bool up = false;
>>>       > + uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
>>>       > + if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2))) // some PHYs latch
>>>      down events
>>>       > + bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); // read again
>>>       > + up = bsr & MG_BIT(2);
>>>       > + if (up && full_duplex != NULL && speed != NULL) {
>>>       > + uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
>>>       > + if (id1 == MG_PHY_DP83x) {
>>>       > + uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
>>>       > + if (id2 == MG_PHY_DP83867) {
>>>       > + uint16_t physts = phy->read_reg(phy_addr,
>>>      MG_PHY_DP83867_REG_PHYSTS);
>>>       > + *full_duplex = physts & MG_BIT(13);
>>>       > + *speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
>>>       > + : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
>>>       > + : MG_PHY_SPEED_10M;
>>>       > + } else {
>>>       > + uint16_t physts = phy->read_reg(phy_addr,
>>>      MG_PHY_DP83x_REG_PHYSTS);
>>>       > + *full_duplex = physts & MG_BIT(2);
>>>       > + *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M :
>>>      MG_PHY_SPEED_100M;
>>>       > + }
>>>       > + } else if (id1 == MG_PHY_KSZ8x) {
>>>       > + uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
>>>       > + *full_duplex = pc1r & MG_BIT(2);
>>>       > + *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
>>>       > + } else if (id1 == MG_PHY_LAN87x) {
>>>       > + uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
>>>       > + *full_duplex = scsr & MG_BIT(4);
>>>       > + *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M :
>>>      MG_PHY_SPEED_10M;
>>>       > + } else if (id1 == MG_PHY_RTL8201) {
>>>       > + uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
>>>       > + *full_duplex = bcr & MG_BIT(8);
>>>       > + *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M :
>>>      MG_PHY_SPEED_10M;
>>>       > + }
>>>       > + }
>>>       > + return up;
>>>       > +}
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/ra.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
>>>      MG_ENABLE_DRIVER_RA
>>>       > +struct ra_etherc {
>>>       > + volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR,
>>>      RESERVED2, ECSIPR,
>>>       > + RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR,
>>>      RESERVED6[3], IPGR,
>>>       > + APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR,
>>>      RESERVED8[20], MAHR,
>>>       > + RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR,
>>>      RESERVED11, CEFCR,
>>>       > + FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
>>>       > };
>>>       > -#undef EMAC
>>>       > -#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>>>       >
>>>       > -#undef BIT
>>>       > -#define BIT(x) ((uint32_t) 1 << (x))
>>>       > -#define ETH_PKT_SIZE 1540 // Max frame size
>>>       > +struct ra_edmac {
>>>       > + volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR,
>>>      RESERVED2, TDLAR,
>>>       > + RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR,
>>>      RESERVED6, TRSCER,
>>>       > + RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10,
>>>      RMCR,
>>>       > + RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR,
>>>      TRIMD,
>>>       > + RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
>>>       > +};
>>>       > +
>>>       > +#undef ETHERC
>>>       > +#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
>>>       > +#undef EDMAC
>>>       > +#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
>>>       > +#undef RASYSC
>>>       > +#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
>>>       > +#undef ICU_IELSR
>>>       > +#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
>>>       > +
>>>       > +#define ETH_PKT_SIZE 1536 // Max frame size, multiple of 32
>>>       > #define ETH_DESC_CNT 4 // Descriptors count
>>>       > -#define ETH_DS 4 // Descriptor size (words)
>>>       >
>>>       > -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>>>      ethernet buffers
>>>       > -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>>>      ethernet buffers
>>>       > -static struct mip_if *s_ifp; // MIP interface
>>>       > -enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 }; // PHY
>>>      constants
>>>       > +// TODO(): handle these in a portable compiler-independent
>>>      CMSIS-friendly way
>>>       > +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
>>>       > +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
>>>       >
>>>       > -static inline void tm4cspin(volatile uint32_t count) {
>>>       > +// Descriptors: 16-byte aligned
>>>       > +// Buffers: 32-byte aligned (27.3.1)
>>>       > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4]
>>>      MG_16BYTE_ALIGNED;
>>>       > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][4]
>>>      MG_16BYTE_ALIGNED;
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>>>      MG_32BYTE_ALIGNED;
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]
>>>      MG_32BYTE_ALIGNED;
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +
>>>       > +// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or
>>>      equivalent
>>>       > +static inline void raspin(volatile uint32_t count) {
>>>       > while (count--) (void) 0;
>>>       > }
>>>       > -
>>>       > -static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
>>>       > - EMAC->EMACMIIADDR &= (0xf << 2);
>>>       > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>>>      << 6);
>>>       > - EMAC->EMACMIIADDR |= BIT(0);
>>>       > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
>>>       > - return EMAC->EMACMIIDATA;
>>>       > +// count to get the 200ns SMC semi-cycle period (2.5MHz) calling
>>>      raspin():
>>>       > +// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
>>>       > +static uint32_t s_smispin;
>>>       > +
>>>       > +// Bit-banged SMI
>>>       > +static void smi_preamble(void) {
>>>       > + unsigned int i = 32;
>>>       > + uint32_t pir = MG_BIT(1) | MG_BIT(2); // write, mdio = 1, mdc = 0
>>>       > + ETHERC->PIR = pir;
>>>       > + while (i--) {
>>>       > + pir &= ~MG_BIT(0); // mdc = 0
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + pir |= MG_BIT(0); // mdc = 1
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + }
>>>       > }
>>>       > -
>>>       > -static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
>>>      val) {
>>>       > - EMAC->EMACMIIDATA = val;
>>>       > - EMAC->EMACMIIADDR &= (0xf << 2);
>>>       > - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>>>      << 6) | BIT(1);
>>>       > - EMAC->EMACMIIADDR |= BIT(0);
>>>       > - while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
>>>       > +static void smi_wr(uint16_t header, uint16_t data) {
>>>       > + uint32_t word = (header << 16) | data;
>>>       > + smi_preamble();
>>>       > + unsigned int i = 32;
>>>       > + while (i--) {
>>>       > + uint32_t pir = MG_BIT(1) |
>>>       > + (word & 0x80000000 ? MG_BIT(2) : 0); // write, mdc = 0, data
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + pir |= MG_BIT(0); // mdc = 1
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + word <<= 1;
>>>       > + }
>>>       > }
>>>       > -
>>>       > -static uint32_t get_sysclk(void) {
>>>       > - struct sysctl {
>>>       > - volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
>>>      PLLFREQ0,
>>>       > - PLLFREQ1;
>>>       > - } *sysctl = (struct sysctl *) 0x400FE000;
>>>       > - uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
>>>      25000000 /* 25MHz */;
>>>       > - if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
>>>       > - uint32_t fin, vco, mdiv, n, q, psysdiv;
>>>       > - uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
>>>       > - if (pllsrc == 0) {
>>>       > - clk = piosc;
>>>       > - } else if (pllsrc == 3) {
>>>       > - clk = mosc;
>>>       > - } else {
>>>       > - MG_ERROR(("Unsupported clock source"));
>>>       > - }
>>>       > - q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
>>>       > - n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
>>>       > - fin = clk / ((q + 1) * (n + 1));
>>>       > - mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
>>>       > - 0; // mint + (mfrac / 1024); MFRAC not supported
>>>       > - psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
>>>       > - vco = (uint32_t) ((uint64_t) fin * mdiv);
>>>       > - return vco / (psysdiv + 1);
>>>       > +static uint16_t smi_rd(uint16_t header) {
>>>       > + smi_preamble();
>>>       > + unsigned int i = 16; // 2 LSb as turnaround
>>>       > + uint32_t pir;
>>>       > + while (i--) {
>>>       > + pir = (i > 1 ? MG_BIT(1) : 0) |
>>>       > + (header & 0x8000
>>>       > + ? MG_BIT(2)
>>>       > + : 0); // mdc = 0, header, set read direction at turnaround
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + pir |= MG_BIT(0); // mdc = 1
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > + header <<= 1;
>>>       > }
>>>       > - uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
>>>       > - if (oscsrc == 0) {
>>>       > - clk = piosc;
>>>       > - } else if (oscsrc == 3) {
>>>       > - clk = mosc;
>>>       > - } else {
>>>       > - MG_ERROR(("Unsupported clock source"));
>>>       > + i = 16;
>>>       > + uint16_t data = 0;
>>>       > + while (i--) {
>>>       > + data <<= 1;
>>>       > + pir = 0; // read, mdc = 0
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin / 2); // 1/4 clock period, 300ns max access time
>>>       > + data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0); // read mdio
>>>       > + raspin(s_smispin / 2); // 1/4 clock period
>>>       > + pir |= MG_BIT(0); // mdc = 1
>>>       > + ETHERC->PIR = pir;
>>>       > + raspin(s_smispin);
>>>       > }
>>>       > - uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
>>>       > - return clk / (osysdiv + 1);
>>>       > + return data;
>>>       > }
>>>       >
>>>       > -// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
>>>      (AHB); as per
>>>       > -// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
>>>      clock can be
>>>       > -// derived from the PIOSC (internal RC), and it can go above
>>>      specs, the
>>>       > -// datasheets specify a range of frequencies and activate one of
>>>      a series of
>>>       > -// dividers to keep the MDC clock safely below 2.5MHz. We guess
>>>      a divider
>>>       > -// setting based on SYSCLK with a +5% drift. If the user uses a
>>>      different clock
>>>       > -// from our defaults, needs to set the macros on top Valid for
>>>      TM4C129x (20.7)
>>>       > -// (4.5% worst case drift)
>>>       > -// The PHY receives the main oscillator (MOSC) (20.3.1)
>>>       > -static int guess_mdc_cr(void) {
>>>       > - uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
>>>       > - uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
>>>       > - uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
>>>       > - int result = -1; // Invalid CR value
>>>       > - if (sysclk < 25000000) {
>>>       > - MG_ERROR(("SYSCLK too low"));
>>>       > - } else {
>>>       > - for (int i = 0; i < 4; i++) {
>>>       > - if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > - result = crs[i];
>>>       > - break;
>>>       > - }
>>>       > - }
>>>       > - if (result < 0) MG_ERROR(("SYSCLK too high"));
>>>       > - }
>>>       > - MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
>>>       > - return result;
>>>       > +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) |
>>>      (reg << 2) | (2 << 0)));
>>>       > }
>>>       >
>>>       > -static bool mip_driver_tm4c_init(struct mip_if *ifp) {
>>>       > - struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data
>>>      *) ifp->driver_data;
>>>       > +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg <<
>>>      2) | (2 << 0)), val);
>>>       > +}
>>>       > +
>>>       > +// MDC clock is generated manually; as per 802.3, it must not
>>>      exceed 2.5MHz
>>>       > +static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_ra_data *d =
>>>       > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
>>>       > s_ifp = ifp;
>>>       >
>>>       > + // Init SMI clock timing. If user told us the clock value, use it.
>>>       > + // TODO(): Otherwise, guess
>>>       > + s_smispin = d->clock / 15000000;
>>>       > +
>>>       > // Init RX descriptors
>>>       > for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > - s_rxdesc[i][0] = BIT(31); // Own
>>>       > - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address
>>>      chained
>>>       > - s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>>>      data buffer
>>>       > - s_rxdesc[i][3] =
>>>       > - (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > - // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
>>>       > + s_rxdesc[i][0] = MG_BIT(31); // RACT
>>>       > + s_rxdesc[i][1] = ETH_PKT_SIZE << 16; // RBL
>>>       > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; // Point to data buffer
>>>       > }
>>>       > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
>>>      descriptor
>>>       >
>>>       > // Init TX descriptors
>>>       > for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > - s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>>>       > - s_txdesc[i][3] =
>>>       > - (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + // TACT = 0
>>>       > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
>>>       > }
>>>       > -
>>>       > - EMAC->EMACDMABUSMOD |= BIT(0); // Software reset
>>>       > - while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1); //
>>>      Wait until done
>>>       > -
>>>       > - // Set MDC clock divider. If user told us the value, use it.
>>>      Otherwise, guess
>>>       > - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>>>      d->mdc_cr;
>>>       > - EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
>>>       > -
>>>       > - // NOTE(cpq): we do not use extended descriptor bit 7, and do
>>>      not use
>>>       > - // hardware checksum. Therefore, descriptor size is 4, not 8
>>>       > - // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23)
>>>      | BIT(25);
>>>       > - EMAC->EMACIM = BIT(3) | BIT(9); // Mask timestamp & PMT IT
>>>       > - EMAC->EMACFLOWCTL = BIT(7); // Disable zero-quanta pause
>>>       > - // EMAC->EMACFRAMEFLTR = BIT(31); // Receive all
>>>       > - // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
>>>       > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15)); // Reset internal
>>>      PHY (EPHY)
>>>       > - emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12)); // Set
>>>      autonegotiation
>>>       > - EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
>>>      descriptors
>>>       > - EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
>>>      descriptors
>>>       > - EMAC->EMACDMAIM = BIT(6) | BIT(16); // RIE, NIE
>>>       > - EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE,
>>>      Duplex, Fast
>>>       > - EMAC->EMACDMAOPMODE =
>>>       > - BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
>>>       > - EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>>>       > - EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
>>>       > - ((uint32_t) ifp->mac[2] << 16) |
>>>       > - ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>>>       > - // NOTE(scaprile) There are 3 additional slots for filtering,
>>>      disabled by
>>>       > - // default. This also applies to the STM32 driver (at least for
>>>      F7)
>>>       > -
>>>       > - if (ifp->queue.len == 0) ifp->queue.len = 8192;
>>>       > + s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30); // Wrap last
>>>      descriptor
>>>       > +
>>>       > + EDMAC->EDMR = MG_BIT(0); // Software reset, wait 64 PCLKA
>>>      clocks (27.2.1)
>>>       > + uint32_t sckdivcr = RASYSC[8]; // get divisors from SCKDIVCR
>>>      (8.2.2)
>>>       > + uint32_t ick = 1 << ((sckdivcr >> 24) & 7); // sys_clock div
>>>       > + uint32_t pcka = 1 << ((sckdivcr >> 12) & 7); // pclka div
>>>       > + raspin((64U * pcka) / (3U * ick));
>>>       > + EDMAC->EDMR = MG_BIT(6); // Initialize, little-endian (27.2.1)
>>>       > +
>>>       > + MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
>>>       > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
>>>       > + mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
>>>       > +
>>>       > + // Select RMII mode,
>>>       > + ETHERC->ECMR = MG_BIT(2) | MG_BIT(1); // 100M, Full-duplex, CRC
>>>       > + // ETHERC->ECMR |= MG_BIT(0); // Receive all
>>>       > + ETHERC->RFLR = 1518; // Set max rx length
>>>       > +
>>>       > + EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
>>>       > + EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
>>>       > + // MAC address filtering (bytes in reversed order)
>>>       > + ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
>>>       > + ((uint32_t) ifp->mac[1] << 16U) |
>>>       > + ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
>>>       > + ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
>>>       > +
>>>       > + EDMAC->TFTR = 0; // Store and forward (27.2.10)
>>>       > + EDMAC->FDR = 0x070f; // (27.2.11)
>>>       > + EDMAC->RMCR = MG_BIT(0); // (27.2.12)
>>>       > + ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5); // TE RE
>>>       > + EDMAC->EESIPR = MG_BIT(18); // Enable Rx IRQ
>>>       > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
>>>       > + EDMAC->EDTRR = MG_BIT(0); // Transmit Descriptors have changed
>>>       > return true;
>>>       > }
>>>       >
>>>       > -static uint32_t s_txno;
>>>       > -static size_t mip_driver_tm4c_tx(const void *buf, size_t len,
>>>      struct mip_if *ifp) {
>>>       > - if (len > sizeof(s_txbuf[s_txno])) {
>>>       > +// Transmit frame
>>>       > +static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + static int s_txno; // Current descriptor index
>>>       > + if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
>>>       > MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > - len = 0; // fail
>>>       > - } else if ((s_txdesc[s_txno][0] & BIT(31))) {
>>>       > + len = (size_t) -1; // fail
>>>       > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>>>       > + ifp->nerr++;
>>>       > MG_ERROR(("No descriptors available"));
>>>       > - // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>>>       > - // EMAC->EMACDMARIS);
>>>       > - len = 0; // fail
>>>       > + len = 0; // retry later
>>>       > } else {
>>>       > - memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > - s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>>>       > - s_txdesc[s_txno][0] =
>>>       > - BIT(20) | BIT(28) | BIT(29) | BIT(30); // Chain,FS,LS,IC
>>>       > - s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno][1] = len << 16; // Set data len
>>>       > + s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28; // (27.3.1.1) mark
>>>      valid
>>>       > + EDMAC->EDTRR = MG_BIT(0); // Transmit request
>>>       > if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > }
>>>       > - EMAC->EMACDMARIS = BIT(2) | BIT(5); // Clear any prior TU/UNF
>>>       > - EMAC->EMACTXPOLLD = 0; // and resume
>>>       > return len;
>>>       > - (void) ifp;
>>>       > }
>>>       >
>>>       > -static bool mip_driver_tm4c_up(struct mip_if *ifp) {
>>>       > - uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
>>>       > - (void) ifp;
>>>       > - return (bmsr & BIT(2)) ? 1 : 0;
>>>       > +static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_ra_data *d =
>>>       > + (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
>>>       > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + // tmp = reg with flags set to the most likely situation: 100M
>>>      full-duplex
>>>       > + // if(link is slow or half) set flags otherwise
>>>       > + // reg = tmp
>>>       > + uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1); // 100M
>>>      Full-duplex
>>>       > + if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2); // 10M
>>>       > + if (full_duplex == false) ecmr &= ~MG_BIT(1); // Half-duplex
>>>       > + ETHERC->ECMR = ecmr; // IRQ handler does not fiddle with these
>>>      registers
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
>>>       > + ecmr & MG_BIT(1) ? "full" : "half"));
>>>       > + }
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -void EMAC0_IRQHandler(void);
>>>       > +void EDMAC_IRQHandler(void);
>>>       > static uint32_t s_rxno;
>>>       > -void EMAC0_IRQHandler(void) {
>>>       > - qp_mark(QP_IRQTRIGGERED, 0);
>>>       > - if (EMAC->EMACDMARIS & BIT(6)) { // Frame received, loop
>>>       > - EMAC->EMACDMARIS = BIT(16) | BIT(6); // Clear flag
>>>       > - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > - if (s_rxdesc[s_rxno][0] & BIT(31)) break; // exit when done
>>>       > - if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) |
>>>      BIT(9))) &&
>>>       > - !(s_rxdesc[s_rxno][0] & BIT(15))) { // skip partial/errored frames
>>>       > - uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
>>>       > - // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>>>       > - // EMAC->EMACDMARIS);
>>>       > - mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > - }
>>>       > - s_rxdesc[s_rxno][0] = BIT(31);
>>>       > - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > +void EDMAC_IRQHandler(void) {
>>>       > + struct mg_tcpip_driver_ra_data *d =
>>>       > + (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
>>>       > + EDMAC->EESR = MG_BIT(18); // Ack IRQ in EDMAC 1st
>>>       > + ICU_IELSR[d->irqno] &= ~MG_BIT(16); // Ack IRQ in ICU last
>>>       > + // Frame received, loop
>>>       > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > + uint32_t r = s_rxdesc[s_rxno][0];
>>>       > + if (r & MG_BIT(31)) break; // exit when done
>>>       > + // skip partial/errored frames (27.3.1.2)
>>>       > + if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
>>>       > + size_t len = s_rxdesc[s_rxno][1] & 0xffff;
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); // CRC already
>>>      stripped
>>>       > }
>>>       > + s_rxdesc[s_rxno][0] |= MG_BIT(31);
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > }
>>>       > - EMAC->EMACDMARIS = BIT(7); // Clear possible RU while processing
>>>       > - EMAC->EMACRXPOLLD = 0; // and resume RX
>>>       > + EDMAC->EDRRR = MG_BIT(0); // Receive Descriptors have changed
>>>       > + // If b0 == 0, descriptors were exhausted and probably frames
>>>      were dropped,
>>>       > + // (27.2.9 RMFCR counts them)
>>>       > }
>>>       >
>>>       > -struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init,
>>>      mip_driver_tm4c_tx,
>>>       > - mip_driver_rx, mip_driver_tm4c_up};
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_ra =
>>>      {mg_tcpip_driver_ra_init,
>>>       > + mg_tcpip_driver_ra_tx, NULL,
>>>       > + mg_tcpip_driver_ra_up};
>>>       > +
>>>       > #endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "mip/driver_w5500.c"
>>>       > +#line 1 "src/drivers/same54.c"
>>>       > #endif
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_MIP
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
>>>      MG_ENABLE_DRIVER_SAME54
>>>       >
>>>       > -enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
>>>       > +#include <sam.h>
>>>       >
>>>       > -static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t
>>>      addr, bool wr,
>>>       > - void *buf, size_t len) {
>>>       > - uint8_t *p = (uint8_t *) buf;
>>>       > - uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
>>>       > - (uint8_t) ((block << 3) | (wr ? 4 : 0))};
>>>       > - s->begin(s->spi);
>>>       > - for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
>>>       > - for (size_t i = 0; i < len; i++) {
>>>       > - uint8_t r = s->txn(s->spi, p[i]);
>>>       > - if (!wr) p[i] = r;
>>>       > - }
>>>       > - s->end(s->spi);
>>>       > +#define ETH_PKT_SIZE 1536 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 2 // Descriptor size (words)
>>>       > +
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > +static uint8_t s_txno; // Current TX descriptor
>>>       > +static uint8_t s_rxno; // Current RX descriptor
>>>       > +
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>>>       > +
>>>       > +#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
>>>       > +#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
>>>       > +#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
>>>       > +
>>>       > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
>>>       > + GMAC_MAN_OP(2) | // Setting the read operation
>>>       > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
>>>       > + GMAC_MAN_REGA(reg); // Setting the register
>>>       > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
>>>       > + return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk; // Getting the
>>>      read value
>>>       > }
>>>       >
>>>       > -// clang-format off
>>>       > -static void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t
>>>      addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf,
>>>      len); }
>>>       > -static void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t
>>>      addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
>>>       > -static void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t
>>>      addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8),
>>>      (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
>>>       > -static void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t
>>>      addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf,
>>>      len); }
>>>       > -static uint8_t w5500_r1(struct mip_spi *s, uint8_t block,
>>>      uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
>>>      return r; }
>>>       > -static uint16_t w5500_r2(struct mip_spi *s, uint8_t block,
>>>      uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
>>>      buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
>>>       > -// clang-format on
>>>       > +#if 0
>>>       > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) | //
>>>      Setting the write operation
>>>       > + GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
>>>       > + GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val); // Setting the register
>>>       > + while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting
>>>      until the write op is complete
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > -static size_t w5500_rx(void *buf, size_t buflen, struct mip_if
>>>      *ifp) {
>>>       > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>>>       > - uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
>>>      recv len
>>>       > - while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
>>>      it is stable
>>>       > - // printf("RSR: %d\n", (int) n);
>>>       > - if (n > 0) {
>>>       > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
>>>       > - n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
>>>       > - if (n <= len + 2 && n > 1) {
>>>       > - r = (uint16_t) (n - 2);
>>>       > - w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
>>>       > +int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
>>>       > + if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
>>>       > + return d->mdc_cr;
>>>       > + } else {
>>>       > + // get MCLK from GCLK_GENERATOR 0
>>>       > + uint32_t div = 512;
>>>       > + uint32_t mclk;
>>>       > + if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
>>>       > + div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
>>>       > + if (div == 0) div = 1;
>>>       > }
>>>       > - w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
>>>      read pointer
>>>       > - w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
>>>       > - // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
>>>       > + switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
>>>       > + case GCLK_GENCTRL_SRC_XOSC0_Val:
>>>       > + mclk = 32000000UL; /* 32MHz */
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_XOSC1_Val:
>>>       > + mclk = 32000000UL; /* 32MHz */
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_OSCULP32K_Val:
>>>       > + mclk = 32000UL;
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_XOSC32K_Val:
>>>       > + mclk = 32000UL;
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_DFLL_Val:
>>>       > + mclk = 48000000UL; /* 48MHz */
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_DPLL0_Val:
>>>       > + mclk = 200000000UL; /* 200MHz */
>>>       > + break;
>>>       > + case GCLK_GENCTRL_SRC_DPLL1_Val:
>>>       > + mclk = 200000000UL; /* 200MHz */
>>>       > + break;
>>>       > + default:
>>>       > + mclk = 200000000UL; /* 200MHz */
>>>       > + }
>>>       > +
>>>       > + mclk /= div;
>>>       > + uint8_t crs[] = {0, 1, 2, 3, 4, 5}; // GMAC->NCFGR::CLK values
>>>       > + uint8_t dividers[] = {8, 16, 32, 48, 64, 96}; // Respective CLK
>>>      dividers
>>>       > + for (int i = 0; i < 6; i++) {
>>>       > + if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > + return crs[i];
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + return 5;
>>>       > }
>>>       > - return r;
>>>       > }
>>>       >
>>>       > -static size_t w5500_tx(const void *buf, size_t buflen, struct
>>>      mip_if *ifp) {
>>>       > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>>>       > - uint16_t n = 0, len = (uint16_t) buflen;
>>>       > - while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
>>>       > - uint16_t ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
>>>       > - w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
>>>       > - w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
>>>      write pointer
>>>       > - w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
>>>       > - for (int i = 0; i < 40; i++) {
>>>       > - uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
>>>       > - if (ir == 0) continue;
>>>       > - // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
>>>      (int) n, ptr);
>>>       > - w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
>>>       > - if (ir & 8) len = 0; // Timeout. Report error
>>>       > - if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
>>>       > +static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_same54_data *d =
>>>       > + (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
>>>       > + s_ifp = ifp;
>>>       > +
>>>       > + MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
>>>       > + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
>>>       > + GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d)); //
>>>      Set MDC divider
>>>       > + GMAC_REGS->GMAC_NCR = 0; // Disable RX & TX
>>>       > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk; // Enable MDC & MDIO
>>>       > +
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init TX descriptors
>>>       > + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; // Point to data buffer
>>>       > + s_txdesc[i][1] = MG_BIT(31); // OWN bit
>>>       > }
>>>       > - return len;
>>>       > + s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30); // Last tx
>>>      descriptor - wrap
>>>       > +
>>>       > + GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18) // DMA recv buf 1536
>>>       > + | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
>>>       > + GMAC_DCFGR_TXPBMS(1); // See #2487
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) { // Init RX descriptors
>>>       > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; // Address of the data
>>>      buffer
>>>       > + s_rxdesc[i][1] = 0; // Clear status
>>>       > + }
>>>       > + s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1); // Last rx
>>>      descriptor - wrap
>>>       > +
>>>       > + GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc; // about the
>>>      descriptor addresses
>>>       > + GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc; // Let the
>>>      controller know
>>>       > +
>>>       > + GMAC_REGS->SA[0].GMAC_SAB =
>>>       > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
>>>       > + GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5],
>>>      ifp->mac[4]);
>>>       > +
>>>       > + GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk; // Disable MII, use RMII
>>>       > + GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk |
>>>      GMAC_NCFGR_MTIHEN_Msk |
>>>       > + GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
>>>       > + GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
>>>       > + GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
>>>       > + GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
>>>       > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
>>>       > + GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
>>>       > + GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
>>>       > + GMAC_REGS->GMAC_IDR = ~0U; // Disable interrupts, then enable
>>>      required
>>>       > + GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
>>>       > + GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
>>>       > + GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
>>>       > + GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
>>>       > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
>>>       > + NVIC_EnableIRQ(GMAC_IRQn);
>>>       > +
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -static bool w5500_init(struct mip_if *ifp) {
>>>       > - struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
>>>       > - s->end(s->spi);
>>>       > - w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
>>>       > - w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
>>>       > - w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
>>>       > - // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
>>>       > - w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
>>>       > - w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
>>>       > - w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
>>>       > - w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
>>>       > - return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
>>>       > +static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t
>>>      len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // Frame is too big
>>>       > + } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No free descriptors"));
>>>       > + len = 0; // All descriptors are busy, fail
>>>       > + } else {
>>>       > + uint32_t status = len | MG_BIT(15); // Frame length, last chunk
>>>       > + if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30); // wrap
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno][1] = status;
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > + }
>>>       > + __DSB(); // Ensure descriptors have been written
>>>       > + GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk; // Enable transmission
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -static bool w5500_up(struct mip_if *ifp) {
>>>       > - struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
>>>       > - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
>>>       > - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
>>>       > +static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
>>>       > + uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
>>>       > + bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
>>>       > +
>>>       > + // If PHY is ready, update NCFGR accordingly
>>>       > + if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
>>>       > + uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
>>>       > + bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
>>>       > + bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
>>>       > + GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
>>>       > + ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
>>>       > + GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
>>>       > + }
>>>       > +
>>>       > + return up;
>>>       > +}
>>>       > +
>>>       > +void GMAC_Handler(void);
>>>       > +void GMAC_Handler(void) {
>>>       > + uint32_t isr = GMAC_REGS->GMAC_ISR;
>>>       > + uint32_t rsr = GMAC_REGS->GMAC_RSR;
>>>       > + uint32_t tsr = GMAC_REGS->GMAC_TSR;
>>>       > + if (isr & GMAC_ISR_RCOMP_Msk) {
>>>       > + if (rsr & GMAC_ISR_RCOMP_Msk) {
>>>       > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
>>>       > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>>>       > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // Disown
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > + }
>>>       > + }
>>>       > + }
>>>       > +
>>>       > + if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
>>>      GMAC_TSR_TXCOMP_Msk |
>>>       > + GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
>>>       > + GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
>>>       > + // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
>>>       > + if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |=
>>>      MG_BIT(31);
>>>       > + }
>>>       > +
>>>       > + GMAC_REGS->GMAC_RSR = rsr;
>>>       > + GMAC_REGS->GMAC_TSR = tsr;
>>>       > }
>>>       >
>>>       > -struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx,
>>>      w5500_rx, w5500_up};
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_same54 = {
>>>       > + mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
>>>       > + mg_tcpip_driver_same54_up};
>>>       > #endif
>>>       >
>>>       > #ifdef MG_ENABLE_LINES
>>>       > -#line 1 "mip/mip.c"
>>>       > +#line 1 "src/drivers/stm32f.c"
>>>       > #endif
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_MIP
>>>       > -
>>>       > -#define MIP_ETHEMERAL_PORT 49152
>>>       > -#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
>>>       > -#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
>>>       > -
>>>       > -#ifndef MIP_QSIZE
>>>       > -#define MIP_QSIZE (16 * 1024) // Queue size
>>>       > -#endif
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
>>>       > + MG_ENABLE_DRIVER_STM32F
>>>       > +struct stm32f_eth {
>>>       > + volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR,
>>>      MACMIIDR, MACFCR,
>>>       > + MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1,
>>>      MACDBGR, MACSR,
>>>       > + MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR,
>>>      MACA3HR,
>>>       > + MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
>>>       > + RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
>>>       > + RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
>>>       > + RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR,
>>>      PTPTSLUR,
>>>       > + PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR,
>>>      RESERVED9[564],
>>>       > + DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR,
>>>      DMAIER,
>>>       > + DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
>>>       > + DMACHRBAR;
>>>       > +};
>>>       > +#undef ETH
>>>       > +#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
>>>       >
>>>       > -#ifndef MIP_TCP_KEEPALIVE_MS
>>>       > -#define MIP_TCP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
>>>       > -#endif
>>>       > +#define ETH_PKT_SIZE 1540 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 4 // Descriptor size (words)
>>>       >
>>>       > -#define MIP_TCP_ACK_MS 150 // Timeout for ACKing
>>>       > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>>>      ethernet buffers
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>>>      ethernet buffers
>>>       > +static uint8_t s_txno; // Current TX descriptor
>>>       > +static uint8_t s_rxno; // Current RX descriptor
>>>       >
>>>       > -struct connstate {
>>>       > - uint32_t seq, ack; // TCP seq/ack counters
>>>       > - uint64_t timer; // TCP keep-alive / ACK timer
>>>       > - uint8_t mac[6]; // Peer MAC address
>>>       > - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive
>>>       > -#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long,
>>>      send keepalive
>>>       > -#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it
>>>      soon
>>>       > - uint8_t tmiss; // Number of keep-alive misses
>>>       > - struct mg_iobuf raw; // For TLS only. Incoming raw data
>>>       > -};
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       >
>>>       > -#pragma pack(push, 1)
>>>       > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + ETH->MACMIIAR &= (7 << 2);
>>>       > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
>>>       > + ETH->MACMIIAR |= MG_BIT(0);
>>>       > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
>>>       > + return ETH->MACMIIDR & 0xffff;
>>>       > +}
>>>       >
>>>       > -struct lcp {
>>>       > - uint8_t addr, ctrl, proto[2], code, id, len[2];
>>>       > -};
>>>       > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + ETH->MACMIIDR = val;
>>>       > + ETH->MACMIIAR &= (7 << 2);
>>>       > + ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg <<
>>>      6) | MG_BIT(1);
>>>       > + ETH->MACMIIAR |= MG_BIT(0);
>>>       > + while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
>>>       > +}
>>>       >
>>>       > -struct eth {
>>>       > - uint8_t dst[6]; // Destination MAC address
>>>       > - uint8_t src[6]; // Source MAC address
>>>       > - uint16_t type; // Ethernet type
>>>       > -};
>>>       > +static uint32_t get_hclk(void) {
>>>       > + struct rcc {
>>>       > + volatile uint32_t CR, PLLCFGR, CFGR;
>>>       > + } *rcc = (struct rcc *) 0x40023800;
>>>       > + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /*
>>>      8MHz */;
>>>       >
>>>       > -struct ip {
>>>       > - uint8_t ver; // Version
>>>       > - uint8_t tos; // Unused
>>>       > - uint16_t len; // Length
>>>       > - uint16_t id; // Unused
>>>       > - uint16_t frag; // Fragmentation
>>>       > - uint8_t ttl; // Time to live
>>>       > - uint8_t proto; // Upper level protocol
>>>       > - uint16_t csum; // Checksum
>>>       > - uint32_t src; // Source IP
>>>       > - uint32_t dst; // Destination IP
>>>       > -};
>>>       > + if (rcc->CFGR & (1 << 2)) {
>>>       > + clk = hse;
>>>       > + } else if (rcc->CFGR & (1 << 3)) {
>>>       > + uint32_t vco, m, n, p;
>>>       > + m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
>>>       > + n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
>>>       > + p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
>>>       > + clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
>>>       > + vco = (uint32_t) ((uint64_t) clk * n / m);
>>>       > + clk = vco / p;
>>>       > + } else {
>>>       > + clk = hsi;
>>>       > + }
>>>       > + uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
>>>       > + if (hpre < 8) return clk;
>>>       >
>>>       > -struct ip6 {
>>>       > - uint8_t ver; // Version
>>>       > - uint8_t opts[3]; // Options
>>>       > - uint16_t len; // Length
>>>       > - uint8_t proto; // Upper level protocol
>>>       > - uint8_t ttl; // Time to live
>>>       > - uint8_t src[16]; // Source IP
>>>       > - uint8_t dst[16]; // Destination IP
>>>       > -};
>>>       > + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>>>       > + return ((uint32_t) clk) >> ahbptab[hpre - 8];
>>>       > +}
>>>       >
>>>       > -struct icmp {
>>>       > - uint8_t type;
>>>       > - uint8_t code;
>>>       > - uint16_t csum;
>>>       > -};
>>>       > +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB);
>>>      as per 802.3,
>>>       > +// it must not exceed 2.5MHz As the AHB clock can be (and
>>>      usually is) derived
>>>       > +// from the HSI (internal RC), and it can go above specs, the
>>>      datasheets
>>>       > +// specify a range of frequencies and activate one of a series
>>>      of dividers to
>>>       > +// keep the MDC clock safely below 2.5MHz. We guess a divider
>>>      setting based on
>>>       > +// HCLK with a +5% drift. If the user uses a different clock
>>>      from our
>>>       > +// defaults, needs to set the macros on top Valid for
>>>      STM32F74xxx/75xxx
>>>       > +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case
>>>      drift)
>>>       > +static int guess_mdc_cr(void) {
>>>       > + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values
>>>       > + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK
>>>      dividers
>>>       > + uint32_t hclk = get_hclk(); // Guess system HCLK
>>>       > + int result = -1; // Invalid CR value
>>>       > + if (hclk < 25000000) {
>>>       > + MG_ERROR(("HCLK too low"));
>>>       > + } else {
>>>       > + for (int i = 0; i < 6; i++) {
>>>       > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > + result = crs[i];
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + if (result < 0) MG_ERROR(("HCLK too high"));
>>>       > + }
>>>       > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>>>       > + return result;
>>>       > +}
>>>       >
>>>       > -struct arp {
>>>       > - uint16_t fmt; // Format of hardware address
>>>       > - uint16_t pro; // Format of protocol address
>>>       > - uint8_t hlen; // Length of hardware address
>>>       > - uint8_t plen; // Length of protocol address
>>>       > - uint16_t op; // Operation
>>>       > - uint8_t sha[6]; // Sender hardware address
>>>       > - uint32_t spa; // Sender protocol address
>>>       > - uint8_t tha[6]; // Target hardware address
>>>       > - uint32_t tpa; // Target protocol address
>>>       > -};
>>>       > +static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_stm32f_data *d =
>>>       > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
>>>       > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>>>       > + s_ifp = ifp;
>>>       >
>>>       > -struct tcp {
>>>       > - uint16_t sport; // Source port
>>>       > - uint16_t dport; // Destination port
>>>       > - uint32_t seq; // Sequence number
>>>       > - uint32_t ack; // Acknowledgement number
>>>       > - uint8_t off; // Data offset
>>>       > - uint8_t flags; // TCP flags
>>>       > -#define TH_FIN 0x01
>>>       > -#define TH_SYN 0x02
>>>       > -#define TH_RST 0x04
>>>       > -#define TH_PUSH 0x08
>>>       > -#define TH_ACK 0x10
>>>       > -#define TH_URG 0x20
>>>       > -#define TH_ECE 0x40
>>>       > -#define TH_CWR 0x80
>>>       > - uint16_t win; // Window
>>>       > - uint16_t csum; // Checksum
>>>       > - uint16_t urp; // Urgent pointer
>>>       > -};
>>>       > + // Init RX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_rxdesc[i][0] = MG_BIT(31); // Own
>>>       > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
>>>      address chained
>>>       > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>>>      data buffer
>>>       > + s_rxdesc[i][3] =
>>>       > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + }
>>>       >
>>>       > -struct udp {
>>>       > - uint16_t sport; // Source port
>>>       > - uint16_t dport; // Destination port
>>>       > - uint16_t len; // UDP length
>>>       > - uint16_t csum; // UDP checksum
>>>       > -};
>>>       > + // Init TX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>>>       > + s_txdesc[i][3] =
>>>       > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + }
>>>       >
>>>       > -struct dhcp {
>>>       > - uint8_t op, htype, hlen, hops;
>>>       > - uint32_t xid;
>>>       > - uint16_t secs, flags;
>>>       > - uint32_t ciaddr, yiaddr, siaddr, giaddr;
>>>       > - uint8_t hwaddr[208];
>>>       > - uint32_t magic;
>>>       > - uint8_t options[32];
>>>       > -};
>>>       > + ETH->DMABMR |= MG_BIT(0); // Software reset
>>>       > + while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0; // Wait until
>>>      done
>>>       >
>>>       > -#pragma pack(pop)
>>>       > + // Set MDC clock divider. If user told us the value, use it.
>>>      Otherwise, guess
>>>       > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>>>      d->mdc_cr;
>>>       > + ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
>>>       >
>>>       > -struct pkt {
>>>       > - struct mg_str raw; // Raw packet data
>>>       > - struct mg_str pay; // Payload data
>>>       > - struct eth *eth;
>>>       > - struct llc *llc;
>>>       > - struct arp *arp;
>>>       > - struct ip *ip;
>>>       > - struct ip6 *ip6;
>>>       > - struct icmp *icmp;
>>>       > - struct tcp *tcp;
>>>       > - struct udp *udp;
>>>       > - struct dhcp *dhcp;
>>>       > -};
>>>       > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
>>>      not use
>>>       > + // hardware checksum. Therefore, descriptor size is 4, not 8
>>>       > + // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
>>>      MG_BIT(23) |
>>>       > + // MG_BIT(25);
>>>       > + ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
>>>       > + ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause
>>>       > + // ETH->MACFFR = MG_BIT(31); // Receive all
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
>>>       > + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors
>>>       > + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors
>>>       > + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE
>>>       > + ETH->MACCR =
>>>       > + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE,
>>>      Duplex, Fast
>>>       > + ETH->DMAOMR =
>>>       > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
>>>      TSF, RSF
>>>       >
>>>       > -static void q_copyin(struct queue *q, const uint8_t *buf, size_t
>>>      len,
>>>       > - size_t head) {
>>>       > - size_t left = q->len - head;
>>>       > - memcpy(&q->buf[head], buf, left < len ? left : len);
>>>       > - if (left < len) memcpy(q->buf, &buf[left], len - left);
>>>       > + // MAC address filtering
>>>       > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>>>       > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>>>       > + ((uint32_t) ifp->mac[2] << 16) |
>>>       > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -static void q_copyout(struct queue *q, uint8_t *buf, size_t len,
>>>      size_t tail) {
>>>       > - size_t left = q->len - tail;
>>>       > - memcpy(buf, &q->buf[tail], left < len ? left : len);
>>>       > - if (left < len) memcpy(&buf[left], q->buf, len - left);
>>>       > +static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t
>>>      len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // Frame is too big
>>>       > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No free descriptors"));
>>>       > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>>>      ETH->DMASR);
>>>       > + len = 0; // All descriptors are busy, fail
>>>       > + } else {
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>>>       > + s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29); //
>>>      Chain,FS,LS
>>>       > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
>>>      take over
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > + }
>>>       > + MG_DSB(); // ensure descriptors have been written
>>>       > + ETH->DMASR = MG_BIT(2) | MG_BIT(5); // Clear any prior TBUS/TUS
>>>       > + ETH->DMATPDR = 0; // and resume
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -static bool q_write(struct queue *q, const void *buf, size_t len) {
>>>       > - bool success = false;
>>>       > - size_t left = (q->len - q->head + q->tail - 1) % q->len;
>>>       > - if (len + sizeof(size_t) <= left) {
>>>       > - q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
>>>       > - q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) %
>>>      q->len);
>>>       > - q->head = (q->head + sizeof(len) + len) % q->len;
>>>       > - success = true;
>>>       > +static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_stm32f_data *d =
>>>       > + (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
>>>       > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + // tmp = reg with flags set to the most likely situation: 100M
>>>      full-duplex
>>>       > + // if(link is slow or half) set flags otherwise
>>>       > + // reg = tmp
>>>       > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M,
>>>      Full-duplex
>>>       > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
>>>       > + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex
>>>       > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
>>>      register
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
>>>       > + maccr & MG_BIT(11) ? "full" : "half"));
>>>       > }
>>>       > - return success;
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -#ifdef MIP_QPROFILE
>>>       > -static inline size_t q_space(struct queue *q) {
>>>       > - return q->tail > q->head ? q->tail - q->head : q->tail +
>>>      (q->len - q->head);
>>>       > +#ifdef __riscv
>>>       > +__attribute__((interrupt())) // For RISCV CH32V307, which share
>>>      the same MAC
>>>       > +#endif
>>>       > +void ETH_IRQHandler(void);
>>>       > +void ETH_IRQHandler(void) {
>>>       > + if (ETH->DMASR & MG_BIT(6)) { // Frame received, loop
>>>       > + ETH->DMASR = MG_BIT(16) | MG_BIT(6); // Clear flag
>>>       > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
>>>       > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
>>>       > + (MG_BIT(8) | MG_BIT(9))) &&
>>>       > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
>>>      frames
>>>       > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
>>>       > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>>>       > + // ETH->DMASR);
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > + }
>>>       > + s_rxdesc[s_rxno][0] = MG_BIT(31);
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > + }
>>>       > + }
>>>       > + // Cleanup flags
>>>       > + ETH->DMASR = MG_BIT(16) // NIS, normal interrupt summary
>>>       > + | MG_BIT(7); // Clear possible RBUS while processing
>>>       > + ETH->DMARPDR = 0; // and resume RX
>>>       > }
>>>       > +
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
>>>       > + mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
>>>       > + mg_tcpip_driver_stm32f_up};
>>>       > #endif
>>>       >
>>>       > -static inline size_t q_avail(struct queue *q) {
>>>       > - size_t n = 0;
>>>       > - if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n),
>>>      q->tail);
>>>       > - return n;
>>>       > -}
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/stm32h.c"
>>>       > +#endif
>>>       >
>>>       > -static size_t q_read(struct queue *q, void *buf) {
>>>       > - size_t n = q_avail(q);
>>>       > - if (n > 0) {
>>>       > - q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
>>>       > - q->tail = (q->tail + sizeof(n) + n) % q->len;
>>>       > - }
>>>       > - return n;
>>>       > -}
>>>       >
>>>       > -static struct mg_str mkstr(void *buf, size_t len) {
>>>       > - struct mg_str str = {(char *) buf, len};
>>>       > - return str;
>>>       > -}
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
>>>       > + MG_ENABLE_DRIVER_STM32H
>>>       > +struct stm32h_eth {
>>>       > + volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
>>>       > + RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR,
>>>      MACIVIR,
>>>       > + RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7],
>>>      MACISR,
>>>       > + MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2],
>>>      MACLCSR,
>>>       > + MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR,
>>>      RESERVED10,
>>>       > + MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR,
>>>      MACMDIODR,
>>>       > + RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR,
>>>      MACA1HR,
>>>       > + MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248],
>>>      MMCCR,
>>>       > + MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR,
>>>      MMCTMCGPR,
>>>       > + RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
>>>       > + RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
>>>       > + MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
>>>       > + RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
>>>       > + RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
>>>       > + MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR,
>>>      MACSSIR,
>>>       > + MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25,
>>>      MACTSSR,
>>>       > + RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR,
>>>      RESERVED28,
>>>       > + MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
>>>       > + RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR,
>>>      MACPPSIR,
>>>       > + MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R,
>>>      MACSPI2R, MACLMIR,
>>>       > + RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55],
>>>      MTLTQOMR,
>>>       > + MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR,
>>>      MTLRQMPOCR, MTLRQDR,
>>>       > + RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR,
>>>      RESERVED37[60], DMACCR,
>>>       > + DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
>>>       > + DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
>>>       > + DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42,
>>>      DMACCARDR,
>>>       > + RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR,
>>>      RESERVED45[2],
>>>       > + DMACMFCR;
>>>       > +};
>>>       > +#undef ETH
>>>       > +#define ETH \
>>>       > + ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL
>>>      + 0x8000UL))
>>>       >
>>>       > -static void mkpay(struct pkt *pkt, void *p) {
>>>       > - pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] -
>>>      (char *) p));
>>>       > +#define ETH_PKT_SIZE 1540 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 4 // Descriptor size (words)
>>>       > +
>>>       > +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX
>>>      descriptors
>>>       > +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX
>>>      descriptors
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>>>      ethernet buffers
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>>>      ethernet buffers
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +
>>>       > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + ETH->MACMDIOAR &= (0xF << 8);
>>>       > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
>>>      16) | 3 << 2;
>>>       > + ETH->MACMDIOAR |= MG_BIT(0);
>>>       > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
>>>       > + return (uint16_t) ETH->MACMDIODR;
>>>       > }
>>>       >
>>>       > -static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
>>>       > - const uint8_t *p = (const uint8_t *) buf;
>>>       > - for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] :
>>>      (uint32_t) (p[i] << 8);
>>>       > - return sum;
>>>       > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + ETH->MACMDIODR = val;
>>>       > + ETH->MACMDIOAR &= (0xF << 8);
>>>       > + ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg <<
>>>      16) | 1 << 2;
>>>       > + ETH->MACMDIOAR |= MG_BIT(0);
>>>       > + while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
>>>       > }
>>>       >
>>>       > -static uint16_t csumfin(uint32_t sum) {
>>>       > - while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
>>>       > - return mg_htons(~sum & 0xffff);
>>>       > +static uint32_t get_hclk(void) {
>>>       > + struct rcc {
>>>       > + volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1,
>>>      D1CFGR,
>>>       > + D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR,
>>>      PLL1FRACR,
>>>       > + PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR,
>>>      D2CCIP1R,
>>>       > + D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5,
>>>      BDCR, CSR,
>>>       > + RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR,
>>>      APB1LRSTR,
>>>       > + APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR,
>>>      RESERVED11[9],
>>>       > + RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR,
>>>      APB1HENR,
>>>       > + APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
>>>       > + AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR,
>>>      APB4LPENR,
>>>       > + RESERVED13[4];
>>>       > + } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
>>>       > + uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /*
>>>      8MHz */,
>>>       > + csi = 4000000 /* 4MHz */;
>>>       > + unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
>>>       > +
>>>       > + if (sel == 1) {
>>>       > + clk = csi;
>>>       > + } else if (sel == 2) {
>>>       > + clk = hse;
>>>       > + } else if (sel == 3) {
>>>       > + uint32_t vco, m, n, p;
>>>       > + unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
>>>       > + m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
>>>       > + n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
>>>       > + ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional
>>>      mode
>>>       > + p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
>>>       > + if (src == 1) {
>>>       > + clk = csi;
>>>       > + } else if (src == 2) {
>>>       > + clk = hse;
>>>       > + } else {
>>>       > + clk = hsi;
>>>       > + clk >>= ((rcc->CR & 3) >> 3);
>>>       > + }
>>>       > + vco = (uint32_t) ((uint64_t) clk * n / m);
>>>       > + clk = vco / p;
>>>       > + } else {
>>>       > + clk = hsi;
>>>       > + clk >>= ((rcc->CR & 3) >> 3);
>>>       > + }
>>>       > + const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div)
>>>       > + uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
>>>       > + if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
>>>       > + MG_DEBUG(("D1 CLK: %u", clk));
>>>       > + uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
>>>       > + if (hpre < 8) return clk;
>>>       > + return ((uint32_t) clk) >> cptab[hpre - 8];
>>>       > +}
>>>       > +
>>>       > +// Guess CR from AHB1 clock. MDC clock is generated from the ETH
>>>      peripheral
>>>       > +// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB
>>>      clock can
>>>       > +// be derived from HSI or CSI (internal RC) clocks, and those
>>>      can go above
>>>       > +// specs, the datasheets specify a range of frequencies and
>>>      activate one of a
>>>       > +// series of dividers to keep the MDC clock safely below 2.5MHz.
>>>      We guess a
>>>       > +// divider setting based on HCLK with some drift. If the user
>>>      uses a different
>>>       > +// clock from our defaults, needs to set the macros on top.
>>>      Valid for
>>>       > +// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock
>>>      has a 7.5 %
>>>       > +// worst case drift @ max temp)
>>>       > +static int guess_mdc_cr(void) {
>>>       > + const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR
>>>      values
>>>       > + const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective
>>>      HCLK dividers
>>>       > + uint32_t hclk = get_hclk(); // Guess system HCLK
>>>       > + int result = -1; // Invalid CR value
>>>       > + for (int i = 0; i < 6; i++) {
>>>       > + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > + result = crs[i];
>>>       > + break;
>>>       > + }
>>>       > + }
>>>       > + if (result < 0) MG_ERROR(("HCLK too high"));
>>>       > + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -static uint16_t ipcsum(const void *buf, size_t len) {
>>>       > - uint32_t sum = csumup(0, buf, len);
>>>       > - return csumfin(sum);
>>>       > -}
>>>       > +static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_stm32h_data *d =
>>>       > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
>>>       > + s_ifp = ifp;
>>>       > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>>>       > + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
>>>       >
>>>       > -// ARP cache is organised as a doubly linked list. A successful
>>>      cache lookup
>>>       > -// moves an entry to the head of the list. New entries are added
>>>      by replacing
>>>       > -// the last entry in the list with a new IP/MAC.
>>>       > -// ARP cache format: | prev | next | Entry0 | Entry1 | .... |
>>>      EntryN |
>>>       > -// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
>>>       > -// prev and next are 1-byte offsets in the cache, so cache size
>>>      is max 256 bytes
>>>       > -// ARP entry size is 12 bytes
>>>       > -static void arp_cache_init(uint8_t *p, int n, int size) {
>>>       > - for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i
>>>      - 1) * size);
>>>       > - for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i
>>>      + 1) * size);
>>>       > - p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
>>>       > - p[1] = p[3 + (n - 1) * size] = 2;
>>>       > -}
>>>       > + // Init RX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>>>      data buffer
>>>       > + s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN,
>>>      IOC, BUF1V
>>>       > + }
>>>       >
>>>       > -#if 0
>>>       > -static inline void arp_cache_dump(const uint8_t *p) {
>>>       > - MG_INFO(("ARP cache:"));
>>>       > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
>>>      + 1]) {
>>>       > - MG_INFO((" %I -> %A", 4, &p[j + 2], &p[j + 6]));
>>>       > + // Init TX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>>>       > }
>>>       > -}
>>>       > -#endif
>>>       >
>>>       > -static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF,
>>>      0xFF, 0xFF};
>>>       > + ETH->DMAMR |= MG_BIT(0); // Software reset
>>>       > + while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done
>>>       >
>>>       > -static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
>>>       > - uint8_t *p = ifp->arp_cache;
>>>       > - if (ip == 0) return NULL;
>>>       > - // use broadcast MAC for local and global broadcast IP
>>>       > - if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
>>>       > - return (uint8_t *) bcastmac;
>>>       > - for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j
>>>      + 1]) {
>>>       > - if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
>>>       > - p[1] = j, p[0] = p[j]; // Found entry! Point list head to us
>>>       > - // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
>>>       > - return p + j + 6; // And return MAC address
>>>       > - }
>>>       > - }
>>>       > - return NULL;
>>>       > -}
>>>       > + // Set MDC clock divider. If user told us the value, use it.
>>>      Otherwise, guess
>>>       > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>>>      d->mdc_cr;
>>>       > + ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
>>>       > +
>>>       > + // NOTE(scaprile): We do not use timing facilities so the DMA
>>>      engine does not
>>>       > + // re-write buffer address
>>>       > + ETH->DMAMR = 0 << 16; // use interrupt mode 0 (58.8.1) (reset
>>>      value)
>>>       > + ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this
>>>      actually needed
>>>       > + ETH->MACIER = 0; // Do not enable additional irq sources (reset
>>>      value)
>>>       > + ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause
>>>       > + // ETH->MACPFR = MG_BIT(31); // Receive all
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + mg_phy_init(&phy, phy_addr, phy_conf);
>>>       > + ETH->DMACRDLAR =
>>>       > + (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address
>>>       > + ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length
>>>       > + ETH->DMACRDTPR =
>>>       > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
>>>       > + 1]; // last valid descriptor address
>>>       > + ETH->DMACTDLAR =
>>>       > + (uint32_t) (uintptr_t) s_txdesc; // TX descriptors start address
>>>       > + ETH->DMACTDRLR = ETH_DESC_CNT - 1; // ring length
>>>       > + ETH->DMACTDTPR =
>>>       > + (uint32_t) (uintptr_t) s_txdesc; // first available descriptor
>>>      address
>>>       > + ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table)
>>>      (reset value)
>>>       > + ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE
>>>       > + ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
>>>       > + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved
>>>       > + ETH->MTLTQOMR |= MG_BIT(1); // TSF
>>>       > + ETH->MTLRQOMR |= MG_BIT(5); // RSF
>>>       > + ETH->DMACTCR |= MG_BIT(0); // ST
>>>       > + ETH->DMACRCR |= MG_BIT(0); // SR
>>>       >
>>>       > -static void arp_cache_add(struct mip_if *ifp, uint32_t ip,
>>>      uint8_t mac[6]) {
>>>       > - uint8_t *p = ifp->arp_cache;
>>>       > - if (ip == 0 || ip == ~0U) return; // Bad IP
>>>       > - if (arp_cache_find(ifp, ip) != NULL) return; // Already exists,
>>>      do nothing
>>>       > - memcpy(p + p[0] + 2, &ip, sizeof(ip)); // Replace last entry:
>>>      IP address
>>>       > - memcpy(p + p[0] + 6, mac, 6); // And MAC address
>>>       > - p[1] = p[0], p[0] = p[p[1]]; // Point list head to us
>>>       > - MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
>>>       > + // MAC address filtering
>>>       > + ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>>>       > + ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
>>>       > + ((uint32_t) ifp->mac[2] << 16) |
>>>       > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -static size_t ether_output(struct mip_if *ifp, size_t len) {
>>>       > - // size_t min = 64; // Pad short frames to 64 bytes (minimum
>>>      Ethernet size)
>>>       > - // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len
>>>      = min;
>>>       > - // mg_hexdump(ifp->tx.ptr, len);
>>>       > - return ifp->driver->tx(ifp->tx.ptr, len, ifp);
>>>       > +static uint32_t s_txno;
>>>       > +static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t
>>>      len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // Frame is too big
>>>       > + } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
>>>       > + s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X",
>>>      s_txdesc[i][3]));
>>>       > + len = 0; // All descriptors are busy, fail
>>>       > + } else {
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno][2] = (uint32_t) len; // Set data len
>>>       > + s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29); // FD, LD
>>>       > + s_txdesc[s_txno][3] |= MG_BIT(31); // Set OWN bit - let DMA
>>>      take over
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > + }
>>>       > + ETH->DMACSR |= MG_BIT(2) | MG_BIT(1); // Clear any prior TBU, TPS
>>>       > + ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno]; //
>>>      and resume
>>>       > + return len;
>>>       > + (void) ifp;
>>>       > }
>>>       >
>>>       > -static void arp_ask(struct mip_if *ifp, uint32_t ip) {
>>>       > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>>>       > - struct arp *arp = (struct arp *) (eth + 1);
>>>       > - memset(eth->dst, 255, sizeof(eth->dst));
>>>       > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
>>>       > - eth->type = mg_htons(0x806);
>>>       > - memset(arp, 0, sizeof(*arp));
>>>       > - arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
>>>       > - arp->plen = 4;
>>>       > - arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
>>>       > - memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
>>>       > - ether_output(ifp, PDIFF(eth, arp + 1));
>>>       > +static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_stm32h_data *d =
>>>       > + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
>>>       > + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + // tmp = reg with flags set to the most likely situation: 100M
>>>      full-duplex
>>>       > + // if(link is slow or half) set flags otherwise
>>>       > + // reg = tmp
>>>       > + uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M,
>>>      Full-duplex
>>>       > + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M
>>>       > + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex
>>>       > + ETH->MACCR = maccr; // IRQ handler does not fiddle with this
>>>      register
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
>>>       > + maccr & MG_BIT(13) ? "full" : "half"));
>>>       > + }
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -static void onstatechange(struct mip_if *ifp) {
>>>       > - if (ifp->state == MIP_STATE_READY) {
>>>       > - MG_INFO(("READY, IP: %I", 4, &ifp->ip));
>>>       > - MG_INFO((" GW: %I", 4, &ifp->gw));
>>>       > - if (ifp->lease_expire > ifp->now) {
>>>       > - MG_INFO(
>>>       > - (" Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
>>>       > +void ETH_IRQHandler(void);
>>>       > +static uint32_t s_rxno;
>>>       > +void ETH_IRQHandler(void) {
>>>       > + if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop
>>>       > + ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag
>>>       > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > + if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break; // exit when done
>>>       > + if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
>>>       > + (MG_BIT(28) | MG_BIT(29))) &&
>>>       > + !(s_rxdesc[s_rxno][3] & MG_BIT(15))) { // skip partial/errored
>>>      frames
>>>       > + uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
>>>       > + // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len,
>>>      s_rxdesc[s_rxno][3],
>>>       > + // ETH->DMACSR));
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > + }
>>>       > + s_rxdesc[s_rxno][3] =
>>>       > + MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > }
>>>       > - arp_ask(ifp, ifp->gw);
>>>       > - } else if (ifp->state == MIP_STATE_UP) {
>>>       > - MG_ERROR(("Link up"));
>>>       > - } else if (ifp->state == MIP_STATE_DOWN) {
>>>       > - MG_ERROR(("Link down"));
>>>       > }
>>>       > + ETH->DMACSR =
>>>       > + MG_BIT(7) | MG_BIT(8); // Clear possible RBU RPS while processing
>>>       > + ETH->DMACRDTPR =
>>>       > + (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1]; // and
>>>      resume RX
>>>       > }
>>>       >
>>>       > -static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto,
>>>      uint32_t ip_src,
>>>       > - uint32_t ip_dst, size_t plen) {
>>>       > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>>>       > - struct ip *ip = (struct ip *) (eth + 1);
>>>       > - uint8_t *mac = arp_cache_find(ifp, ip_dst); // Dst IP in ARP
>>>      cache ?
>>>       > - if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
>>>       > - arp_ask(ifp, ip_dst); // Same net, lookup
>>>       > - if (!mac) mac = arp_cache_find(ifp, ifp->gw); // Use gateway MAC
>>>       > - if (!mac) arp_ask(ifp, ifp->gw); // Not found? lookup
>>>       > - if (mac) memcpy(eth->dst, mac, sizeof(eth->dst)); // Found? Use it
>>>       > - if (!mac) memset(eth->dst, 255, sizeof(eth->dst)); // No? Use
>>>      broadcast
>>>       > - memcpy(eth->src, ifp->mac, sizeof(eth->src)); // TODO(cpq): ARP
>>>      lookup
>>>       > - eth->type = mg_htons(0x800);
>>>       > - memset(ip, 0, sizeof(*ip));
>>>       > - ip->ver = 0x45; // Version 4, header length 5 words
>>>       > - ip->frag = 0x40; // Don't fragment
>>>       > - ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
>>>       > - ip->ttl = 64;
>>>       > - ip->proto = proto;
>>>       > - ip->src = ip_src;
>>>       > - ip->dst = ip_dst;
>>>       > - ip->csum = ipcsum(ip, sizeof(*ip));
>>>       > - return ip;
>>>       > -}
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
>>>       > + mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
>>>       > + mg_tcpip_driver_stm32h_up};
>>>       > +#endif
>>>       >
>>>       > -static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t
>>>      sport,
>>>       > - uint32_t ip_dst, uint16_t dport, const void *buf,
>>>       > - size_t len) {
>>>       > - struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len +
>>>      sizeof(struct udp));
>>>       > - struct udp *udp = (struct udp *) (ip + 1);
>>>       > - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
>>>       > - udp->sport = sport;
>>>       > - udp->dport = dport;
>>>       > - udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
>>>       > - udp->csum = 0;
>>>       > - uint32_t cs = csumup(0, udp, sizeof(*udp));
>>>       > - cs = csumup(cs, buf, len);
>>>       > - cs = csumup(cs, &ip->src, sizeof(ip->src));
>>>       > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>>>       > - cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
>>>       > - udp->csum = csumfin(cs);
>>>       > - memmove(udp + 1, buf, len);
>>>       > - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
>>>       > - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) +
>>>      sizeof(*udp) + len);
>>>       > -}
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/tm4c.c"
>>>       > +#endif
>>>       >
>>>       > -static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
>>>       > - uint8_t *opts, size_t optslen) {
>>>       > - struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>>>       > - dhcp.magic = mg_htonl(0x63825363);
>>>       > - memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
>>>       > - memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
>>>       > - memcpy(&dhcp.options, opts, optslen);
>>>       > - tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp,
>>>      sizeof(dhcp));
>>>       > -}
>>>       >
>>>       > -static void tx_dhcp_request(struct mip_if *ifp, uint32_t src,
>>>      uint32_t dst) {
>>>       > - uint8_t opts[] = {
>>>       > - 53, 1, 3, // Type: DHCP request
>>>       > - 55, 2, 1, 3, // GW and mask
>>>       > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
>>>       > - 54, 4, 0, 0, 0, 0, // DHCP server ID
>>>       > - 50, 4, 0, 0, 0, 0, // Requested IP
>>>       > - 255 // End of options
>>>       > - };
>>>       > - memcpy(opts + 14, &dst, sizeof(dst));
>>>       > - memcpy(opts + 20, &src, sizeof(src));
>>>       > - tx_dhcp(ifp, src, dst, opts, sizeof(opts));
>>>       > -}
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>>>      MG_ENABLE_DRIVER_TM4C
>>>       > +struct tm4c_emac {
>>>       > + volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH,
>>>      EMACHASHTBLL,
>>>       > + EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0,
>>>      EMACSTATUS,
>>>       > + EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM,
>>>      EMACADDR0H,
>>>       > + EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L,
>>>      EMACADDR3H,
>>>       > + EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
>>>       > + EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
>>>       > + EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL,
>>>      RESERVED6[4],
>>>       > + EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4],
>>>      EMACRXCNTCRCERR,
>>>       > + EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
>>>       > + EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL,
>>>      EMACSUBSECINC,
>>>       > + EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
>>>       > + EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
>>>       > + RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
>>>       > + EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR,
>>>      EMACTXDLADDR,
>>>       > + EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
>>>       > + RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA,
>>>      EMACHOSRXBA,
>>>       > + RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
>>>       > + EMACEPHYIM, EMACEPHYIMSC;
>>>       > +};
>>>       > +#undef EMAC
>>>       > +#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
>>>       >
>>>       > -static void tx_dhcp_discover(struct mip_if *ifp) {
>>>       > - uint8_t opts[] = {
>>>       > - 53, 1, 1, // Type: DHCP discover
>>>       > - 55, 2, 1, 3, // Parameters: ip, mask
>>>       > - 255 // End of options
>>>       > - };
>>>       > - tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
>>>       > - MG_DEBUG(("DHCP discover sent"));
>>>       > -}
>>>       > +#define ETH_PKT_SIZE 1540 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 4 // Descriptor size (words)
>>>       >
>>>       > -static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
>>>       > - // ARP request. Make a response, then send
>>>       > - MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4,
>>>      &pkt->arp->spa, 4,
>>>       > - &pkt->arp->tpa));
>>>       > - struct eth *eth = (struct eth *) ifp->tx.ptr;
>>>       > - struct arp *arp = (struct arp *) (eth + 1);
>>>       > - memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
>>>       > - memcpy(eth->src, ifp->mac, sizeof(eth->src));
>>>       > - eth->type = mg_htons(0x806);
>>>       > - *arp = *pkt->arp;
>>>       > - arp->op = mg_htons(2);
>>>       > - memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
>>>       > - memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
>>>       > - arp->tpa = pkt->arp->spa;
>>>       > - arp->spa = ifp->ip;
>>>       > - MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
>>>       > - ether_output(ifp, PDIFF(eth, arp + 1));
>>>       > - } else if (pkt->arp->op == mg_htons(2)) {
>>>       > - if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) !=
>>>      0) return;
>>>       > - // MG_INFO(("ARP RESPONSE"));
>>>       > - arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
>>>       > - }
>>>       > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX
>>>      ethernet buffers
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX
>>>      ethernet buffers
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +enum {
>>>       > + EPHY_ADDR = 0,
>>>       > + EPHYBMCR = 0,
>>>       > + EPHYBMSR = 1,
>>>       > + EPHYSTS = 16
>>>       > +}; // PHY constants
>>>       > +
>>>       > +static inline void tm4cspin(volatile uint32_t count) {
>>>       > + while (count--) (void) 0;
>>>       > }
>>>       >
>>>       > -static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - // MG_DEBUG(("ICMP %d", (int) len));
>>>       > - if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst ==
>>>      ifp->ip) {
>>>       > - size_t hlen = sizeof(struct eth) + sizeof(struct ip) +
>>>      sizeof(struct icmp);
>>>       > - size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
>>>       > - if (plen > space) plen = space;
>>>       > - struct ip *ip =
>>>       > - tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
>>>       > - struct icmp *icmp = (struct icmp *) (ip + 1);
>>>       > - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0
>>>       > - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX
>>>       > - icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
>>>       > - ether_output(ifp, hlen + plen);
>>>       > - }
>>>       > +static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + EMAC->EMACMIIADDR &= (0xf << 2);
>>>       > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>>>      << 6);
>>>       > + EMAC->EMACMIIADDR |= MG_BIT(0);
>>>       > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
>>>       > + return EMAC->EMACMIIDATA;
>>>       > }
>>>       >
>>>       > -static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - uint32_t ip = 0, gw = 0, mask = 0;
>>>       > - uint8_t *p = pkt->dhcp->options,
>>>       > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
>>>       > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>>>       > - while (p + 1 < end && p[0] != 255) { // Parse options
>>>       > - if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) { //
>>>      Mask
>>>       > - memcpy(&mask, p + 2, sizeof(mask));
>>>       > - } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end)
>>>      { // GW
>>>       > - memcpy(&gw, p + 2, sizeof(gw));
>>>       > - ip = pkt->dhcp->yiaddr;
>>>       > - } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease
>>>       > - uint32_t lease = 0;
>>>       > - memcpy(&lease, p + 2, sizeof(lease));
>>>       > - ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
>>>       > - }
>>>       > - p += p[1] + 2;
>>>       > - }
>>>       > - if (ip && mask && gw && ifp->ip == 0) {
>>>       > - arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *)
>>>      pkt->raw.ptr)->src);
>>>       > - ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
>>>       > - ifp->state = MIP_STATE_READY;
>>>       > - onstatechange(ifp);
>>>       > - tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
>>>       > - }
>>>       > +static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t
>>>      val) {
>>>       > + EMAC->EMACMIIDATA = val;
>>>       > + EMAC->EMACMIIADDR &= (0xf << 2);
>>>       > + EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg
>>>      << 6) | MG_BIT(1);
>>>       > + EMAC->EMACMIIADDR |= MG_BIT(0);
>>>       > + while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
>>>       > }
>>>       >
>>>       > -// Simple DHCP server that assigns a next IP address: ifp->ip + 1
>>>       > -static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - uint8_t op = 0, *p = pkt->dhcp->options,
>>>       > - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
>>>       > - if (end < (uint8_t *) (pkt->dhcp + 1)) return;
>>>       > - // struct dhcp *req = pkt->dhcp;
>>>       > - struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
>>>       > - res.yiaddr = ifp->ip;
>>>       > - ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
>>>       > - while (p + 1 < end && p[0] != 255) { // Parse options
>>>       > - if (p[0] == 53 && p[1] == 1 && p + 2 < end) { // Message type
>>>       > - op = p[2];
>>>       > +static uint32_t get_sysclk(void) {
>>>       > + struct sysctl {
>>>       > + volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43],
>>>      PLLFREQ0,
>>>       > + PLLFREQ1;
>>>       > + } *sysctl = (struct sysctl *) 0x400FE000;
>>>       > + uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc =
>>>      25000000 /* 25MHz */;
>>>       > + if (sysctl->RSCLKCFG & (1 << 28)) { // USEPLL
>>>       > + uint32_t fin, vco, mdiv, n, q, psysdiv;
>>>       > + uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
>>>       > + if (pllsrc == 0) {
>>>       > + clk = piosc;
>>>       > + } else if (pllsrc == 3) {
>>>       > + clk = mosc;
>>>       > + } else {
>>>       > + MG_ERROR(("Unsupported clock source"));
>>>       > }
>>>       > - p += p[1] + 2;
>>>       > - }
>>>       > - if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
>>>       > - uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or
>>>      DHCP ACK
>>>       > - uint8_t opts[] = {
>>>       > - 53, 1, msg, // Message type
>>>       > - 1, 4, 0, 0, 0, 0, // Subnet mask
>>>       > - 54, 4, 0, 0, 0, 0, // Server ID
>>>       > - 12, 3, 'm', 'i', 'p', // Host name: "mip"
>>>       > - 51, 4, 255, 255, 255, 255, // Lease time
>>>       > - 255 // End of options
>>>       > - };
>>>       > - memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
>>>       > - memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
>>>       > - memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
>>>       > - memcpy(&res.options, opts, sizeof(opts));
>>>       > - res.magic = pkt->dhcp->magic;
>>>       > - res.xid = pkt->dhcp->xid;
>>>       > - arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
>>>       > - tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr,
>>>      mg_htons(68),
>>>       > - &res, sizeof(res));
>>>       > + q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
>>>       > + n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
>>>       > + fin = clk / ((q + 1) * (n + 1));
>>>       > + mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
>>>       > + 0; // mint + (mfrac / 1024); MFRAC not supported
>>>       > + psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
>>>       > + vco = (uint32_t) ((uint64_t) fin * mdiv);
>>>       > + return vco / (psysdiv + 1);
>>>       > }
>>>       > -}
>>>       > -
>>>       > -static struct mg_connection *getpeer(struct mg_mgr *mgr, struct
>>>      pkt *pkt,
>>>       > - bool lsn) {
>>>       > - struct mg_connection *c = NULL;
>>>       > - for (c = mgr->conns; c != NULL; c = c->next) {
>>>       > - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport)
>>>      break;
>>>       > - if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
>>>       > - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
>>>       > - break;
>>>       > + uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
>>>       > + if (oscsrc == 0) {
>>>       > + clk = piosc;
>>>       > + } else if (oscsrc == 3) {
>>>       > + clk = mosc;
>>>       > + } else {
>>>       > + MG_ERROR(("Unsupported clock source"));
>>>       > }
>>>       > - return c;
>>>       > + uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
>>>       > + return clk / (osysdiv + 1);
>>>       > }
>>>       >
>>>       > -static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
>>>       > - if (c == NULL) {
>>>       > - // No UDP listener on this port. Should send ICMP, but keep
>>>      silent.
>>>       > - } else if (c != NULL) {
>>>       > - c->rem.port = pkt->udp->sport;
>>>       > - c->rem.ip = pkt->ip->src;
>>>       > - if (c->recv.len >= MG_MAX_RECV_SIZE) {
>>>       > - mg_error(c, "max_recv_buf_size reached");
>>>       > - } else if (c->recv.size - c->recv.len < pkt->pay.len &&
>>>       > - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
>>>       > - mg_error(c, "oom");
>>>       > - } else {
>>>       > - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
>>>       > - c->recv.len += pkt->pay.len;
>>>       > - mg_call(c, MG_EV_READ, &pkt->pay.len);
>>>       > +// Guess CR from SYSCLK. MDC clock is generated from SYSCLK
>>>      (AHB); as per
>>>       > +// 802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB
>>>      clock can be
>>>       > +// derived from the PIOSC (internal RC), and it can go above
>>>      specs, the
>>>       > +// datasheets specify a range of frequencies and activate one of
>>>      a series of
>>>       > +// dividers to keep the MDC clock safely below 2.5MHz. We guess
>>>      a divider
>>>       > +// setting based on SYSCLK with a +5% drift. If the user uses a
>>>      different clock
>>>       > +// from our defaults, needs to set the macros on top Valid for
>>>      TM4C129x (20.7)
>>>       > +// (4.5% worst case drift)
>>>       > +// The PHY receives the main oscillator (MOSC) (20.3.1)
>>>       > +static int guess_mdc_cr(void) {
>>>       > + uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values
>>>       > + uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers
>>>       > + uint32_t sysclk = get_sysclk(); // Guess system SYSCLK
>>>       > + int result = -1; // Invalid CR value
>>>       > + if (sysclk < 25000000) {
>>>       > + MG_ERROR(("SYSCLK too low"));
>>>       > + } else {
>>>       > + for (int i = 0; i < 4; i++) {
>>>       > + if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
>>>       > + result = crs[i];
>>>       > + break;
>>>       > + }
>>>       > }
>>>       > + if (result < 0) MG_ERROR(("SYSCLK too high"));
>>>       > }
>>>       > + MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
>>>       > + return result;
>>>       > }
>>>       >
>>>       > -static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip,
>>>      uint8_t flags,
>>>       > - uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
>>>       > - const void *buf, size_t len) {
>>>       > - struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct
>>>      tcp) + len);
>>>       > - struct tcp *tcp = (struct tcp *) (ip + 1);
>>>       > - memset(tcp, 0, sizeof(*tcp));
>>>       > - if (buf != NULL && len) memmove(tcp + 1, buf, len);
>>>       > - tcp->sport = sport;
>>>       > - tcp->dport = dport;
>>>       > - tcp->seq = seq;
>>>       > - tcp->ack = ack;
>>>       > - tcp->flags = flags;
>>>       > - tcp->win = mg_htons(8192);
>>>       > - tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
>>>       > - uint32_t cs = 0;
>>>       > - uint16_t n = (uint16_t) (sizeof(*tcp) + len);
>>>       > - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t)
>>>      (n & 255)};
>>>       > - cs = csumup(cs, tcp, n);
>>>       > - cs = csumup(cs, &ip->src, sizeof(ip->src));
>>>       > - cs = csumup(cs, &ip->dst, sizeof(ip->dst));
>>>       > - cs = csumup(cs, pseudo, sizeof(pseudo));
>>>       > - tcp->csum = csumfin(cs);
>>>       > - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
>>>       > -}
>>>       > +static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_tm4c_data *d =
>>>       > + (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
>>>       > + s_ifp = ifp;
>>>       >
>>>       > -static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt,
>>>      uint8_t flags,
>>>       > - uint32_t seq, const void *buf, size_t len) {
>>>       > - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
>>>       > - return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport,
>>>      pkt->tcp->sport, seq,
>>>       > - mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
>>>       > -}
>>>       > + // Init RX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_rxdesc[i][0] = MG_BIT(31); // Own
>>>       > + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd
>>>      address chained
>>>       > + s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to
>>>      data buffer
>>>       > + s_rxdesc[i][3] =
>>>       > + (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
>>>       > + }
>>>       >
>>>       > -static void settmout(struct mg_connection *c, uint8_t type) {
>>>       > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS :
>>>      MIP_TCP_KEEPALIVE_MS;
>>>       > - s->timer = ifp->now + n;
>>>       > - s->ttype = type;
>>>       > - MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
>>>       > -}
>>>       > + // Init TX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer
>>>       > + s_txdesc[i][3] =
>>>       > + (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
>>>       > + }
>>>       >
>>>       > -static struct mg_connection *accept_conn(struct mg_connection *lsn,
>>>       > - struct pkt *pkt) {
>>>       > - struct mg_connection *c = mg_alloc_conn(lsn->mgr);
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>>>      mg_ntohl(pkt->tcp->seq);
>>>       > - settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > - c->rem.ip = pkt->ip->src;
>>>       > - c->rem.port = pkt->tcp->sport;
>>>       > - MG_DEBUG(
>>>       > - ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip,
>>>      mg_ntohs(c->rem.port)));
>>>       > - LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
>>>       > - c->is_accepted = 1;
>>>       > - c->is_hexdumping = lsn->is_hexdumping;
>>>       > - c->pfn = lsn->pfn;
>>>       > - c->loc = lsn->loc;
>>>       > - c->pfn_data = lsn->pfn_data;
>>>       > - c->fn = lsn->fn;
>>>       > - c->fn_data = lsn->fn_data;
>>>       > - mg_call(c, MG_EV_OPEN, NULL);
>>>       > - mg_call(c, MG_EV_ACCEPT, NULL);
>>>       > - return c;
>>>       > + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset
>>>       > + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); //
>>>      Wait until done
>>>       > +
>>>       > + // Set MDC clock divider. If user told us the value, use it.
>>>      Otherwise, guess
>>>       > + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() :
>>>      d->mdc_cr;
>>>       > + EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
>>>       > +
>>>       > + // NOTE(cpq): we do not use extended descriptor bit 7, and do
>>>      not use
>>>       > + // hardware checksum. Therefore, descriptor size is 4, not 8
>>>       > + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) |
>>>      MG_BIT(23) | MG_BIT(25);
>>>       > + EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT
>>>       > + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause
>>>       > + // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all
>>>       > + // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
>>>       > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset
>>>      internal PHY (EPHY)
>>>       > + emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set
>>>      autonegotiation
>>>       > + EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX
>>>      descriptors
>>>       > + EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX
>>>      descriptors
>>>       > + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE
>>>       > + EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) |
>>>      MG_BIT(14); // RE, TE, Duplex, Fast
>>>       > + EMAC->EMACDMAOPMODE =
>>>       > + MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST,
>>>      TSF, RSF
>>>       > + EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
>>>       > + EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
>>>       > + ((uint32_t) ifp->mac[2] << 16) |
>>>       > + ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
>>>       > + // NOTE(scaprile) There are 3 additional slots for filtering,
>>>      disabled by
>>>       > + // default. This also applies to the STM32 driver (at least for
>>>      F7)
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
>>>       > - if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len -
>>>      max_headers_len;
>>>       > - if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port,
>>>      c->rem.port,
>>>       > - mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
>>>       > - s->seq += (uint32_t) len;
>>>       > - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > +static uint32_t s_txno;
>>>       > +static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // fail
>>>       > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No descriptors available"));
>>>       > + // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
>>>       > + // EMAC->EMACDMARIS);
>>>       > + len = 0; // fail
>>>       > } else {
>>>       > - return MG_IO_ERR;
>>>       > + memcpy(s_txbuf[s_txno], buf, len); // Copy data
>>>       > + s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
>>>       > + s_txdesc[s_txno][0] =
>>>       > + MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30); //
>>>      Chain,FS,LS,IC
>>>       > + s_txdesc[s_txno][0] |= MG_BIT(31); // Set OWN bit - let DMA
>>>      take over
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > }
>>>       > - return (long) len;
>>>       > + EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior
>>>      TU/UNF
>>>       > + EMAC->EMACTXPOLLD = 0; // and resume
>>>       > + return len;
>>>       > + (void) ifp;
>>>       > }
>>>       >
>>>       > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - if (s->raw.len == 0) return MG_IO_WAIT;
>>>       > - if (len > s->raw.len) len = s->raw.len;
>>>       > - memcpy(buf, s->raw.buf, len);
>>>       > - mg_iobuf_del(&s->raw, 0, len);
>>>       > - MG_DEBUG(("%lu", len));
>>>       > - return (long) len;
>>>       > +static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
>>>       > + uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
>>>       > + bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
>>>       > + // tmp = reg with flags set to the most likely situation: 100M
>>>      full-duplex
>>>       > + // if(link is slow or half) set flags otherwise
>>>       > + // reg = tmp
>>>       > + uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); //
>>>      100M, Full-duplex
>>>       > + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M
>>>       > + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex
>>>       > + EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with
>>>      this register
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 :
>>>      10,
>>>       > + emaccfg & MG_BIT(11) ? "full" : "half"));
>>>       > + }
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -static void read_conn(struct mg_connection *c, struct pkt *pkt) {
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
>>>       > - uint32_t seq = mg_ntohl(pkt->tcp->seq);
>>>       > - s->raw.align = c->recv.align;
>>>       > - if (pkt->tcp->flags & TH_FIN) {
>>>       > - s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq =
>>>      mg_htonl(pkt->tcp->ack);
>>>       > - c->is_closing = 1;
>>>       > - } else if (pkt->pay.len == 0) {
>>>       > - // TODO(cpq): handle this peer's ACK
>>>       > - } else if (seq != s->ack) {
>>>       > - // TODO(cpq): peer sent us SEQ which we don't expect.
>>>      Retransmit rather
>>>       > - // than close this connection
>>>       > - mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
>>>       > - } else if (io->size - io->len < pkt->pay.len &&
>>>       > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>>>       > - mg_error(c, "oom");
>>>       > - } else {
>>>       > - // Copy TCP payload into the IO buffer. If the connection is
>>>      plain text, we
>>>       > - // copy to c->recv. If the connection is TLS, this data is
>>>      encrypted,
>>>       > - // therefore we copy that encrypted data to the s->raw iobuffer
>>>      instead,
>>>       > - // and then call mg_tls_recv() to decrypt it. NOTE:
>>>      mg_tls_recv() will
>>>       > - // call back mg_io_recv() which grabs raw data from s->raw
>>>       > - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
>>>       > - io->len += pkt->pay.len;
>>>       > -
>>>       > - MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq),
>>>      s->ack));
>>>       > - // Advance ACK counter
>>>       > - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
>>>       > -#if 0
>>>       > - // Send ACK immediately
>>>       > - MG_DEBUG((" imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
>>>       > - tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK,
>>>      c->loc.port,
>>>       > - c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
>>>       > -#else
>>>       > - // if not already running, setup a timer to send an ACK later
>>>       > - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
>>>       > -#endif
>>>       > -
>>>       > - if (c->is_tls) {
>>>       > - // TLS connection. Make room for decrypted data in c->recv
>>>       > - io = &c->recv;
>>>       > - if (io->size - io->len < pkt->pay.len &&
>>>       > - !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
>>>       > - mg_error(c, "oom");
>>>       > - } else {
>>>       > - // Decrypt data directly into c->recv
>>>       > - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
>>>       > - if (n == MG_IO_ERR) {
>>>       > - mg_error(c, "TLS recv error");
>>>       > - } else if (n > 0) {
>>>       > - // Decrypted successfully - trigger MG_EV_READ
>>>       > - io->len += (size_t) n;
>>>       > - mg_call(c, MG_EV_READ, &n);
>>>       > - }
>>>       > +void EMAC0_IRQHandler(void);
>>>       > +static uint32_t s_rxno;
>>>       > +void EMAC0_IRQHandler(void) {
>>>       > + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop
>>>       > + EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag
>>>       > + for (uint32_t i = 0; i < 10; i++) { // read as they arrive but
>>>      not forever
>>>       > + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done
>>>       > + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
>>>      (MG_BIT(8) | MG_BIT(9))) &&
>>>       > + !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored
>>>      frames
>>>       > + uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
>>>       > + // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
>>>       > + // EMAC->EMACDMARIS);
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
>>>       > }
>>>       > - } else {
>>>       > - // Plain text connection, data is already in c->recv, trigger
>>>      MG_EV_READ
>>>       > - mg_call(c, MG_EV_READ, &pkt->pay.len);
>>>       > + s_rxdesc[s_rxno][0] = MG_BIT(31);
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > }
>>>       > }
>>>       > + EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while
>>>      processing
>>>       > + EMAC->EMACRXPOLLD = 0; // and resume RX
>>>       > }
>>>       >
>>>       > -static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
>>>       > - struct connstate *s = c == NULL ? NULL : (struct connstate *)
>>>      (c + 1);
>>>       > -#if 0
>>>       > - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int)
>>>      pkt->pay.len));
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_tm4c =
>>>      {mg_tcpip_driver_tm4c_init,
>>>       > + mg_tcpip_driver_tm4c_tx, NULL,
>>>       > + mg_tcpip_driver_tm4c_up};
>>>       > #endif
>>>       > - if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN
>>>      | TH_ACK)) {
>>>       > - s->seq = mg_ntohl(pkt->tcp->ack), s->ack =
>>>      mg_ntohl(pkt->tcp->seq) + 1;
>>>       > - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > - c->is_connecting = 0; // Client connected
>>>       > - settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > - mg_call(c, MG_EV_CONNECT, NULL); // Let user know
>>>       > - } else if (c != NULL && c->is_connecting) {
>>>       > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > - } else if (c != NULL && pkt->tcp->flags & TH_RST) {
>>>       > - mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>>>       > - } else if (c != NULL) {
>>>       > -#if 0
>>>       > - MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
>>>       > - 4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
>>>       > - 4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
>>>       > - mg_hexdump(pkt->pay.buf, pkt->pay.len);
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/w5500.c"
>>>       > #endif
>>>       > - s->tmiss = 0; // Reset missed keep-alive counter
>>>       > - if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
>>>       > - settmout(c, MIP_TTYPE_KEEPALIVE); // unless a former ACK
>>>      timeout is pending
>>>       > - read_conn(c, pkt); // Override timer with ACK timeout if needed
>>>       > - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
>>>       > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > - } else if (pkt->tcp->flags & TH_RST) {
>>>       > - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
>>>       > - // ignore RST if not connected
>>>       > - } else if (pkt->tcp->flags & TH_SYN) {
>>>       > - // Use peer's source port as ISN, in order to recognise the
>>>      handshake
>>>       > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
>>>       > - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
>>>       > - } else if (pkt->tcp->flags & TH_FIN) {
>>>       > - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport)
>>>      + 1U) {
>>>       > - accept_conn(c, pkt);
>>>       > - } else if (!c->is_accepted ) { // no peer
>>>       > - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
>>>       > - } else {
>>>       > - // MG_DEBUG(("dropped silently.."));
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
>>>      MG_ENABLE_DRIVER_W5500
>>>       > +
>>>       > +enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
>>>       > +
>>>       > +static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr,
>>>       > + bool wr, void *buf, size_t len) {
>>>       > + size_t i;
>>>       > + uint8_t *p = (uint8_t *) buf;
>>>       > + uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
>>>       > + (uint8_t) ((block << 3) | (wr ? 4 : 0))};
>>>       > + s->begin(s->spi);
>>>       > + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
>>>       > + for (i = 0; i < len; i++) {
>>>       > + uint8_t r = s->txn(s->spi, p[i]);
>>>       > + if (!wr) p[i] = r;
>>>       > }
>>>       > + s->end(s->spi);
>>>       > }
>>>       >
>>>       > -static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - // MG_DEBUG(("IP %d", (int) pkt->pay.len));
>>>       > - if (pkt->ip->proto == 1) {
>>>       > - pkt->icmp = (struct icmp *) (pkt->ip + 1);
>>>       > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>>>       > - mkpay(pkt, pkt->icmp + 1);
>>>       > - rx_icmp(ifp, pkt);
>>>       > - } else if (pkt->ip->proto == 17) {
>>>       > - pkt->udp = (struct udp *) (pkt->ip + 1);
>>>       > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
>>>       > - mkpay(pkt, pkt->udp + 1);
>>>       > - if (pkt->udp->dport == mg_htons(68)) {
>>>       > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>>>       > - mkpay(pkt, pkt->dhcp + 1);
>>>       > - rx_dhcp_client(ifp, pkt);
>>>       > - } else if (ifp->enable_dhcp_server && pkt->udp->dport ==
>>>      mg_htons(67)) {
>>>       > - pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
>>>       > - mkpay(pkt, pkt->dhcp + 1);
>>>       > - rx_dhcp_server(ifp, pkt);
>>>       > - } else {
>>>       > - rx_udp(ifp, pkt);
>>>       > +// clang-format off
>>>       > +static void w5500_wn(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
>>>      true, buf, len); }
>>>       > +static void w5500_w1(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
>>>       > +static void w5500_w2(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >>
>>>      8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf,
>>>      sizeof(buf)); }
>>>       > +static void w5500_rn(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr,
>>>      false, buf, len); }
>>>       > +static uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1);
>>>      return r; }
>>>       > +static uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block,
>>>      uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr,
>>>      buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
>>>       > +// clang-format on
>>>       > +
>>>       > +static size_t w5500_rx(void *buf, size_t buflen, struct
>>>      mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>>>       > + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read
>>>      recv len
>>>       > + while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2; // Until
>>>      it is stable
>>>       > + // printf("RSR: %d\n", (int) n);
>>>       > + if (n > 0) {
>>>       > + uint16_t ptr = w5500_r2(s, W5500_S0, 0x28); // Get read pointer
>>>       > + n = w5500_r2(s, W5500_RX0, ptr); // Read frame length
>>>       > + if (n <= len + 2 && n > 1) {
>>>       > + r = (uint16_t) (n - 2);
>>>       > + w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
>>>       > }
>>>       > - } else if (pkt->ip->proto == 6) {
>>>       > - pkt->tcp = (struct tcp *) (pkt->ip + 1);
>>>       > - if (pkt->pay.len < sizeof(*pkt->tcp)) return;
>>>       > - mkpay(pkt, pkt->tcp + 1);
>>>       > - uint16_t iplen = mg_ntohs(pkt->ip->len);
>>>       > - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off
>>>       >> 4) * 4U));
>>>       > - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
>>>       > - rx_tcp(ifp, pkt);
>>>       > + w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n)); // Advance
>>>      read pointer
>>>       > + w5500_w1(s, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
>>>       > + // printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
>>>       > }
>>>       > + return r;
>>>       > }
>>>       >
>>>       > -static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
>>>       > - // MG_DEBUG(("IP %d", (int) len));
>>>       > - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
>>>       > - pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
>>>       > - if (pkt->pay.len < sizeof(*pkt->icmp)) return;
>>>       > - mkpay(pkt, pkt->icmp + 1);
>>>       > - rx_icmp(ifp, pkt);
>>>       > - } else if (pkt->ip6->proto == 17) {
>>>       > - pkt->udp = (struct udp *) (pkt->ip6 + 1);
>>>       > - if (pkt->pay.len < sizeof(*pkt->udp)) return;
>>>       > - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
>>>       > - // mg_htons(udp->dport)));
>>>       > - mkpay(pkt, pkt->udp + 1);
>>>       > +static size_t w5500_tx(const void *buf, size_t buflen,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>>>       > + uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
>>>       > + while (n < len) n = w5500_r2(s, W5500_S0, 0x20); // Wait for space
>>>       > + ptr = w5500_r2(s, W5500_S0, 0x24); // Get write pointer
>>>       > + w5500_wn(s, W5500_TX0, ptr, (void *) buf, len); // Write data
>>>       > + w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len)); // Advance
>>>      write pointer
>>>       > + w5500_w1(s, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
>>>       > + for (i = 0; i < 40; i++) {
>>>       > + uint8_t ir = w5500_r1(s, W5500_S0, 2); // Read S0 IR
>>>       > + if (ir == 0) continue;
>>>       > + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len,
>>>      (int) n, ptr);
>>>       > + w5500_w1(s, W5500_S0, 2, ir); // Write S0 IR: clear it!
>>>       > + if (ir & 8) len = 0; // Timeout. Report error
>>>       > + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
>>>       > }
>>>       > + return len;
>>>       > }
>>>       >
>>>       > -static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
>>>       > - const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
>>>       > - struct pkt pkt;
>>>       > - memset(&pkt, 0, sizeof(pkt));
>>>       > - pkt.raw.ptr = (char *) buf;
>>>       > - pkt.raw.len = len;
>>>       > - pkt.eth = (struct eth *) buf;
>>>       > - if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt?
>>>       > - if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
>>>       > - memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
>>>       > - // Not for us. Drop silently
>>>       > - } else if (pkt.eth->type == mg_htons(0x806)) {
>>>       > - pkt.arp = (struct arp *) (pkt.eth + 1);
>>>       > - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;
>>>      // Truncated
>>>       > - rx_arp(ifp, &pkt);
>>>       > - } else if (pkt.eth->type == mg_htons(0x86dd)) {
>>>       > - pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
>>>       > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;
>>>      // Truncated
>>>       > - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
>>>       > - mkpay(&pkt, pkt.ip6 + 1);
>>>       > - rx_ip6(ifp, &pkt);
>>>       > - } else if (pkt.eth->type == mg_htons(0x800)) {
>>>       > - pkt.ip = (struct ip *) (pkt.eth + 1);
>>>       > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>>>      // Truncated
>>>       > - // Truncate frame to what IP header tells us
>>>       > - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) <
>>>      pkt.raw.len) {
>>>       > - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
>>>       > - }
>>>       > - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;
>>>      // Truncated
>>>       > - if ((pkt.ip->ver >> 4) != 4) return; // Not IP
>>>       > - mkpay(&pkt, pkt.ip + 1);
>>>       > - rx_ip(ifp, &pkt);
>>>       > - } else {
>>>       > - MG_DEBUG((" Unknown eth type %x", mg_htons(pkt.eth->type)));
>>>       > +static bool w5500_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
>>>       > + s->end(s->spi);
>>>       > + w5500_w1(s, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
>>>       > + w5500_w1(s, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
>>>       > + w5500_w1(s, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
>>>       > + // w5500_wn(s, W5500_CR, 9, s->mac, 6); // Set source MAC
>>>       > + w5500_w1(s, W5500_S0, 0x1e, 16); // Sock0 RX buf size
>>>       > + w5500_w1(s, W5500_S0, 0x1f, 16); // Sock0 TX buf size
>>>       > + w5500_w1(s, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
>>>       > + w5500_w1(s, W5500_S0, 1, 1); // Sock0 CR -> OPEN
>>>       > + return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
>>>       > +}
>>>       > +
>>>       > +static bool w5500_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *)
>>>      ifp->driver_data;
>>>       > + uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
>>>       > + return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
>>>       > +}
>>>       > +
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init,
>>>      w5500_tx, w5500_rx,
>>>       > + w5500_up};
>>>       > +#endif
>>>       > +
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/xmc.c"
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
>>>      MG_ENABLE_DRIVER_XMC
>>>       > +
>>>       > +struct ETH_GLOBAL_TypeDef {
>>>       > + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER,
>>>      HASH_TABLE_HIGH,
>>>       > + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL,
>>>      VLAN_TAG, VERSION,
>>>       > + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS,
>>>      RESERVED[2],
>>>       > + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH,
>>>      MAC_ADDRESS0_LOW,
>>>       > + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH,
>>>      MAC_ADDRESS2_LOW,
>>>       > + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
>>>       > + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT,
>>>      MMC_RECEIVE_INTERRUPT_MASK,
>>>       > + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
>>>       > + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
>>>       > + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5,
>>>      RX_STATISTICS_2[30],
>>>       > + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
>>>       > + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
>>>       > + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
>>>       > + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
>>>       > + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
>>>       > + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
>>>       > + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
>>>       > + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
>>>       > + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
>>>       > + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
>>>       > + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
>>>       > + CURRENT_HOST_RECEIVE_DESCRIPTOR,
>>>      CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
>>>       > + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
>>>       > +};
>>>       > +
>>>       > +#undef ETH0
>>>       > +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
>>>       > +
>>>       > +#define ETH_PKT_SIZE 1536 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 4 // Descriptor size (words)
>>>       > +
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > +static uint8_t s_txno; // Current TX descriptor
>>>       > +static uint8_t s_rxno; // Current RX descriptor
>>>       > +
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>>>       > +
>>>       > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
>>>       > + ((uint32_t)addr << 11) |
>>>       > + ((uint32_t)reg << 6) | 1;
>>>       > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
>>>       > + return (uint16_t)(ETH0->GMII_DATA & 0xffff);
>>>       > +}
>>>       > +
>>>       > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + ETH0->GMII_DATA = val;
>>>       > + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
>>>       > + ((uint32_t)addr << 11) |
>>>       > + ((uint32_t)reg << 6) | 3;
>>>       > + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
>>>       > +}
>>>       > +
>>>       > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data
>>>      *d) {
>>>       > + if (d->mdc_cr == -1) {
>>>       > + // assume ETH clock is 60MHz by default
>>>       > + // then according to 13.2.8.1, we need to set value 3
>>>       > + return 3;
>>>       > }
>>>       > +
>>>       > + return d->mdc_cr;
>>>       > }
>>>       >
>>>       > -static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
>>>       > - if (ifp == NULL || ifp->driver == NULL) return;
>>>       > - bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms,
>>>      1000, uptime_ms);
>>>       > - ifp->now = uptime_ms;
>>>       > +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_xmc_data *d =
>>>       > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
>>>       > + s_ifp = ifp;
>>>       >
>>>       > - // Handle physical interface up/down status
>>>       > - if (expired_1000ms && ifp->driver->up) {
>>>       > - bool up = ifp->driver->up(ifp);
>>>       > - bool current = ifp->state != MIP_STATE_DOWN;
>>>       > - if (up != current) {
>>>       > - ifp->state = up == false ? MIP_STATE_DOWN
>>>       > - : ifp->enable_dhcp_client ? MIP_STATE_UP
>>>       > - : MIP_STATE_READY;
>>>       > - if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
>>>       > - onstatechange(ifp);
>>>       > + // reset MAC
>>>       > + ETH0->BUS_MODE |= 1;
>>>       > + while (ETH0->BUS_MODE & 1) (void) 0;
>>>       > +
>>>       > + // set clock rate
>>>       > + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
>>>       > +
>>>       > + // init phy
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
>>>       > +
>>>       > + // configure MAC: DO, DM, FES, TC
>>>       > + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14)
>>>      | MG_BIT(24);
>>>       > +
>>>       > + // set the MAC address
>>>       > + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
>>>       > + ETH0->MAC_ADDRESS0_LOW =
>>>       > + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
>>>       > +
>>>       > + // Configure the receive filter
>>>       > + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
>>>       > + // Disable flow control
>>>       > + ETH0->FLOW_CONTROL = 0;
>>>       > + // Enable store and forward mode
>>>       > + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
>>>       > +
>>>       > + // Configure DMA bus mode (AAL, USP, RPBL, PBL)
>>>       > + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8);
>>>       > +
>>>       > + // init RX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
>>>       > + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
>>>       > + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
>>>       > + if (i == ETH_DESC_CNT - 1) {
>>>       > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
>>>       > + } else {
>>>       > + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
>>>       > }
>>>       > }
>>>       > - if (ifp->state == MIP_STATE_DOWN) return;
>>>       > - // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
>>>       > + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
>>>      &s_rxdesc[0][0];
>>>       >
>>>       > - if (ifp->ip == 0 && expired_1000ms) {
>>>       > - tx_dhcp_discover(ifp); // If IP not configured, send DHCP
>>>       > - } else if (ifp->enable_dhcp_client == false && expired_1000ms
>>>      && ifp->gw &&
>>>       > - arp_cache_find(ifp, ifp->gw) == NULL) {
>>>       > - arp_ask(ifp, ifp->gw); // If GW's MAC address in not in ARP cache
>>>       > + // init TX descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
>>>       > + s_txdesc[i][2] = (uint32_t) s_txbuf[i];
>>>       > + if (i == ETH_DESC_CNT - 1) {
>>>       > + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
>>>       > + } else {
>>>       > + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
>>>       > + }
>>>       > }
>>>       > + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t)
>>>      &s_txdesc[0][0];
>>>       >
>>>       > - // Read data from the network
>>>       > - size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len,
>>>      ifp);
>>>       > - mip_rx(ifp, (void *) ifp->rx.ptr, len);
>>>       > - qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
>>>       > + // Clear interrupts
>>>       > + ETH0->STATUS = 0xFFFFFFFF;
>>>       >
>>>       > - // Process timeouts
>>>       > - for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c =
>>>      c->next) {
>>>       > - if (c->is_udp || c->is_listening) continue;
>>>       > - if (c->is_connecting || c->is_resolving) continue;
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - if (uptime_ms > s->timer) {
>>>       > - if (s->ttype == MIP_TTYPE_ACK) {
>>>       > - MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
>>>       > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
>>>       > - mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
>>>       > - } else {
>>>       > - if (s->tmiss++ > 2) {
>>>       > - mg_error(c, "keepalive");
>>>       > - } else {
>>>       > - MG_DEBUG(("%lu keepalive", c->id));
>>>       > - tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
>>>       > - mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
>>>       > - }
>>>       > - }
>>>       > - settmout(c, MIP_TTYPE_KEEPALIVE);
>>>       > - }
>>>       > - }
>>>       > -#ifdef MIP_QPROFILE
>>>       > - qp_log();
>>>       > -#endif
>>>       > + // Disable MAC interrupts
>>>       > + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
>>>       > + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
>>>       > + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
>>>       > + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
>>>       > +
>>>       > + //Enable interrupts (NIE, RIE, TIE)
>>>       > + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
>>>       > +
>>>       > + // Enable MAC transmission and reception (TE, RE)
>>>       > + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
>>>       > + // Enable DMA transmission and reception (ST, SR)
>>>       > + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -// This function executes in interrupt context, thus it should
>>>      copy data
>>>       > -// somewhere fast. Note that newlib's malloc is not thread safe,
>>>      thus use
>>>       > -// our lock-free queue with preallocated buffer to copy data and
>>>      return asap
>>>       > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
>>>       > - if (q_write(&ifp->queue, buf, len)) {
>>>       > - qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
>>>       > +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // Frame is too big
>>>       > + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No free descriptors"));
>>>       > + len = 0; // All descriptors are busy, fail
>>>       > } else {
>>>       > - ifp->dropped++;
>>>       > - qp_mark(QP_FRAMEDROPPED, ifp->dropped);
>>>       > - MG_ERROR(("dropped %d", (int) len));
>>>       > + memcpy(s_txbuf[s_txno], buf, len);
>>>       > + s_txdesc[s_txno][1] = len;
>>>       > + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
>>>       > + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) |
>>>      MG_BIT(20);
>>>       > + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control
>>>      to DMA
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > }
>>>       > -}
>>>       >
>>>       > -size_t mip_qread(void *buf, struct mip_if *ifp) {
>>>       > - size_t len = q_read(&ifp->queue, buf);
>>>       > - qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
>>>       > + // Resume processing
>>>       > + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
>>>       > + ETH0->TRANSMIT_POLL_DEMAND = 0;
>>>       > return len;
>>>       > }
>>>       >
>>>       > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
>>>       > - return mip_qread((void *) ifp->rx.ptr, ifp);
>>>       > - (void) len, (void) buf;
>>>       > -}
>>>       > +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_xmc_data *d =
>>>       > + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ?
>>>      10 : 100,
>>>       > + full_duplex ? "full" : "half"));
>>>       > + }
>>>       > + return up;
>>>       > +}
>>>       > +
>>>       > +void ETH0_IRQHandler(void);
>>>       > +void ETH0_IRQHandler(void) {
>>>       > + uint32_t irq_status = ETH0->STATUS;
>>>       > +
>>>       > + // check if a frame was received
>>>       > + if (irq_status & MG_BIT(6)) {
>>>       > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
>>>       > + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>>>       > + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to
>>>      DMA
>>>       > + // Resume processing
>>>       > + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
>>>       > + ETH0->RECEIVE_POLL_DEMAND = 0;
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > + }
>>>       > + }
>>>       > + ETH0->STATUS = MG_BIT(6);
>>>       > + }
>>>       >
>>>       > -void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
>>>       > - if (ifp->driver->init && !ifp->driver->init(ifp)) {
>>>       > - MG_ERROR(("driver init failed"));
>>>       > - } else {
>>>       > - size_t maxpktsize = 1540;
>>>       > - ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len =
>>>      maxpktsize;
>>>       > - ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len =
>>>      maxpktsize;
>>>       > - if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1,
>>>      ifp->queue.len);
>>>       > - ifp->timer_1000ms = mg_millis();
>>>       > - arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
>>>       > - mgr->priv = ifp;
>>>       > - ifp->mgr = mgr;
>>>       > - mgr->extraconnsize = sizeof(struct connstate);
>>>       > - if (ifp->ip == 0) ifp->enable_dhcp_client = true;
>>>       > -#ifdef MIP_QPROFILE
>>>       > - qp_init();
>>>       > -#endif
>>>       > + // clear Successful transmission interrupt
>>>       > + if (irq_status & 1) {
>>>       > + ETH0->STATUS = 1;
>>>       > }
>>>       > -}
>>>       >
>>>       > -void mip_free(struct mip_if *ifp) {
>>>       > - free((char *) ifp->rx.ptr);
>>>       > - free((char *) ifp->tx.ptr);
>>>       > + // clear normal interrupt
>>>       > + if (irq_status & MG_BIT(16)) {
>>>       > + ETH0->STATUS = MG_BIT(16);
>>>       > + }
>>>       > }
>>>       >
>>>       > -int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d,
>>>      bool udp) {
>>>       > - (void) m, (void) fn, (void) d, (void) udp;
>>>       > - MG_ERROR(("Not implemented"));
>>>       > - return -1;
>>>       > -}
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_xmc = {
>>>       > + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
>>>       > + mg_tcpip_driver_xmc_up};
>>>       > +#endif
>>>       >
>>>       > -#if 0
>>>       > -static uint16_t mkeport(void) {
>>>       > - uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
>>>       > - mg_random(&a, sizeof(a));
>>>       > - c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
>>>       > - return c;
>>>       > -}
>>>       > +#ifdef MG_ENABLE_LINES
>>>       > +#line 1 "src/drivers/xmc7.c"
>>>       > #endif
>>>       >
>>>       > -void mg_connect_resolved(struct mg_connection *c) {
>>>       > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>>>       > - c->is_resolving = 0;
>>>       > - if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport =
>>>      MIP_ETHEMERAL_PORT;
>>>       > - c->loc.ip = ifp->ip;
>>>       > - c->loc.port = mg_htons(ifp->eport++);
>>>       > - MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip,
>>>      mg_ntohs(c->loc.port),
>>>       > - 4, &c->rem.ip, mg_ntohs(c->rem.port)));
>>>       > - mg_call(c, MG_EV_RESOLVE, NULL);
>>>       > - if (c->is_udp) {
>>>       > - mg_call(c, MG_EV_CONNECT, NULL);
>>>       > - } else {
>>>       > - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
>>>       > - tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn,
>>>      0, NULL, 0);
>>>       > - c->is_connecting = 1;
>>>       > - }
>>>       > -}
>>>       >
>>>       > -bool mg_open_listener(struct mg_connection *c, const char *url) {
>>>       > - c->loc.port = mg_htons(mg_url_port(url));
>>>       > - return true;
>>>       > -}
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
>>>      MG_ENABLE_DRIVER_XMC7
>>>       > +
>>>       > +struct ETH_Type {
>>>       > + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
>>>       > + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
>>>       > + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
>>>       > + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT,
>>>      PAUSE_TIME,
>>>       > + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU,
>>>      JUMBO_MAX_LENGTH,
>>>       > + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
>>>       > + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM,
>>>      HASH_TOP,
>>>       > + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
>>>       > + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
>>>       > + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
>>>       > + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
>>>       > + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
>>>       > + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
>>>       > + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
>>>       > + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
>>>       > + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
>>>       > + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
>>>       > + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
>>>       > + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
>>>       > + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
>>>       > + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
>>>       > + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65,
>>>      FRAMES_RXED_128,
>>>       > + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024,
>>>      FRAMES_RXED_1519,
>>>       > + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
>>>       > + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS,
>>>      RX_RESOURCE_ERRORS,
>>>       > + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
>>>       > + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC,
>>>      TSU_TIMER_MSB_SEC,
>>>       > + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC,
>>>      TSU_TIMER_SEC,
>>>       > + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
>>>       > + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
>>>       > + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
>>>       > + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
>>>       > + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS,
>>>      RESERVED6[8],
>>>       > + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3,
>>>      RESERVED7,
>>>       > + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
>>>       > + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4,
>>>      DESIGNCFG_DEBUG5,
>>>       > + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8,
>>>      DESIGNCFG_DEBUG9,
>>>       > + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
>>>       > + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
>>>       > + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS,
>>>      RESERVED11,
>>>       > + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
>>>       > + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
>>>       > + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
>>>       > + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3,
>>>      RESERVED16[3],
>>>       > + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A,
>>>      CBS_IDLESLOPE_Q_B,
>>>       > + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL,
>>>      UPPER_RX_Q_BASE_ADDR,
>>>       > + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
>>>       > + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
>>>       > +};
>>>       >
>>>       > -static void write_conn(struct mg_connection *c) {
>>>       > - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
>>>       > - : mg_io_send(c, c->send.buf, c->send.len);
>>>       > - if (len > 0) {
>>>       > - mg_iobuf_del(&c->send, 0, (size_t) len);
>>>       > - mg_call(c, MG_EV_WRITE, &len);
>>>       > - }
>>>       > +#define ETH0 ((struct ETH_Type *) 0x40490000)
>>>       > +
>>>       > +#define ETH_PKT_SIZE 1536 // Max frame size
>>>       > +#define ETH_DESC_CNT 4 // Descriptors count
>>>       > +#define ETH_DS 2 // Descriptor size (words)
>>>       > +
>>>       > +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
>>>       > +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
>>>       > +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
>>>       > +static uint8_t s_txno; // Current TX descriptor
>>>       > +static uint8_t s_rxno; // Current RX descriptor
>>>       > +
>>>       > +static struct mg_tcpip_if *s_ifp; // MIP interface
>>>       > +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
>>>       > +
>>>       > +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
>>>       > + // WRITE1, READ OPERATION, PHY, REG, WRITE10
>>>       > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf)
>>>      << 24) |
>>>       > + ((reg & 0x1f) << 18) | MG_BIT(17);
>>>       > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
>>>       > + return ETH0->PHY_MANAGEMENT & 0xffff;
>>>       > }
>>>       >
>>>       > -static void close_conn(struct mg_connection *c) {
>>>       > - struct connstate *s = (struct connstate *) (c + 1);
>>>       > - mg_iobuf_free(&s->raw); // For TLS connections, release raw data
>>>       > - if (c->is_udp == false && c->is_listening == false) { // For
>>>      TCP conns,
>>>       > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv; // send
>>>      TCP FIN
>>>       > - tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
>>>       > - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
>>>       > - }
>>>       > - mg_close_conn(c);
>>>       > +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t
>>>      val) {
>>>       > + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf)
>>>      << 24) |
>>>       > + ((reg & 0x1f) << 18) | MG_BIT(17) | val;
>>>       > + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
>>>       > }
>>>       >
>>>       > -static bool can_write(struct mg_connection *c) {
>>>       > - return c->is_connecting == 0 && c->is_resolving == 0 &&
>>>      c->send.len > 0 &&
>>>       > - c->is_tls_hs == 0;
>>>       > +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data
>>>      *d) {
>>>       > + // see ETH0 -> NETWORK_CONFIG register
>>>       > + (void) d;
>>>       > + return 3;
>>>       > }
>>>       >
>>>       > -void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
>>>       > - struct mg_connection *c, *tmp;
>>>       > - uint64_t now = mg_millis();
>>>       > - mip_poll((struct mip_if *) mgr->priv, now);
>>>       > - mg_timer_poll(&mgr->timers, now);
>>>       > - for (c = mgr->conns; c != NULL; c = tmp) {
>>>       > - tmp = c->next;
>>>       > - mg_call(c, MG_EV_POLL, &now);
>>>       > - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
>>>       > - c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
>>>       > - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
>>>       > - if (c->is_tls_hs) mg_tls_handshake(c);
>>>       > - if (can_write(c)) write_conn(c);
>>>       > - if (c->is_draining && c->send.len == 0) c->is_closing = 1;
>>>       > - if (c->is_closing) close_conn(c);
>>>       > +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_xmc7_data *d =
>>>       > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
>>>       > + s_ifp = ifp;
>>>       > +
>>>       > + // enable controller, set RGMII mode
>>>       > + ETH0->CTL = MG_BIT(31) | 2;
>>>       > +
>>>       > + uint32_t cr = get_clock_rate(d);
>>>       > + // set NSP change, ignore RX FCS, data bus width, clock rate
>>>       > + // frame length 1536, full duplex, speed
>>>       > + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
>>>       > + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
>>>       > + MG_BIT(1) | MG_BIT(0);
>>>       > +
>>>       > + // config DMA settings: Force TX burst, Discard on Error, set
>>>      RX buffer size
>>>       > + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
>>>       > + ETH0->DMA_CONFIG =
>>>       > + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8)
>>>      | 4;
>>>       > +
>>>       > + // initialize descriptors
>>>       > + for (int i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
>>>       > + if (i == ETH_DESC_CNT - 1) {
>>>       > + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor
>>>       > + }
>>>       > +
>>>       > + s_txdesc[i][0] = (uint32_t) s_txbuf[i];
>>>       > + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor
>>>       > + if (i == ETH_DESC_CNT - 1) {
>>>       > + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor
>>>       > + }
>>>       > }
>>>       > - (void) ms;
>>>       > + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
>>>       > + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
>>>       > +
>>>       > + // disable other queues
>>>       > + ETH0->TRANSMIT_Q2_PTR = 1;
>>>       > + ETH0->TRANSMIT_Q1_PTR = 1;
>>>       > + ETH0->RECEIVE_Q2_PTR = 1;
>>>       > + ETH0->RECEIVE_Q1_PTR = 1;
>>>       > +
>>>       > + // enable interrupts (TX and RX complete)
>>>       > + ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
>>>       > +
>>>       > + // set MAC address
>>>       > + ETH0->SPEC_ADD1_BOTTOM =
>>>       > + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 |
>>>      ifp->mac[0];
>>>       > + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
>>>       > +
>>>       > + // enable MDIO, TX, RX
>>>       > + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
>>>       > +
>>>       > + // start transmission
>>>       > + ETH0->NETWORK_CONTROL |= MG_BIT(9);
>>>       > +
>>>       > + // init phy
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
>>>       > +
>>>       > + (void) d;
>>>       > + return true;
>>>       > }
>>>       >
>>>       > -bool mg_send(struct mg_connection *c, const void *buf, size_t
>>>      len) {
>>>       > - struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
>>>       > - bool res = false;
>>>       > - if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
>>>       > - mg_error(c, "net down");
>>>       > - } else if (c->is_udp) {
>>>       > - tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf,
>>>      len);
>>>       > - res = true;
>>>       > +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
>>>       > + struct mg_tcpip_if *ifp) {
>>>       > + if (len > sizeof(s_txbuf[s_txno])) {
>>>       > + MG_ERROR(("Frame too big, %ld", (long) len));
>>>       > + len = 0; // Frame is too big
>>>       > + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
>>>       > + ifp->nerr++;
>>>       > + MG_ERROR(("No free descriptors"));
>>>       > + len = 0; // All descriptors are busy, fail
>>>       > } else {
>>>       > - res = mg_iobuf_add(&c->send, c->send.len, buf, len);
>>>       > - }
>>>       > - return res;
>>>       > -}
>>>       > + memcpy(s_txbuf[s_txno], buf, len);
>>>       > + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30)
>>>      : 0) |
>>>       > + MG_BIT(15) | len; // Last buffer and length
>>>       >
>>>       > -#ifdef MIP_QPROFILE
>>>       > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
>>>       > + if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
>>>       > + }
>>>       >
>>>       > -#pragma pack(push, 1)
>>>       > -struct qpentry {
>>>       > - uint32_t timestamp;
>>>       > - uint16_t type;
>>>       > - uint16_t len;
>>>       > -};
>>>       > -#pragma pack(pop)
>>>       > + MG_DSB();
>>>       > + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
>>>       > + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission
>>>       >
>>>       > -static struct queue qp;
>>>       > + return len;
>>>       > +}
>>>       >
>>>       > -// This is called from IRQ and main contexts; two producers,
>>>      single consumer
>>>       > -// TODO(scaprile): avoid concurrency issues (2 queues ?)
>>>       > -void qp_mark(unsigned int type, int len) {
>>>       > - static bool ovf = false;
>>>       > - static uint16_t irq_ctr = 0, drop_ctr = 0;
>>>       > - struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
>>>       > - .type = (uint16_t) type,
>>>       > - .len = (uint16_t) len};
>>>       > - if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
>>>       > - if (ovf) {
>>>       > - e.type = (uint16_t) QP_QUEUEOVF;
>>>       > - e.len = drop_ctr;
>>>       > +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
>>>       > + struct mg_tcpip_driver_xmc7_data *d =
>>>       > + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
>>>       > + uint8_t speed = MG_PHY_SPEED_10M;
>>>       > + bool up = false, full_duplex = false;
>>>       > + struct mg_phy phy = {eth_read_phy, eth_write_phy};
>>>       > + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
>>>       > + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state
>>>      just went up
>>>       > + if (speed == MG_PHY_SPEED_1000M) {
>>>       > + ETH0->NETWORK_CONFIG |= MG_BIT(10);
>>>       > + }
>>>       > + MG_DEBUG(("Link is %uM %s-duplex",
>>>       > + speed == MG_PHY_SPEED_10M ? 10 :
>>>       > + (speed == MG_PHY_SPEED_100M ? 100 : 1000),
>>>       > + full_duplex ? "full" : "half"));
>>>       > }
>>>       > - ovf = !q_write(&qp, &e, sizeof(e));
>>>       > + (void) d;
>>>       > + return up;
>>>       > }
>>>       >
>>>       > -void qp_log(void) {
>>>       > - struct qpentry e;
>>>       > - const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP",
>>>      "OVFL"};
>>>       > - for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
>>>       > - MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
>>>       > +void ETH_IRQHandler(void) {
>>>       > + uint32_t irq_status = ETH0->INT_STATUS;
>>>       > + if (irq_status & MG_BIT(1)) {
>>>       > + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
>>>       > + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
>>>       > + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
>>>       > + //MG_INFO(("Receive complete: %ld bytes", len));
>>>       > + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
>>>       > + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control
>>>      to DMA
>>>       > + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
>>>       > + }
>>>       > + }
>>>       > }
>>>       > -}
>>>       >
>>>       > -void qp_init(void) {
>>>       > - qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
>>>       > - qp.buf = calloc(1, qp.len); // THERE IS NO FREE
>>>       > + ETH0->INT_STATUS = irq_status;
>>>       > }
>>>       > -#endif // MIP_QPROFILE
>>>       >
>>>       > -#endif // MG_ENABLE_MIP
>>>       > +struct mg_tcpip_driver mg_tcpip_driver_xmc7 =
>>>      {mg_tcpip_driver_xmc7_init,
>>>       > + mg_tcpip_driver_xmc7_tx, NULL,
>>>       > + mg_tcpip_driver_xmc7_up};
>>>       > +#endif
>>>       > diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
>>>       > index 350aad9c..3a439a6e 100644
>>>       > --- a/mongoose/mongoose.h
>>>       > +++ b/mongoose/mongoose.h
>>>       > @@ -1,5 +1,5 @@
>>>       > // Copyright (c) 2004-2013 Sergey Lyubka
>>>       > -// Copyright (c) 2013-2022 Cesanta Software Limited
>>>       > +// Copyright (c) 2013-2024 Cesanta Software Limited
>>>       > // All rights reserved
>>>       > //
>>>       > // This software is dual-licensed: you can redistribute it and/or
>>>      modify
>>>       > @@ -15,58 +15,48 @@
>>>       > // Alternatively, you can license this software under a commercial
>>>       > // license, as set out in https://www.mongoose.ws/licensing/
>>>      <https://www.mongoose.ws/licensing/>
>>>       > //
>>>       > -// SPDX-License-Identifier: GPL-2.0-only
>>>       > +// SPDX-License-Identifier: GPL-2.0-only or commercial
>>>       >
>>>       > #ifndef MONGOOSE_H
>>>       > #define MONGOOSE_H
>>>       >
>>>       > -#define MG_VERSION "7.8"
>>>       > +#define MG_VERSION "7.14"
>>>       >
>>>       > #ifdef __cplusplus
>>>       > extern "C" {
>>>       > #endif
>>>       >
>>>       >
>>>       > -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h
>>>       > -#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
>>>       > -#define MG_ARCH_WIN32 2 // Windows
>>>       > -#define MG_ARCH_ESP32 3 // ESP32
>>>       > -#define MG_ARCH_ESP8266 4 // ESP8266
>>>       > -#define MG_ARCH_FREERTOS 5 // FreeRTOS
>>>       > -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
>>>       > -#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
>>>       > -#define MG_ARCH_NEWLIB 8 // Bare metal ARM
>>>       > -#define MG_ARCH_RTX 9 // Keil MDK RTX
>>>       > -#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
>>>       > -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
>>>       > +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h
>>>       > +#define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ...
>>>       > +#define MG_ARCH_WIN32 2 // Windows
>>>       > +#define MG_ARCH_ESP32 3 // ESP32
>>>       > +#define MG_ARCH_ESP8266 4 // ESP8266
>>>       > +#define MG_ARCH_FREERTOS 5 // FreeRTOS
>>>       > +#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
>>>       > +#define MG_ARCH_ZEPHYR 7 // Zephyr RTOS
>>>       > +#define MG_ARCH_NEWLIB 8 // Bare metal ARM
>>>       > +#define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX)
>>>       > +#define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS
>>>       > +#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040
>>>       > +#define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard
>>>       > +#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5,
>>>      FreeRTOS)
>>>       > +#define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS
>>>       >
>>>       > #if !defined(MG_ARCH)
>>>       > #if defined(__unix__) || defined(__APPLE__)
>>>       > #define MG_ARCH MG_ARCH_UNIX
>>>       > #elif defined(_WIN32)
>>>       > #define MG_ARCH MG_ARCH_WIN32
>>>       > -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
>>>       > -#define MG_ARCH MG_ARCH_ESP8266
>>>       > -#elif defined(__ZEPHYR__)
>>>       > -#define MG_ARCH MG_ARCH_ZEPHYR
>>>       > -#elif defined(ESP_PLATFORM)
>>>       > -#define MG_ARCH MG_ARCH_ESP32
>>>       > -#elif defined(FREERTOS_IP_H)
>>>       > -#define MG_ARCH MG_ARCH_FREERTOS
>>>       > -#define MG_ENABLE_FREERTOS_TCP 1
>>>       > -#elif defined(AZURE_RTOS_THREADX)
>>>       > -#define MG_ARCH MG_ARCH_AZURERTOS
>>>       > -#elif defined(PICO_TARGET_NAME)
>>>       > -#define MG_ARCH MG_ARCH_RP2040
>>>       > #endif
>>>       > #endif // !defined(MG_ARCH)
>>>       >
>>>       > #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
>>>       > -#include "mongoose_custom.h" // keep this include
>>>       > +#include "mongoose_config.h" // keep this include
>>>       > #endif
>>>       >
>>>       > #if !defined(MG_ARCH)
>>>       > -#error "MG_ARCH is not specified and we couldn't guess it. Set
>>>      -D MG_ARCH=..."
>>>       > +#error "MG_ARCH is not specified and we couldn't guess it.
>>>      Define MG_ARCH=... in your compiler"
>>>       > #endif
>>>       >
>>>       > // http://esr.ibiblio.org/?p=5095 <http://esr.ibiblio.org/?p=5095>
>>>       > @@ -130,7 +120,8 @@ extern "C" {
>>>       > #include <sys/types.h>
>>>       > #include <time.h>
>>>       >
>>>       > -#include <esp_timer.h>
>>>       > +#include <esp_ota_ops.h> // Use angle brackets to avoid
>>>       > +#include <esp_timer.h> // amalgamation ditching them
>>>       >
>>>       > #define MG_PATH_MAX 128
>>>       >
>>>       > @@ -166,15 +157,24 @@ extern "C" {
>>>       > #if MG_ARCH == MG_ARCH_FREERTOS
>>>       >
>>>       > #include <ctype.h>
>>>       > -// #include <errno.h> // Cannot include errno - might conflict
>>>      with lwip!
>>>       > +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
>>>       > +#include <errno.h>
>>>       > +#endif
>>>       > #include <stdarg.h>
>>>       > #include <stdbool.h>
>>>       > #include <stddef.h>
>>>       > #include <stdint.h>
>>>       > #include <stdio.h>
>>>       > -#include <stdlib.h> // rand(), strtol(), atoi()
>>>       > +#include <stdlib.h> // rand(), strtol(), atoi()
>>>       > #include <string.h>
>>>       > +#if defined(__ARMCC_VERSION)
>>>       > +#define mode_t size_t
>>>       > +#include <alloca.h>
>>>       > +#include <time.h>
>>>       > +#elif defined(__CCRH__)
>>>       > +#else
>>>       > #include <sys/stat.h>
>>>       > +#endif
>>>       >
>>>       > #include <FreeRTOS.h>
>>>       > #include <task.h>
>>>       > @@ -186,7 +186,7 @@ extern "C" {
>>>       > #define calloc(a, b) mg_calloc(a, b)
>>>       > #define free(a) vPortFree(a)
>>>       > #define malloc(a) pvPortMalloc(a)
>>>       > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
>>>       > +#define strdup(s) mg_mprintf("%s", s)
>>>       >
>>>       > // Re-route calloc/free to the FreeRTOS's functions, don't use
>>>      stdlib
>>>       > static inline void *mg_calloc(size_t cnt, size_t size) {
>>>       > @@ -240,9 +240,34 @@ static inline int mg_mkdir(const char *path,
>>>      mode_t mode) {
>>>       > #include <pico/stdlib.h>
>>>       > int mkdir(const char *, mode_t);
>>>       > #endif
>>>       > -
>>>       > -
>>>       > -#if MG_ARCH == MG_ARCH_RTX
>>>       > +
>>>       >
>>>       > +
>>>       >
>>>       > +#if MG_ARCH == MG_ARCH_RTTHREAD
>>>       >
>>>       > +
>>>       >
>>>       > +#include <rtthread.h>
>>>       >
>>>       > +#include <ctype.h>
>>>       >
>>>       > +#include <errno.h>
>>>       >
>>>       > +#include <fcntl.h>
>>>       >
>>>       > +#include <sys/socket.h>
>>>       >
>>>       > +#include <sys/select.h>
>>>       >
>>>       > +#include <stdarg.h>
>>>       >
>>>       > +#include <stdbool.h>
>>>       >
>>>       > +#include <stdint.h>
>>>       >
>>>       > +#include <stdio.h>
>>>       >
>>>       > +#include <stdlib.h>
>>>       >
>>>       > +#include <string.h>
>>>       >
>>>       > +#include <sys/types.h>
>>>       >
>>>       > +#include <time.h>
>>>       >
>>>       > +
>>>       >
>>>       > +#ifndef MG_IO_SIZE
>>>       >
>>>       > +#define MG_IO_SIZE 1460
>>>       >
>>>       > +#endif
>>>       >
>>>       > +
>>>       >
>>>       > +#endif // MG_ARCH == MG_ARCH_RTTHREAD
>>>       >
>>>       > +
>>>       > +
>>>       > +#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
>>>       > + MG_ARCH == MG_ARCH_CMSIS_RTOS2
>>>       >
>>>       > #include <ctype.h>
>>>       > #include <errno.h>
>>>       > @@ -252,11 +277,35 @@ int mkdir(const char *, mode_t);
>>>       > #include <stdint.h>
>>>       > #include <stdio.h>
>>>       > #include <stdlib.h>
>>>       > +#include <alloca.h>
>>>       > #include <string.h>
>>>       > #include <time.h>
>>>       > +#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
>>>       > +#include "cmsis_os.h" // keep this include
>>>       > +// https://developer.arm.com/documentation/ka003821/latest
>>>      <https://developer.arm.com/documentation/ka003821/latest>
>>>       > +extern uint32_t rt_time_get(void);
>>>       > +#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
>>>       > +#include "cmsis_os2.h" // keep this include
>>>       > +#endif
>>>       > +
>>>       > +#define strdup(s) mg_mprintf("%s", s)
>>>       > +
>>>       > +#if defined(__ARMCC_VERSION)
>>>       > +#define mode_t size_t
>>>       > +#define mkdir(a, b) mg_mkdir(a, b)
>>>       > +static inline int mg_mkdir(const char *path, mode_t mode) {
>>>       > + (void) path, (void) mode;
>>>       > + return -1;
>>>       > +}
>>>       > +#endif
>>>       >
>>>       > -#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
>>>      !MG_ENABLE_LWIP)
>>>       > +#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH ==
>>>      MG_ARCH_CMSIS_RTOS2) && \
>>>       > + !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) ||
>>>      !MG_ENABLE_LWIP) && \
>>>       > + (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
>>>       > #define MG_ENABLE_RL 1
>>>       > +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
>>>       > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
>>>       > +#endif
>>>       > #endif
>>>       >
>>>       > #endif
>>>       > @@ -403,7 +452,6 @@ typedef enum { false = 0, true = 1 } bool;
>>>       > #define MG_INVALID_SOCKET INVALID_SOCKET
>>>       > #define MG_SOCKET_TYPE SOCKET
>>>       > typedef unsigned long nfds_t;
>>>       > -#define MG_SOCKET_ERRNO WSAGetLastError()
>>>       > #if defined(_MSC_VER)
>>>       > #pragma comment(lib, "ws2_32.lib")
>>>       > #ifndef alloca
>>>       > @@ -411,9 +459,6 @@ typedef unsigned long nfds_t;
>>>       > #endif
>>>       > #endif
>>>       > #define poll(a, b, c) WSAPoll((a), (b), (c))
>>>       > -#ifndef SO_EXCLUSIVEADDRUSE
>>>       > -#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
>>>       > -#endif
>>>       > #define closesocket(x) closesocket(x)
>>>       >
>>>       > typedef int socklen_t;
>>>       > @@ -423,16 +468,24 @@ typedef int socklen_t;
>>>       > #define MG_PATH_MAX FILENAME_MAX
>>>       > #endif
>>>       >
>>>       > -#ifndef EINPROGRESS
>>>       > -#define EINPROGRESS WSAEINPROGRESS
>>>       > -#endif
>>>       > -#ifndef EWOULDBLOCK
>>>       > -#define EWOULDBLOCK WSAEWOULDBLOCK
>>>       > +#ifndef SO_EXCLUSIVEADDRUSE
>>>       > +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
>>>       > #endif
>>>       >
>>>       > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError()
>>>      : 0)
>>>       > +
>>>       > +#define MG_SOCK_PENDING(errcode) \
>>>       > + (((errcode) < 0) && \
>>>       > + (WSAGetLastError() == WSAEINTR || WSAGetLastError() ==
>>>      WSAEINPROGRESS || \
>>>       > + WSAGetLastError() == WSAEWOULDBLOCK))
>>>       > +
>>>       > +#define MG_SOCK_RESET(errcode) \
>>>       > + (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
>>>       > +
>>>       > #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
>>>       > -#define sleep(x) Sleep(x)
>>>       > +#define sleep(x) Sleep((x) *1000)
>>>       > #define mkdir(a, b) _mkdir(a)
>>>       > +#define timegm(x) _mkgmtime(x)
>>>       >
>>>       > #ifndef S_ISDIR
>>>       > #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
>>>       > @@ -442,6 +495,10 @@ typedef int socklen_t;
>>>       > #define MG_ENABLE_DIRLIST 1
>>>       > #endif
>>>       >
>>>       > +#ifndef SIGPIPE
>>>       > +#define SIGPIPE 0
>>>       > +#endif
>>>       > +
>>>       > #endif
>>>       >
>>>       >
>>>       > @@ -451,8 +508,9 @@ typedef int socklen_t;
>>>       >
>>>       > #include <ctype.h>
>>>       > #include <errno.h>
>>>       > -#include <fcntl.h>
>>>       > #include <zephyr/net/socket.h>
>>>       > +#include <zephyr/posix/fcntl.h>
>>>       > +#include <zephyr/posix/sys/select.h>
>>>       > #include <stdarg.h>
>>>       > #include <stdbool.h>
>>>       > #include <stdint.h>
>>>       > @@ -464,11 +522,18 @@ typedef int socklen_t;
>>>       >
>>>       > #define MG_PUTCHAR(x) printk("%c", x)
>>>       > #ifndef strdup
>>>       > -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
>>>       > +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf)
>>>       > #endif
>>>       > #define strerror(x) zsock_gai_strerror(x)
>>>       > +
>>>       > +#ifndef FD_CLOEXEC
>>>       > #define FD_CLOEXEC 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef F_SETFD
>>>       > #define F_SETFD 0
>>>       > +#endif
>>>       > +
>>>       > #define MG_ENABLE_SSI 0
>>>       >
>>>       > int rand(void);
>>>       > @@ -479,24 +544,12 @@ int sscanf(const char *, const char *, ...);
>>>       >
>>>       > #if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
>>>       >
>>>       > -#include <ctype.h>
>>>       > -#include <errno.h>
>>>       > #include <limits.h>
>>>       > -#include <stdarg.h>
>>>       > -#include <stdbool.h>
>>>       > -#include <stddef.h>
>>>       > -#include <stdio.h>
>>>       > -#include <stdlib.h>
>>>       > -#include <string.h>
>>>       > -#include <sys/stat.h>
>>>       > -#include <time.h>
>>>       > -
>>>       > -#include <FreeRTOS.h>
>>>       > #include <list.h>
>>>       > -#include <task.h>
>>>       >
>>>       > #include <FreeRTOS_IP.h>
>>>       > #include <FreeRTOS_Sockets.h>
>>>       > +#include <FreeRTOS_errno_TCP.h> // contents to be moved and file
>>>      removed, some day
>>>       >
>>>       > #define MG_SOCKET_TYPE Socket_t
>>>       > #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
>>>       > @@ -512,6 +565,20 @@ int sscanf(const char *, const char *, ...);
>>>       > #define SO_ERROR 0
>>>       > #define SOL_SOCKET 0
>>>       > #define SO_REUSEADDR 0
>>>       > +
>>>       > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
>>>       > +
>>>       > +#define MG_SOCK_PENDING(errcode) \
>>>       > + ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
>>>       > + (errcode) == -pdFREERTOS_ERRNO_EISCONN || \
>>>       > + (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
>>>       > + (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
>>>       > +
>>>       > +#define MG_SOCK_RESET(errcode) ((errcode) ==
>>>      -pdFREERTOS_ERRNO_ENOTCONN)
>>>       > +
>>>       > +// actually only if optional timeout is enabled
>>>       > +#define MG_SOCK_INTR(fd) (fd == NULL)
>>>       > +
>>>       > #define sockaddr_in freertos_sockaddr
>>>       > #define sockaddr freertos_sockaddr
>>>       > #define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
>>>       > @@ -543,8 +610,17 @@ static inline int
>>>      mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
>>>       >
>>>       >
>>>       > #if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
>>>       > -#if defined(__GNUC__)
>>>       > +
>>>       > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
>>>       > #include <sys/stat.h>
>>>       > +#endif
>>>       > +
>>>       > +struct timeval;
>>>       > +
>>>       > +#include <lwip/sockets.h>
>>>       > +
>>>       > +#if !LWIP_TIMEVAL_PRIVATE
>>>       > +#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang
>>>      sets both
>>>       > #include <sys/time.h>
>>>       > #else
>>>       > struct timeval {
>>>       > @@ -552,8 +628,7 @@ struct timeval {
>>>       > long tv_usec;
>>>       > };
>>>       > #endif
>>>       > -
>>>       > -#include <lwip/sockets.h>
>>>       > +#endif
>>>       >
>>>       > #if LWIP_SOCKET != 1
>>>       > // Sockets support disabled in LWIP by default
>>>       > @@ -565,16 +640,25 @@ struct timeval {
>>>       > #if defined(MG_ENABLE_RL) && MG_ENABLE_RL
>>>       > #include <rl_net.h>
>>>       >
>>>       > -#define MG_ENABLE_CUSTOM_MILLIS 1
>>>       > #define closesocket(x) closesocket(x)
>>>       > -#define mkdir(a, b) (-1)
>>>       > -#define EWOULDBLOCK BSD_EWOULDBLOCK
>>>       > -#define EAGAIN BSD_EWOULDBLOCK
>>>       > -#define EINPROGRESS BSD_EWOULDBLOCK
>>>       > -#define EINTR BSD_EWOULDBLOCK
>>>       > -#define ECONNRESET BSD_ECONNRESET
>>>       > -#define EPIPE BSD_ECONNRESET
>>>       > +
>>>       > #define TCP_NODELAY SO_KEEPALIVE
>>>       > +
>>>       > +#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
>>>       > +
>>>       > +#define MG_SOCK_PENDING(errcode) \
>>>       > + ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
>>>       > + (errcode) == BSD_EINPROGRESS)
>>>       > +
>>>       > +#define MG_SOCK_RESET(errcode) \
>>>       > + ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
>>>       > +
>>>       > +// In blocking mode, which is enabled by default, accept() waits
>>>      for a
>>>       > +// connection request. In non blocking mode, you must call accept()
>>>       > +// again if the error code BSD_EWOULDBLOCK is returned.
>>>       > +#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
>>>       > +
>>>       > +#define socklen_t int
>>>       > #endif
>>>       >
>>>       >
>>>       > @@ -582,8 +666,12 @@ struct timeval {
>>>       > #define MG_ENABLE_LOG 1
>>>       > #endif
>>>       >
>>>       > -#ifndef MG_ENABLE_MIP
>>>       > -#define MG_ENABLE_MIP 0 // Mongoose built-in network stack
>>>       > +#ifndef MG_ENABLE_CUSTOM_LOG
>>>       > +#define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_ENABLE_TCPIP
>>>       > +#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack
>>>       > #endif
>>>       >
>>>       > #ifndef MG_ENABLE_LWIP
>>>       > @@ -599,7 +687,7 @@ struct timeval {
>>>       > #endif
>>>       >
>>>       > #ifndef MG_ENABLE_SOCKET
>>>       > -#define MG_ENABLE_SOCKET !MG_ENABLE_MIP
>>>       > +#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
>>>       > #endif
>>>       >
>>>       > #ifndef MG_ENABLE_POLL
>>>       > @@ -614,18 +702,6 @@ struct timeval {
>>>       > #define MG_ENABLE_FATFS 0
>>>       > #endif
>>>       >
>>>       > -#ifndef MG_ENABLE_MBEDTLS
>>>       > -#define MG_ENABLE_MBEDTLS 0
>>>       > -#endif
>>>       > -
>>>       > -#ifndef MG_ENABLE_OPENSSL
>>>       > -#define MG_ENABLE_OPENSSL 0
>>>       > -#endif
>>>       > -
>>>       > -#ifndef MG_ENABLE_CUSTOM_TLS
>>>       > -#define MG_ENABLE_CUSTOM_TLS 0
>>>       > -#endif
>>>       > -
>>>       > #ifndef MG_ENABLE_SSI
>>>       > #define MG_ENABLE_SSI 0
>>>       > #endif
>>>       > @@ -634,6 +710,10 @@ struct timeval {
>>>       > #define MG_ENABLE_IPV6 0
>>>       > #endif
>>>       >
>>>       > +#ifndef MG_IPV6_V6ONLY
>>>       > +#define MG_IPV6_V6ONLY 0 // IPv6 socket binds only to V6, not V4
>>>      address
>>>       > +#endif
>>>       > +
>>>       > #ifndef MG_ENABLE_MD5
>>>       > #define MG_ENABLE_MD5 1
>>>       > #endif
>>>       > @@ -659,12 +739,16 @@ struct timeval {
>>>       > #define MG_ENABLE_PACKED_FS 0
>>>       > #endif
>>>       >
>>>       > +#ifndef MG_ENABLE_ASSERT
>>>       > +#define MG_ENABLE_ASSERT 0
>>>       > +#endif
>>>       > +
>>>       > #ifndef MG_IO_SIZE
>>>       > #define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer
>>>      growth
>>>       > #endif
>>>       >
>>>       > #ifndef MG_MAX_RECV_SIZE
>>>       > -#define MG_MAX_RECV_SIZE (3 * 1024 * 1024) // Maximum recv IO
>>>      buffer size
>>>       > +#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL) // Maximum recv
>>>      IO buffer size
>>>       > #endif
>>>       >
>>>       > #ifndef MG_DATA_SIZE
>>>       > @@ -688,18 +772,18 @@ struct timeval {
>>>       > #endif
>>>       >
>>>       > #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
>>>       > -#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
>>>       > +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
>>>       > #endif
>>>       >
>>>       > #ifndef MG_DIRSEP
>>>       > #define MG_DIRSEP '/'
>>>       > #endif
>>>       >
>>>       > -#ifndef MG_ENABLE_FILE
>>>       > +#ifndef MG_ENABLE_POSIX_FS
>>>       > #if defined(FOPEN_MAX)
>>>       > -#define MG_ENABLE_FILE 1
>>>       > +#define MG_ENABLE_POSIX_FS 1
>>>       > #else
>>>       > -#define MG_ENABLE_FILE 0
>>>       > +#define MG_ENABLE_POSIX_FS 0
>>>       > #endif
>>>       > #endif
>>>       >
>>>       > @@ -732,60 +816,112 @@ struct timeval {
>>>       > #define MG_EPOLL_MOD(c, wr)
>>>       > #endif
>>>       >
>>>       > +#ifndef MG_ENABLE_PROFILE
>>>       > +#define MG_ENABLE_PROFILE 0
>>>       > +#endif
>>>       >
>>>       > +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also
>>>      initialize
>>>       > +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver
>>>      for
>>>       > +#endif // Mongoose built-in network stack
>>>       >
>>>       > +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
>>>       > +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>>>      (DHCP)
>>>       > +#endif
>>>       >
>>>       > -struct mg_str {
>>>       > - const char *ptr; // Pointer to string data
>>>       > - size_t len; // String len
>>>       > -};
>>>       > +#ifndef MG_TCPIP_MASK
>>>       > +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>>>      (DHCP)
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_TCPIP_GW
>>>       > +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0
>>>      (DHCP)
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_SET_MAC_ADDRESS
>>>       > +#define MG_SET_MAC_ADDRESS(mac)
>>>       > +#endif
>>>       >
>>>       > -#define MG_NULL_STR \
>>>       > - { NULL, 0 }
>>>       > +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
>>>       > +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0
>>>       > +#endif
>>>       >
>>>       > -#define MG_C_STR(a) \
>>>       > - { (a), sizeof(a) - 1 }
>>>       > +
>>>       > +
>>>       > +
>>>       > +// Describes an arbitrary chunk of memory
>>>       > +struct mg_str {
>>>       > + char *buf; // String data
>>>       > + size_t len; // String length
>>>       > +};
>>>       >
>>>       > // Using macro to avoid shadowing C++ struct constructor, see #1298
>>>       > #define mg_str(s) mg_str_s(s)
>>>       >
>>>       > struct mg_str mg_str(const char *s);
>>>       > struct mg_str mg_str_n(const char *s, size_t n);
>>>       > -int mg_lower(const char *s);
>>>       > -int mg_ncasecmp(const char *s1, const char *s2, size_t len);
>>>       > int mg_casecmp(const char *s1, const char *s2);
>>>       > -int mg_vcmp(const struct mg_str *s1, const char *s2);
>>>       > -int mg_vcasecmp(const struct mg_str *str1, const char *str2);
>>>       > int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
>>>       > -struct mg_str mg_strstrip(struct mg_str s);
>>>       > -struct mg_str mg_strdup(const struct mg_str s);
>>>       > -const char *mg_strstr(const struct mg_str haystack, const struct
>>>      mg_str needle);
>>>       > +int mg_strcasecmp(const struct mg_str str1, const struct mg_str
>>>      str2);
>>>       > bool mg_match(struct mg_str str, struct mg_str pattern, struct
>>>      mg_str *caps);
>>>       > -bool mg_globmatch(const char *pattern, size_t plen, const char
>>>      *s, size_t n);
>>>       > -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct
>>>      mg_str *v);
>>>       > -bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str
>>>      *v, char delim);
>>>       > -char *mg_hex(const void *buf, size_t len, char *dst);
>>>       > -void mg_unhex(const char *buf, size_t len, unsigned char *to);
>>>       > -unsigned long mg_unhexn(const char *s, size_t len);
>>>       > -int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
>>>       > -int64_t mg_to64(struct mg_str str);
>>>       > -uint64_t mg_tou64(struct mg_str str);
>>>       > -char *mg_remove_double_dots(char *s);
>>>       > +bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str
>>>      *b, char delim);
>>>       > +
>>>       > +bool mg_str_to_num(struct mg_str, int base, void *val, size_t
>>>      val_len);
>>>       > +
>>>       >
>>>       >
>>>       >
>>>       > +// Single producer, single consumer non-blocking queue
>>>       > +
>>>       > +struct mg_queue {
>>>       > + char *buf;
>>>       > + size_t size;
>>>       > + volatile size_t tail;
>>>       > + volatile size_t head;
>>>       > +};
>>>       > +
>>>       > +void mg_queue_init(struct mg_queue *, char *, size_t); // Init
>>>      queue
>>>       > +size_t mg_queue_book(struct mg_queue *, char **buf, size_t); //
>>>      Reserve space
>>>       > +void mg_queue_add(struct mg_queue *, size_t); // Add new message
>>>       > +size_t mg_queue_next(struct mg_queue *, char **); // Get oldest
>>>      message
>>>       > +void mg_queue_del(struct mg_queue *, size_t); // Delete oldest
>>>      message
>>>       >
>>>       >
>>>       > -typedef void (*mg_pfn_t)(char, void *); // Custom putchar
>>>       > +
>>>       > +
>>>       > +typedef void (*mg_pfn_t)(char, void *); // Output function
>>>       > typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M
>>>      printer
>>>       > -void mg_pfn_iobuf(char ch, void *param); // iobuf printer
>>>       >
>>>       > size_t mg_vxprintf(void (*)(char, void *), void *, const char
>>>      *fmt, va_list *);
>>>       > size_t mg_xprintf(void (*fn)(char, void *), void *, const char
>>>      *fmt, ...);
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +// Convenience wrappers around mg_xprintf
>>>       > size_t mg_vsnprintf(char *buf, size_t len, const char *fmt,
>>>      va_list *ap);
>>>       > size_t mg_snprintf(char *, size_t, const char *fmt, ...);
>>>       > char *mg_vmprintf(const char *fmt, va_list *ap);
>>>       > char *mg_mprintf(const char *fmt, ...);
>>>       > +size_t mg_queue_vprintf(struct mg_queue *, const char *fmt,
>>>      va_list *);
>>>       > +size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
>>>       > +
>>>       > +// %M print helper functions
>>>       > +size_t mg_print_base64(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_esc(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_hex(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list
>>>      *ap);
>>>       > +size_t mg_print_ip_port(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_ip4(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_ip6(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +size_t mg_print_mac(void (*out)(char, void *), void *arg,
>>>      va_list *ap);
>>>       > +
>>>       > +// Various output functions
>>>       > +void mg_pfn_iobuf(char ch, void *param); // param: struct
>>>      mg_iobuf *
>>>       > +void mg_pfn_stdout(char c, void *param); // param: ignored
>>>       > +
>>>       > +// A helper macro for printing JSON: mg_snprintf(buf, len, "%m",
>>>      MG_ESC("hi"))
>>>       > +#define MG_ESC(str) mg_print_esc, 0, (str)
>>>       >
>>>       >
>>>       >
>>>       > @@ -793,16 +929,23 @@ char *mg_mprintf(const char *fmt, ...);
>>>       >
>>>       >
>>>       > enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG,
>>>      MG_LL_VERBOSE };
>>>       > +extern int mg_log_level; // Current log level, one of MG_LL_*
>>>       > +
>>>       > void mg_log(const char *fmt, ...);
>>>       > -bool mg_log_prefix(int ll, const char *file, int line, const
>>>      char *fname);
>>>       > -void mg_log_set(int log_level);
>>>       > +void mg_log_prefix(int ll, const char *file, int line, const
>>>      char *fname);
>>>       > +// bool mg_log2(int ll, const char *file, int line, const char
>>>      *fmt, ...);
>>>       > void mg_hexdump(const void *buf, size_t len);
>>>       > void mg_log_set_fn(mg_pfn_t fn, void *param);
>>>       >
>>>       > +#define mg_log_set(level_) mg_log_level = (level_)
>>>       > +
>>>       > #if MG_ENABLE_LOG
>>>       > -#define MG_LOG(level, args) \
>>>       > - do { \
>>>       > - if (mg_log_prefix((level), __FILE__, __LINE__, __func__))
>>>      mg_log args; \
>>>       > +#define MG_LOG(level, args) \
>>>       > + do { \
>>>       > + if ((level) <= mg_log_level) { \
>>>       > + mg_log_prefix((level), __FILE__, __LINE__, __func__); \
>>>       > + mg_log args; \
>>>       > + } \
>>>       > } while (0)
>>>       > #else
>>>       > #define MG_LOG(level, args) \
>>>       > @@ -854,7 +997,9 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2,
>>>      MG_FS_DIR = 4 };
>>>       > // stat(), write(), read() calls.
>>>       > struct mg_fs {
>>>       > int (*st)(const char *path, size_t *size, time_t *mtime); // stat
>>>      file
>>>       > - void (*ls)(const char *path, void (*fn)(const char *, void *),
>>>      void *);
>>>       > + void (*ls)(const char *path, void (*fn)(const char *, void *),
>>>       > + void *); // List directory entries: call fn(file_name, fn_data)
>>>       > + // for each directory entry
>>>       > void *(*op)(const char *path, int flags); // Open file
>>>       > void (*cl)(void *fd); // Close file
>>>       > size_t (*rd)(void *fd, void *buf, size_t len); // Read file
>>>       > @@ -877,28 +1022,84 @@ struct mg_fd {
>>>       >
>>>       > struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int
>>>      flags);
>>>       > void mg_fs_close(struct mg_fd *fd);
>>>       > -char *mg_file_read(struct mg_fs *fs, const char *path, size_t
>>>      *size);
>>>       > +bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf,
>>>      size_t len);
>>>       > +struct mg_str mg_file_read(struct mg_fs *fs, const char *path);
>>>       > bool mg_file_write(struct mg_fs *fs, const char *path, const void
>>>      *, size_t);
>>>       > bool mg_file_printf(struct mg_fs *fs, const char *path, const
>>>      char *fmt, ...);
>>>       >
>>>       > +// Packed API
>>>       > +const char *mg_unpack(const char *path, size_t *size, time_t
>>>      *mtime);
>>>       > +const char *mg_unlist(size_t no); // Get no'th packed filename
>>>       > +struct mg_str mg_unpacked(const char *path); // Packed file as
>>>      mg_str
>>>       > +
>>>       >
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > +
>>>       > +#if MG_ENABLE_ASSERT
>>>       > +#include <assert.h>
>>>       > +#elif !defined(assert)
>>>       > +#define assert(x)
>>>       > +#endif
>>>       > +
>>>       > +void mg_bzero(volatile unsigned char *buf, size_t len);
>>>       > void mg_random(void *buf, size_t len);
>>>       > char *mg_random_str(char *buf, size_t len);
>>>       > uint16_t mg_ntohs(uint16_t net);
>>>       > uint32_t mg_ntohl(uint32_t net);
>>>       > uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
>>>       > -uint64_t mg_millis(void);
>>>       > +uint64_t mg_millis(void); // Return milliseconds since boot
>>>       > +uint64_t mg_now(void); // Return milliseconds since Epoch
>>>       > +bool mg_path_is_sane(const struct mg_str path);
>>>       >
>>>       > #define mg_htons(x) mg_ntohs(x)
>>>       > #define mg_htonl(x) mg_ntohl(x)
>>>       >
>>>       > -#define MG_U32(a, b, c, d) \
>>>       > - (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
>>>       > - ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
>>>       > +#define MG_U32(a, b, c, d) \
>>>       > + (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) <<
>>>      16) | \
>>>       > + ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255))
>>>       > +
>>>       > +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
>>>       > +
>>>       > +// For printing IPv4 addresses: printf("%d.%d.%d.%d\n",
>>>      MG_IPADDR_PARTS(&ip))
>>>       > +#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
>>>       > +#define MG_IPADDR_PARTS(ADDR) \
>>>       > + MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
>>>       > +
>>>       > +#define MG_REG(x) ((volatile uint32_t *) (x))[0]
>>>       > +#define MG_BIT(x) (((uint32_t) 1U) << (x))
>>>       > +#define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) &
>>>      ~(CLRMASK)) | (SETMASK)
>>>       > +
>>>       > +#define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) /
>>>      (a)) * (a)))
>>>       > +#define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a)))
>>>       > +
>>>       > +#if defined(__GNUC__)
>>>       > +#define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory")
>>>       > +#define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory")
>>>       > +#elif defined(__CCRH__)
>>>       > +#define MG_RH850_DISABLE_IRQ() __DI()
>>>       > +#define MG_RH850_ENABLE_IRQ() __EI()
>>>       > +#else
>>>       > +#define MG_ARM_DISABLE_IRQ()
>>>       > +#define MG_ARM_ENABLE_IRQ()
>>>       > +#endif
>>>       > +
>>>       > +#if defined(__CC_ARM)
>>>       > +#define MG_DSB() __dsb(0xf)
>>>       > +#elif defined(__ARMCC_VERSION)
>>>       > +#define MG_DSB() __builtin_arm_dsb(0xf)
>>>       > +#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
>>>       > +#define MG_DSB() asm("DSB 0xf")
>>>       > +#elif defined(__ICCARM__)
>>>       > +#define MG_DSB() __iar_builtin_DSB()
>>>       > +#else
>>>       > +#define MG_DSB()
>>>       > +#endif
>>>       > +
>>>       > +struct mg_addr;
>>>       > +int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
>>>       >
>>>       > // Linked list management macros
>>>       > #define LIST_ADD_HEAD(type_, head_, elem_) \
>>>       > @@ -946,10 +1147,11 @@ void mg_iobuf_free(struct mg_iobuf *);
>>>       > size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *,
>>>      size_t);
>>>       > size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
>>>       >
>>>       > -int mg_base64_update(unsigned char p, char *to, int len);
>>>       > -int mg_base64_final(char *to, int len);
>>>       > -int mg_base64_encode(const unsigned char *p, int n, char *to);
>>>       > -int mg_base64_decode(const char *src, int n, char *dst);
>>>       > +
>>>       > +size_t mg_base64_update(unsigned char input_byte, char *buf,
>>>      size_t len);
>>>       > +size_t mg_base64_final(char *buf, size_t len);
>>>       > +size_t mg_base64_encode(const unsigned char *p, size_t n, char
>>>      *buf, size_t);
>>>       > +size_t mg_base64_decode(const char *src, size_t n, char *dst,
>>>      size_t);
>>>       >
>>>       >
>>>       >
>>>       > @@ -976,35 +1178,793 @@ typedef struct {
>>>       > void mg_sha1_init(mg_sha1_ctx *);
>>>       > void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data,
>>>      size_t len);
>>>       > void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
>>>       > +// https://github.com/B-Con/crypto-algorithms
>>>      <https://github.com/B-Con/crypto-algorithms>
>>>       > +// Author: Brad Conte (brad AT bradconte.com
>>>      <http://bradconte.com>)
>>>       > +// Disclaimer: This code is presented "as is" without any
>>>      guarantees.
>>>       > +// Details: Defines the API for the corresponding SHA1
>>>      implementation.
>>>       > +// Copyright: public domain
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +typedef struct {
>>>       > + uint32_t state[8];
>>>       > + uint64_t bits;
>>>       > + uint32_t len;
>>>       > + unsigned char buffer[64];
>>>       > +} mg_sha256_ctx;
>>>       > +
>>>       > +void mg_sha256_init(mg_sha256_ctx *);
>>>       > +void mg_sha256_update(mg_sha256_ctx *, const unsigned char
>>>      *data, size_t len);
>>>       > +void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *);
>>>       > +void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz,
>>>      uint8_t *data,
>>>       > + size_t datasz);
>>>       > +#ifndef TLS_X15519_H
>>>       > +#define TLS_X15519_H
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define X25519_BYTES 32
>>>       > +extern const uint8_t X25519_BASE_POINT[X25519_BYTES];
>>>       > +
>>>       > +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t
>>>      scalar[X25519_BYTES],
>>>       > + const uint8_t x1[X25519_BYTES], int clamp);
>>>       > +
>>>       > +
>>>       > +#endif /* TLS_X15519_H */
>>>       >
>>>      +/******************************************************************************
>>>       > + *
>>>       > + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR
>>>      THE GOOD OF ALL
>>>       > + *
>>>       > + * This is a simple and straightforward implementation of
>>>      AES-GCM authenticated
>>>       > + * encryption. The focus of this work was correctness &
>>>      accuracy. It is written
>>>       > + * in straight 'C' without any particular focus upon
>>>      optimization or speed. It
>>>       > + * should be endian (memory byte order) neutral since the few
>>>      places that care
>>>       > + * are handled explicitly.
>>>       > + *
>>>       > + * This implementation of AES-GCM was created by Steven M.
>>>      Gibson of GRC.com.
>>>       > + *
>>>       > + * It is intended for general purpose use, but was written in
>>>      support of GRC's
>>>       > + * reference implementation of the SQRL (Secure Quick Reliable
>>>      Login) client.
>>>       > + *
>>>       > + * See:
>>>      http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
>>>      <http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf>
>>>       > + *
>>>      http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
>>>      <http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/> \
>>>       > + * gcm/gcm-revised-spec.pdf
>>>       > + *
>>>       > + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY
>>>      WARRANTY MADE
>>>       > + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT
>>>      YOUR OWN RISK.
>>>       > + *
>>>       > +
>>>      *******************************************************************************/
>>>       > +#ifndef TLS_AES128_H
>>>       > +#define TLS_AES128_H
>>>       > +
>>>       > +typedef unsigned char uchar; // add some convienent shorter types
>>>       > +typedef unsigned int uint;
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * AES_CONTEXT : cipher context / holds inter-call data
>>>       > +
>>>      ******************************************************************************/
>>>       > +typedef struct {
>>>       > + int mode; // 1 for Encryption, 0 for Decryption
>>>       > + int rounds; // keysize-based rounds count
>>>       > + uint32_t *rk; // pointer to current round key
>>>       > + uint32_t buf[68]; // key expansion buffer
>>>       > +} aes_context;
>>>       > +
>>>       > +
>>>       > +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure
>>>       > +
>>>       >
>>>      +/******************************************************************************
>>>       > + * GCM_CONTEXT : MUST be called once before ANY use of this library
>>>       > +
>>>      ******************************************************************************/
>>>       > +int mg_gcm_initialize(void);
>>>       > +
>>>       > +//
>>>       > +// aes-gcm.h
>>>       > +// MKo
>>>       > +//
>>>       > +// Created by Markus Kosmal on 20/11/14.
>>>       > +//
>>>       > +//
>>>       > +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned
>>>      char *input,
>>>       > + size_t input_length, const unsigned char *key,
>>>       > + const size_t key_len, const unsigned char *iv,
>>>       > + const size_t iv_len, unsigned char *aead,
>>>       > + size_t aead_len, unsigned char *tag,
>>>       > + const size_t tag_len);
>>>       > +
>>>       > +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned
>>>      char *input,
>>>       > + size_t input_length, const unsigned char *key,
>>>       > + const size_t key_len, const unsigned char *iv,
>>>       > + const size_t iv_len);
>>>       > +
>>>       > +#endif /* TLS_AES128_H */
>>>       > +
>>>       > +// End of aes128 PD
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define MG_UECC_SUPPORTS_secp256r1 1
>>>       > +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD
>>>      2-clause license. */
>>>       > +
>>>       > +#ifndef _UECC_H_
>>>       > +#define _UECC_H_
>>>       > +
>>>       > +/* Platform selection options.
>>>       > +If MG_UECC_PLATFORM is not defined, the code will try to guess
>>>      it based on
>>>       > +compiler macros. Possible values for MG_UECC_PLATFORM are
>>>      defined below: */
>>>       > +#define mg_uecc_arch_other 0
>>>       > +#define mg_uecc_x86 1
>>>       > +#define mg_uecc_x86_64 2
>>>       > +#define mg_uecc_arm 3
>>>       > +#define mg_uecc_arm_thumb 4
>>>       > +#define mg_uecc_arm_thumb2 5
>>>       > +#define mg_uecc_arm64 6
>>>       > +#define mg_uecc_avr 7
>>>       > +
>>>       > +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate
>>>      for your platform
>>>       > +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly
>>>      defined then it will
>>>       > +be automatically set based on your platform. */
>>>       > +
>>>       > +/* Optimization level; trade speed for code size.
>>>       > + Larger values produce code that is faster but larger.
>>>       > + Currently supported values are 0 - 4; 0 is unusably slow for most
>>>       > + applications. Optimization level 4 currently only has an effect
>>>      ARM platforms
>>>       > + where more than one curve is enabled. */
>>>       > +#ifndef MG_UECC_OPTIMIZATION_LEVEL
>>>       > +#define MG_UECC_OPTIMIZATION_LEVEL 2
>>>       > +#endif
>>>       > +
>>>       > +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this
>>>      will cause a
>>>       > +specific function to be used for (scalar) squaring instead of
>>>      the generic
>>>       > +multiplication function. This can make things faster somewhat
>>>      faster, but
>>>       > +increases the code size. */
>>>       > +#ifndef MG_UECC_SQUARE_FUNC
>>>       > +#define MG_UECC_SQUARE_FUNC 0
>>>       > +#endif
>>>       > +
>>>       > +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as
>>>      nonzero), this will
>>>       > +switch to native little-endian format for *all* arrays passed in
>>>      and out of the
>>>       > +public API. This includes public and private keys, shared
>>>      secrets, signatures
>>>       > +and message hashes. Using this switch reduces the amount of call
>>>      stack memory
>>>       > +used by uECC, since less intermediate translations are required.
>>>      Note that this
>>>       > +will *only* work on native little-endian processors and it will
>>>      treat the
>>>       > +uint8_t arrays passed into the public API as word arrays,
>>>      therefore requiring
>>>       > +the provided byte arrays to be word aligned on architectures
>>>      that do not support
>>>       > +unaligned accesses. IMPORTANT: Keys and signatures generated with
>>>       > +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys
>>>      and signatures
>>>       > +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties
>>>      must use the same
>>>       > +endianness. */
>>>       > +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
>>>       > +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0
>>>       > +#endif
>>>       > +
>>>       > +/* Curve support selection. Set to 0 to remove that curve. */
>>>       > +#ifndef MG_UECC_SUPPORTS_secp160r1
>>>       > +#define MG_UECC_SUPPORTS_secp160r1 0
>>>       > +#endif
>>>       > +#ifndef MG_UECC_SUPPORTS_secp192r1
>>>       > +#define MG_UECC_SUPPORTS_secp192r1 0
>>>       > +#endif
>>>       > +#ifndef MG_UECC_SUPPORTS_secp224r1
>>>       > +#define MG_UECC_SUPPORTS_secp224r1 0
>>>       > +#endif
>>>       > +#ifndef MG_UECC_SUPPORTS_secp256r1
>>>       > +#define MG_UECC_SUPPORTS_secp256r1 1
>>>       > +#endif
>>>       > +#ifndef MG_UECC_SUPPORTS_secp256k1
>>>       > +#define MG_UECC_SUPPORTS_secp256k1 0
>>>       > +#endif
>>>       > +
>>>       > +/* Specifies whether compressed point format is supported.
>>>       > + Set to 0 to disable point compression/decompression functions. */
>>>       > +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1
>>>       > +#endif
>>>       > +
>>>       > +struct MG_UECC_Curve_t;
>>>       > +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve;
>>>       > +
>>>       > +#ifdef __cplusplus
>>>       > +extern "C" {
>>>       > +#endif
>>>       > +
>>>       > +#if MG_UECC_SUPPORTS_secp160r1
>>>       > +MG_UECC_Curve mg_uecc_secp160r1(void);
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp192r1
>>>       > +MG_UECC_Curve mg_uecc_secp192r1(void);
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp224r1
>>>       > +MG_UECC_Curve mg_uecc_secp224r1(void);
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp256r1
>>>       > +MG_UECC_Curve mg_uecc_secp256r1(void);
>>>       > +#endif
>>>       > +#if MG_UECC_SUPPORTS_secp256k1
>>>       > +MG_UECC_Curve mg_uecc_secp256k1(void);
>>>       > +#endif
>>>       > +
>>>       > +/* MG_UECC_RNG_Function type
>>>       > +The RNG function should fill 'size' random bytes into 'dest'. It
>>>      should return 1
>>>       > +if 'dest' was filled with random data, or 0 if the random data
>>>      could not be
>>>       > +generated. The filled-in values should be either truly random,
>>>      or from a
>>>       > +cryptographically-secure PRNG.
>>>       > +
>>>       > +A correctly functioning RNG function must be set (using
>>>      mg_uecc_set_rng())
>>>       > +before calling mg_uecc_make_key() or mg_uecc_sign().
>>>       > +
>>>       > +Setting a correctly functioning RNG function improves the
>>>      resistance to
>>>       > +side-channel attacks for mg_uecc_shared_secret() and
>>>       > +mg_uecc_sign_deterministic().
>>>       > +
>>>       > +A correct RNG function is set by default when building for
>>>      Windows, Linux, or OS
>>>       > +X. If you are building on another POSIX-compliant system that
>>>      supports
>>>       > +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use
>>>      the predefined
>>>       > +RNG. For embedded platforms there is no predefined RNG function;
>>>      you must
>>>       > +provide your own.
>>>       > +*/
>>>       > +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size);
>>>       > +
>>>       > +/* mg_uecc_set_rng() function.
>>>       > +Set the function that will be used to generate random bytes. The
>>>      RNG function
>>>       > +should return 1 if the random data was generated, or 0 if the
>>>      random data could
>>>       > +not be generated.
>>>       > +
>>>       > +On platforms where there is no predefined RNG function (eg
>>>      embedded platforms),
>>>       > +this must be called before mg_uecc_make_key() or mg_uecc_sign()
>>>      are used.
>>>       > +
>>>       > +Inputs:
>>>       > + rng_function - The function that will be used to generate
>>>      random bytes.
>>>       > +*/
>>>       > +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function);
>>>       > +
>>>       > +/* mg_uecc_get_rng() function.
>>>       > +
>>>       > +Returns the function that will be used to generate random bytes.
>>>       > +*/
>>>       > +MG_UECC_RNG_Function mg_uecc_get_rng(void);
>>>       > +
>>>       > +/* mg_uecc_curve_private_key_size() function.
>>>       > +
>>>       > +Returns the size of a private key for the curve in bytes.
>>>       > +*/
>>>       > +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_curve_public_key_size() function.
>>>       > +
>>>       > +Returns the size of a public key for the curve in bytes.
>>>       > +*/
>>>       > +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_make_key() function.
>>>       > +Create a public/private key pair.
>>>       > +
>>>       > +Outputs:
>>>       > + public_key - Will be filled in with the public key. Must be at
>>>      least 2 *
>>>       > +the curve size (in bytes) long. For example, if the curve is
>>>      secp256r1,
>>>       > +public_key must be 64 bytes long. private_key - Will be filled
>>>      in with the
>>>       > +private key. Must be as long as the curve order; this is
>>>      typically the same as
>>>       > +the curve size, except for secp160r1. For example, if the curve
>>>      is secp256r1,
>>>       > +private_key must be 32 bytes long.
>>>       > +
>>>       > + For secp160r1, private_key must be 21 bytes long! Note that
>>>       > +the first byte will almost always be 0 (there is about a 1 in
>>>      2^80 chance of it
>>>       > +being non-zero).
>>>       > +
>>>       > +Returns 1 if the key pair was generated successfully, 0 if an
>>>      error occurred.
>>>       > +*/
>>>       > +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_shared_secret() function.
>>>       > +Compute a shared secret given your secret key and someone else's
>>>      public key. If
>>>       > +the public key is not from a trusted source and has not been
>>>      previously
>>>       > +verified, you should verify it first using
>>>      mg_uecc_valid_public_key(). Note: It
>>>       > +is recommended that you hash the result of
>>>      mg_uecc_shared_secret() before using
>>>       > +it for symmetric encryption or HMAC.
>>>       > +
>>>       > +Inputs:
>>>       > + public_key - The public key of the remote party.
>>>       > + private_key - Your private key.
>>>       > +
>>>       > +Outputs:
>>>       > + secret - Will be filled in with the shared secret value. Must
>>>      be the same
>>>       > +size as the curve size; for example, if the curve is secp256r1,
>>>      secret must be
>>>       > +32 bytes long.
>>>       > +
>>>       > +Returns 1 if the shared secret was generated successfully, 0 if
>>>      an error
>>>       > +occurred.
>>>       > +*/
>>>       > +int mg_uecc_shared_secret(const uint8_t *public_key, const
>>>      uint8_t *private_key,
>>>       > + uint8_t *secret, MG_UECC_Curve curve);
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +/* mg_uecc_compress() function.
>>>       > +Compress a public key.
>>>       > +
>>>       > +Inputs:
>>>       > + public_key - The public key to compress.
>>>       > +
>>>       > +Outputs:
>>>       > + compressed - Will be filled in with the compressed public key.
>>>      Must be at
>>>       > +least (curve size + 1) bytes long; for example, if the curve is
>>>      secp256r1,
>>>       > + compressed must be 33 bytes long.
>>>       > +*/
>>>       > +void mg_uecc_compress(const uint8_t *public_key, uint8_t
>>>      *compressed,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_decompress() function.
>>>       > +Decompress a compressed public key.
>>>       > +
>>>       > +Inputs:
>>>       > + compressed - The compressed public key.
>>>       > +
>>>       > +Outputs:
>>>       > + public_key - Will be filled in with the decompressed public key.
>>>       > +*/
>>>       > +void mg_uecc_decompress(const uint8_t *compressed, uint8_t
>>>      *public_key,
>>>       > + MG_UECC_Curve curve);
>>>       > +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
>>>       > +
>>>       > +/* mg_uecc_valid_public_key() function.
>>>       > +Check to see if a public key is valid.
>>>       > +
>>>       > +Note that you are not required to check for a valid public key
>>>      before using any
>>>       > +other uECC functions. However, you may wish to avoid spending
>>>      CPU time computing
>>>       > +a shared secret or verifying a signature using an invalid public
>>>      key.
>>>       > +
>>>       > +Inputs:
>>>       > + public_key - The public key to check.
>>>       > +
>>>       > +Returns 1 if the public key is valid, 0 if it is invalid.
>>>       > +*/
>>>       > +int mg_uecc_valid_public_key(const uint8_t *public_key,
>>>      MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_compute_public_key() function.
>>>       > +Compute the corresponding public key for a private key.
>>>       > +
>>>       > +Inputs:
>>>       > + private_key - The private key to compute the public key for
>>>       > +
>>>       > +Outputs:
>>>       > + public_key - Will be filled in with the corresponding public key
>>>       > +
>>>       > +Returns 1 if the key was computed successfully, 0 if an error
>>>      occurred.
>>>       > +*/
>>>       > +int mg_uecc_compute_public_key(const uint8_t *private_key,
>>>      uint8_t *public_key,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_sign() function.
>>>       > +Generate an ECDSA signature for a given hash value.
>>>       > +
>>>       > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
>>>      recommended) and
>>>       > +pass it in to this function along with your private key.
>>>       > +
>>>       > +Inputs:
>>>       > + private_key - Your private key.
>>>       > + message_hash - The hash of the message to sign.
>>>       > + hash_size - The size of message_hash in bytes.
>>>       > +
>>>       > +Outputs:
>>>       > + signature - Will be filled in with the signature value. Must be
>>>      at least 2 *
>>>       > +curve size long. For example, if the curve is secp256r1,
>>>      signature must be 64
>>>       > +bytes long.
>>>       > +
>>>       > +Returns 1 if the signature generated successfully, 0 if an error
>>>      occurred.
>>>       > +*/
>>>       > +int mg_uecc_sign(const uint8_t *private_key, const uint8_t
>>>      *message_hash,
>>>       > + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve);
>>>       > +
>>>       > +/* MG_UECC_HashContext structure.
>>>       > +This is used to pass in an arbitrary hash function to
>>>       > +mg_uecc_sign_deterministic(). The structure will be used for
>>>      multiple hash
>>>       > +computations; each time a new hash is computed, init_hash() will
>>>      be called,
>>>       > +followed by one or more calls to update_hash(), and finally a
>>>      call to
>>>       > +finish_hash() to produce the resulting hash.
>>>       > +
>>>       > +The intention is that you will create a structure that includes
>>>       > +MG_UECC_HashContext followed by any hash-specific data. For
>>>      example:
>>>       > +
>>>       > +typedef struct SHA256_HashContext {
>>>       > + MG_UECC_HashContext uECC;
>>>       > + SHA256_CTX ctx;
>>>       > +} SHA256_HashContext;
>>>       > +
>>>       > +void init_SHA256(MG_UECC_HashContext *base) {
>>>       > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>>>       > + SHA256_Init(&context->ctx);
>>>       > +}
>>>       > +
>>>       > +void update_SHA256(MG_UECC_HashContext *base,
>>>       > + const uint8_t *message,
>>>       > + unsigned message_size) {
>>>       > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>>>       > + SHA256_Update(&context->ctx, message, message_size);
>>>       > +}
>>>       > +
>>>       > +void finish_SHA256(MG_UECC_HashContext *base, uint8_t
>>>      *hash_result) {
>>>       > + SHA256_HashContext *context = (SHA256_HashContext *)base;
>>>       > + SHA256_Final(hash_result, &context->ctx);
>>>       > +}
>>>       > +
>>>       > +... when signing ...
>>>       > +{
>>>       > + uint8_t tmp[32 + 32 + 64];
>>>       > + SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256,
>>>      &finish_SHA256, 64,
>>>       > +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash,
>>>      &ctx.uECC, signature);
>>>       > +}
>>>       > +*/
>>>       > +typedef struct MG_UECC_HashContext {
>>>       > + void (*init_hash)(const struct MG_UECC_HashContext *context);
>>>       > + void (*update_hash)(const struct MG_UECC_HashContext *context,
>>>       > + const uint8_t *message, unsigned message_size);
>>>       > + void (*finish_hash)(const struct MG_UECC_HashContext *context,
>>>       > + uint8_t *hash_result);
>>>       > + unsigned
>>>       > + block_size; /* Hash function block size in bytes, eg 64 for
>>>      SHA-256. */
>>>       > + unsigned
>>>       > + result_size; /* Hash function result size in bytes, eg 32 for
>>>      SHA-256. */
>>>       > + uint8_t *tmp; /* Must point to a buffer of at least (2 *
>>>      result_size +
>>>       > + block_size) bytes. */
>>>       > +} MG_UECC_HashContext;
>>>       > +
>>>       > +/* mg_uecc_sign_deterministic() function.
>>>       > +Generate an ECDSA signature for a given hash value, using a
>>>      deterministic
>>>       > +algorithm (see RFC 6979). You do not need to set the RNG using
>>>      mg_uecc_set_rng()
>>>       > +before calling this function; however, if the RNG is defined it
>>>      will improve
>>>       > +resistance to side-channel attacks.
>>>       > +
>>>       > +Usage: Compute a hash of the data you wish to sign (SHA-2 is
>>>      recommended) and
>>>       > +pass it to this function along with your private key and a hash
>>>      context. Note
>>>       > +that the message_hash does not need to be computed with the same
>>>      hash function
>>>       > +used by hash_context.
>>>       > +
>>>       > +Inputs:
>>>       > + private_key - Your private key.
>>>       > + message_hash - The hash of the message to sign.
>>>       > + hash_size - The size of message_hash in bytes.
>>>       > + hash_context - A hash context to use.
>>>       > +
>>>       > +Outputs:
>>>       > + signature - Will be filled in with the signature value.
>>>       > +
>>>       > +Returns 1 if the signature generated successfully, 0 if an error
>>>      occurred.
>>>       > +*/
>>>       > +int mg_uecc_sign_deterministic(const uint8_t *private_key,
>>>       > + const uint8_t *message_hash, unsigned hash_size,
>>>       > + const MG_UECC_HashContext *hash_context,
>>>       > + uint8_t *signature, MG_UECC_Curve curve);
>>>       > +
>>>       > +/* mg_uecc_verify() function.
>>>       > +Verify an ECDSA signature.
>>>       > +
>>>       > +Usage: Compute the hash of the signed data using the same hash
>>>      as the signer and
>>>       > +pass it to this function along with the signer's public key and
>>>      the signature
>>>       > +values (r and s).
>>>       > +
>>>       > +Inputs:
>>>       > + public_key - The signer's public key.
>>>       > + message_hash - The hash of the signed data.
>>>       > + hash_size - The size of message_hash in bytes.
>>>       > + signature - The signature value.
>>>       > +
>>>       > +Returns 1 if the signature is valid, 0 if it is invalid.
>>>       > +*/
>>>       > +int mg_uecc_verify(const uint8_t *public_key, const uint8_t
>>>      *message_hash,
>>>       > + unsigned hash_size, const uint8_t *signature,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +#ifdef __cplusplus
>>>       > +} /* end of extern "C" */
>>>       > +#endif
>>>       > +
>>>       > +#endif /* _UECC_H_ */
>>>       > +
>>>       > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>>>      2-clause license. */
>>>       > +
>>>       > +#ifndef _UECC_VLI_H_
>>>       > +#define _UECC_VLI_H_
>>>       > +
>>>       > +//
>>>       > +//
>>>       > +
>>>       > +/* Functions for raw large-integer manipulation. These are only
>>>      available
>>>       > + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */
>>>       > +#ifndef MG_UECC_ENABLE_VLI_API
>>>       > +#define MG_UECC_ENABLE_VLI_API 0
>>>       > +#endif
>>>       > +
>>>       > +#ifdef __cplusplus
>>>       > +extern "C" {
>>>       > +#endif
>>>       > +
>>>       > +#if MG_UECC_ENABLE_VLI_API
>>>       > +
>>>       > +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words);
>>>       > +
>>>       > +/* Constant-time comparison to zero - secure way to compare long
>>>      integers */
>>>       > +/* Returns 1 if vli == 0, 0 otherwise. */
>>>       > +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Returns nonzero if bit 'bit' of vli is set. */
>>>       > +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli,
>>>      bitcount_t bit);
>>>       > +
>>>       > +/* Counts the number of bits required to represent vli. */
>>>       > +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
>>>       > + const wordcount_t max_words);
>>>       > +
>>>       > +/* Sets dest = src. */
>>>       > +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t
>>>      *src,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Constant-time comparison function - secure way to compare
>>>      long integers */
>>>       > +/* Returns one if left == right, zero otherwise */
>>>       > +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Constant-time comparison function - secure way to compare
>>>      long integers */
>>>       > +/* Returns sign of left - right, in constant time. */
>>>       > +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, wordcount_t num_words);
>>>       > +
>>>       > +/* Computes vli = vli >> 1. */
>>>       > +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t
>>>      num_words);
>>>       > +
>>>       > +/* Computes result = left + right, returning carry. Can modify
>>>      in place. */
>>>       > +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = left - right, returning borrow. Can modify
>>>      in place. */
>>>       > +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = left * right. Result must be 2 * num_words
>>>      long. */
>>>       > +void mg_uecc_vli_mult(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = left^2. Result must be 2 * num_words long. */
>>>       > +void mg_uecc_vli_square(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = (left + right) % mod.
>>>       > + Assumes that left < mod and right < mod, and that result does
>>>      not overlap
>>>       > + mod. */
>>>       > +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = (left - right) % mod.
>>>       > + Assumes that left < mod and right < mod, and that result does
>>>      not overlap
>>>       > + mod. */
>>>       > +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = product % mod, where product is 2N words long.
>>>       > + Currently only designed to work for mod == curve->p or curve_n. */
>>>       > +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t
>>>      *product,
>>>       > + const mg_uecc_word_t *mod, wordcount_t num_words);
>>>       > +
>>>       > +/* Calculates result = product (mod curve->p), where product is
>>>      up to
>>>       > + 2 * curve->num_words long. */
>>>       > +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result,
>>>      mg_uecc_word_t *product,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +/* Computes result = (left * right) % mod.
>>>       > + Currently only designed to work for mod == curve->p or curve_n. */
>>>       > +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, const mg_uecc_word_t *mod,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = (left * right) % curve->p. */
>>>       > +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *right, MG_UECC_Curve curve);
>>>       > +
>>>       > +/* Computes result = left^2 % mod.
>>>       > + Currently only designed to work for mod == curve->p or curve_n. */
>>>       > +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *left,
>>>       > + const mg_uecc_word_t *mod, wordcount_t num_words);
>>>       > +
>>>       > +/* Computes result = left^2 % curve->p. */
>>>       > +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
>>>       > + const mg_uecc_word_t *left,
>>>       > + MG_UECC_Curve curve);
>>>       > +
>>>       > +/* Computes result = (1 / input) % mod.*/
>>>       > +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *input,
>>>       > + const mg_uecc_word_t *mod, wordcount_t num_words);
>>>       > +
>>>       > +#if MG_UECC_SUPPORT_COMPRESSED_POINT
>>>       > +/* Calculates a = sqrt(a) (mod curve->p) */
>>>       > +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve);
>>>       > +#endif
>>>       > +
>>>       > +/* Converts an integer in uECC native format to big-endian
>>>      bytes. */
>>>       > +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
>>>       > + const mg_uecc_word_t *native);
>>>       > +/* Converts big-endian bytes to an integer in uECC native
>>>      format. */
>>>       > +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const
>>>      uint8_t *bytes,
>>>       > + int num_bytes);
>>>       > +
>>>       > +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve);
>>>       > +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve);
>>>       > +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve);
>>>       > +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve);
>>>       > +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve);
>>>       > +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve);
>>>       > +
>>>       > +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve);
>>>       > +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve);
>>>       > +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve);
>>>       > +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve);
>>>       > +
>>>       > +int mg_uecc_valid_point(const mg_uecc_word_t *point,
>>>      MG_UECC_Curve curve);
>>>       > +
>>>       > +/* Multiplies a point by a scalar. Points are represented by the
>>>      X coordinate
>>>       > + followed by the Y coordinate in the same array, both
>>>      coordinates are
>>>       > + curve->num_words long. Note that scalar must be
>>>      curve->num_n_words long (NOT
>>>       > + curve->num_words). */
>>>       > +void mg_uecc_point_mult(mg_uecc_word_t *result, const
>>>      mg_uecc_word_t *point,
>>>       > + const mg_uecc_word_t *scalar, MG_UECC_Curve curve);
>>>       > +
>>>       > +/* Generates a random integer in the range 0 < random < top.
>>>       > + Both random and top have num_words words. */
>>>       > +int mg_uecc_generate_random_int(mg_uecc_word_t *random,
>>>       > + const mg_uecc_word_t *top,
>>>       > + wordcount_t num_words);
>>>       > +
>>>       > +#endif /* MG_UECC_ENABLE_VLI_API */
>>>       > +
>>>       > +#ifdef __cplusplus
>>>       > +} /* end of extern "C" */
>>>       > +#endif
>>>       > +
>>>       > +#endif /* _UECC_VLI_H_ */
>>>       > +
>>>       > +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD
>>>      2-clause license. */
>>>       > +
>>>       > +#ifndef _UECC_TYPES_H_
>>>       > +#define _UECC_TYPES_H_
>>>       > +
>>>       > +#ifndef MG_UECC_PLATFORM
>>>       > +#if defined(__AVR__) && __AVR__
>>>       > +#define MG_UECC_PLATFORM mg_uecc_avr
>>>       > +#elif defined(__thumb2__) || \
>>>       > + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */
>>>       > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2
>>>       > +#elif defined(__thumb__)
>>>       > +#define MG_UECC_PLATFORM mg_uecc_arm_thumb
>>>       > +#elif defined(__arm__) || defined(_M_ARM)
>>>       > +#define MG_UECC_PLATFORM mg_uecc_arm
>>>       > +#elif defined(__aarch64__)
>>>       > +#define MG_UECC_PLATFORM mg_uecc_arm64
>>>       > +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
>>>       > + defined(__I86__)
>>>       > +#define MG_UECC_PLATFORM mg_uecc_x86
>>>       > +#elif defined(__amd64__) || defined(_M_X64)
>>>       > +#define MG_UECC_PLATFORM mg_uecc_x86_64
>>>       > +#else
>>>       > +#define MG_UECC_PLATFORM mg_uecc_arch_other
>>>       > +#endif
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_UECC_ARM_USE_UMAAL
>>>       > +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6)
>>>       > +#define MG_UECC_ARM_USE_UMAAL 1
>>>       > +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >=
>>>      6) && \
>>>       > + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__)
>>>       > +#define MG_UECC_ARM_USE_UMAAL 1
>>>       > +#else
>>>       > +#define MG_UECC_ARM_USE_UMAAL 0
>>>       > +#endif
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_UECC_WORD_SIZE
>>>       > +#if MG_UECC_PLATFORM == mg_uecc_avr
>>>       > +#define MG_UECC_WORD_SIZE 1
>>>       > +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM ==
>>>      mg_uecc_arm64)
>>>       > +#define MG_UECC_WORD_SIZE 8
>>>       > +#else
>>>       > +#define MG_UECC_WORD_SIZE 4
>>>       > +#endif
>>>       > +#endif
>>>       > +
>>>       > +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \
>>>       > + (MG_UECC_WORD_SIZE != 8)
>>>       > +#error "Unsupported value for MG_UECC_WORD_SIZE"
>>>       > +#endif
>>>       > +
>>>       > +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1))
>>>       > +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR")
>>>       > +#undef MG_UECC_WORD_SIZE
>>>       > +#define MG_UECC_WORD_SIZE 1
>>>       > +#endif
>>>       > +
>>>       > +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \
>>>       > + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
>>>       > + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \
>>>       > + (MG_UECC_WORD_SIZE != 4))
>>>       > +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM")
>>>       > +#undef MG_UECC_WORD_SIZE
>>>       > +#define MG_UECC_WORD_SIZE 4
>>>       > +#endif
>>>       > +
>>>       > +typedef int8_t wordcount_t;
>>>       > +typedef int16_t bitcount_t;
>>>       > +typedef int8_t cmpresult_t;
>>>       > +
>>>       > +#if (MG_UECC_WORD_SIZE == 1)
>>>       > +
>>>       > +typedef uint8_t mg_uecc_word_t;
>>>       > +typedef uint16_t mg_uecc_dword_t;
>>>       > +
>>>       > +#define HIGH_BIT_SET 0x80
>>>       > +#define MG_UECC_WORD_BITS 8
>>>       > +#define MG_UECC_WORD_BITS_SHIFT 3
>>>       > +#define MG_UECC_WORD_BITS_MASK 0x07
>>>       > +
>>>       > +#elif (MG_UECC_WORD_SIZE == 4)
>>>       > +
>>>       > +typedef uint32_t mg_uecc_word_t;
>>>       > +typedef uint64_t mg_uecc_dword_t;
>>>       > +
>>>       > +#define HIGH_BIT_SET 0x80000000
>>>       > +#define MG_UECC_WORD_BITS 32
>>>       > +#define MG_UECC_WORD_BITS_SHIFT 5
>>>       > +#define MG_UECC_WORD_BITS_MASK 0x01F
>>>       > +
>>>       > +#elif (MG_UECC_WORD_SIZE == 8)
>>>       > +
>>>       > +typedef uint64_t mg_uecc_word_t;
>>>       > +
>>>       > +#define HIGH_BIT_SET 0x8000000000000000U
>>>       > +#define MG_UECC_WORD_BITS 64
>>>       > +#define MG_UECC_WORD_BITS_SHIFT 6
>>>       > +#define MG_UECC_WORD_BITS_MASK 0x03F
>>>       > +
>>>       > +#endif /* MG_UECC_WORD_SIZE */
>>>       > +
>>>       > +#endif /* _UECC_TYPES_H_ */
>>>       > +// End of uecc BSD-2
>>>       >
>>>       >
>>>       > struct mg_connection;
>>>       > typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
>>>       > - void *ev_data, void *fn_data);
>>>       > + void *ev_data);
>>>       > void mg_call(struct mg_connection *c, int ev, void *ev_data);
>>>       > void mg_error(struct mg_connection *c, const char *fmt, ...);
>>>       >
>>>       > enum {
>>>       > - MG_EV_ERROR, // Error char *error_message
>>>       > - MG_EV_OPEN, // Connection created NULL
>>>       > - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
>>>       > - MG_EV_RESOLVE, // Host name is resolved NULL
>>>       > - MG_EV_CONNECT, // Connection established NULL
>>>       > - MG_EV_ACCEPT, // Connection accepted NULL
>>>       > - MG_EV_TLS_HS, // TLS handshake succeeded NULL
>>>       > - MG_EV_READ, // Data received from socket long *bytes_read
>>>       > - MG_EV_WRITE, // Data written to socket long *bytes_written
>>>       > - MG_EV_CLOSE, // Connection closed NULL
>>>       > - MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message *
>>>       > - MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct
>>>      mg_http_message *
>>>       > - MG_EV_WS_OPEN, // Websocket handshake done struct
>>>      mg_http_message *
>>>       > - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
>>>       > - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
>>>       > - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
>>>       > - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
>>>       > - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
>>>       > - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
>>>       > - MG_EV_USER // Starting ID for user events
>>>       > + MG_EV_ERROR, // Error char *error_message
>>>       > + MG_EV_OPEN, // Connection created NULL
>>>       > + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis
>>>       > + MG_EV_RESOLVE, // Host name is resolved NULL
>>>       > + MG_EV_CONNECT, // Connection established NULL
>>>       > + MG_EV_ACCEPT, // Connection accepted NULL
>>>       > + MG_EV_TLS_HS, // TLS handshake succeeded NULL
>>>       > + MG_EV_READ, // Data received from socket long *bytes_read
>>>       > + MG_EV_WRITE, // Data written to socket long *bytes_written
>>>       > + MG_EV_CLOSE, // Connection closed NULL
>>>       > + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message *
>>>       > + MG_EV_HTTP_MSG, // Full HTTP request/response struct
>>>      mg_http_message *
>>>       > + MG_EV_WS_OPEN, // Websocket handshake done struct
>>>      mg_http_message *
>>>       > + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message *
>>>       > + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message *
>>>       > + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
>>>       > + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
>>>       > + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
>>>       > + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis
>>>       > + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data
>>>       > + MG_EV_USER // Starting ID for user events
>>>       > };
>>>       >
>>>       >
>>>       > @@ -1021,10 +1981,10 @@ struct mg_dns {
>>>       > };
>>>       >
>>>       > struct mg_addr {
>>>       > - uint16_t port; // TCP or UDP port in network byte order
>>>       > - uint32_t ip; // IP address in network byte order
>>>       > - uint8_t ip6[16]; // IPv6 address
>>>       > - bool is_ip6; // True when address is IPv6 address
>>>       > + uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte
>>>      order
>>>       > + uint16_t port; // TCP or UDP port in network byte order
>>>       > + uint8_t scope_id; // IPv6 scope ID
>>>       > + bool is_ip6; // True when address is IPv6 address
>>>       > };
>>>       >
>>>       > struct mg_mgr {
>>>       > @@ -1036,12 +1996,14 @@ struct mg_mgr {
>>>       > unsigned long nextid; // Next connection ID
>>>       > unsigned long timerid; // Next timer ID
>>>       > void *userdata; // Arbitrary user data pointer
>>>       > + void *tls_ctx; // TLS context shared by all TLS sessions
>>>       > uint16_t mqtt_id; // MQTT IDs for pub/sub
>>>       > void *active_dns_requests; // DNS requests in progress
>>>       > struct mg_timer *timers; // Active timers
>>>       > int epoll_fd; // Used when MG_EPOLL_ENABLE=1
>>>       > void *priv; // Used by the MIP stack
>>>       > size_t extraconnsize; // Used by the MIP stack
>>>       > + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup()
>>>       > #if MG_ENABLE_FREERTOS_TCP
>>>       > SocketSet_t ss; // NOTE(lsm): referenced from socket struct
>>>       > #endif
>>>       > @@ -1056,6 +2018,8 @@ struct mg_connection {
>>>       > unsigned long id; // Auto-incrementing unique connection ID
>>>       > struct mg_iobuf recv; // Incoming data
>>>       > struct mg_iobuf send; // Outgoing data
>>>       > + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE
>>>       > + struct mg_iobuf rtls; // TLS only. Incoming encrypted data
>>>       > mg_event_handler_t fn; // User-specified event handler function
>>>       > void *fn_data; // User-specified function parameter
>>>       > mg_event_handler_t pfn; // Protocol-specific handler function
>>>       > @@ -1066,6 +2030,7 @@ struct mg_connection {
>>>       > unsigned is_client : 1; // Outbound (client) connection
>>>       > unsigned is_accepted : 1; // Accepted (server) connection
>>>       > unsigned is_resolving : 1; // Non-blocking DNS resolution is in
>>>      progress
>>>       > + unsigned is_arplooking : 1; // Non-blocking ARP resolution is
>>>      in progress
>>>       > unsigned is_connecting : 1; // Non-blocking connect is in progress
>>>       > unsigned is_tls : 1; // TLS-enabled connection
>>>       > unsigned is_tls_hs : 1; // TLS handshake is in progress
>>>       > @@ -1096,20 +2061,18 @@ bool mg_send(struct mg_connection *,
>>>      const void *, size_t);
>>>       > size_t mg_printf(struct mg_connection *, const char *fmt, ...);
>>>       > size_t mg_vprintf(struct mg_connection *, const char *fmt,
>>>      va_list *ap);
>>>       > bool mg_aton(struct mg_str str, struct mg_addr *addr);
>>>       > -int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool
>>>      udp);
>>>       >
>>>       > // These functions are used to integrate with custom network stacks
>>>       > struct mg_connection *mg_alloc_conn(struct mg_mgr *);
>>>       > void mg_close_conn(struct mg_connection *c);
>>>       > bool mg_open_listener(struct mg_connection *c, const char *url);
>>>       > +
>>>       > +// Utility functions
>>>       > +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void
>>>      *buf, size_t len);
>>>       > +bool mg_wakeup_init(struct mg_mgr *);
>>>       > struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t
>>>      milliseconds,
>>>       > unsigned flags, void (*fn)(void *), void *arg);
>>>       >
>>>       > -// Low-level IO primives used by TLS layer
>>>       > -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
>>>       > -long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len);
>>>       > -long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
>>>       > -
>>>       >
>>>       >
>>>       >
>>>       > @@ -1127,7 +2090,6 @@ struct mg_http_message {
>>>       > struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers
>>>       > struct mg_str body; // Body
>>>       > struct mg_str head; // Request + headers
>>>       > - struct mg_str chunk; // Chunk for chunked encoding, or partial
>>>      body
>>>       > struct mg_str message; // Request + headers + body
>>>       > };
>>>       >
>>>       > @@ -1169,9 +2131,8 @@ int mg_http_get_var(const struct mg_str *,
>>>      const char *name, char *, size_t);
>>>       > int mg_url_decode(const char *s, size_t n, char *to, size_t
>>>      to_len, int form);
>>>       > size_t mg_url_encode(const char *s, size_t n, char *buf, size_t
>>>      len);
>>>       > void mg_http_creds(struct mg_http_message *, char *, size_t, char
>>>      *, size_t);
>>>       > -bool mg_http_match_uri(const struct mg_http_message *, const
>>>      char *glob);
>>>       > long mg_http_upload(struct mg_connection *c, struct
>>>      mg_http_message *hm,
>>>       > - struct mg_fs *fs, const char *path, size_t max_size);
>>>       > + struct mg_fs *fs, const char *dir, size_t max_size);
>>>       > void mg_http_bauth(struct mg_connection *, const char *user,
>>>      const char *pass);
>>>       > struct mg_str mg_http_get_header_var(struct mg_str s, struct
>>>      mg_str v);
>>>       > size_t mg_http_next_multipart(struct mg_str, size_t, struct
>>>      mg_http_part *);
>>>       > @@ -1183,60 +2144,84 @@ void mg_http_serve_ssi(struct
>>>      mg_connection *c, const char *root,
>>>       > const char *fullpath);
>>>       >
>>>       >
>>>       > +#define MG_TLS_NONE 0 // No TLS support
>>>       > +#define MG_TLS_MBED 1 // mbedTLS
>>>       > +#define MG_TLS_OPENSSL 2 // OpenSSL
>>>       > +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL)
>>>       > +#define MG_TLS_BUILTIN 3 // Built-in
>>>       > +#define MG_TLS_CUSTOM 4 // Custom implementation
>>>       > +
>>>       > +#ifndef MG_TLS
>>>       > +#define MG_TLS MG_TLS_NONE
>>>       > +#endif
>>>       > +
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > struct mg_tls_opts {
>>>       > - const char *ca; // CA certificate file. For both listeners and
>>>      clients
>>>       > - const char *crl; // Certificate Revocation List. For clients
>>>       > - const char *cert; // Certificate
>>>       > - const char *certkey; // Certificate key
>>>       > - const char *ciphers; // Cipher list
>>>       > - struct mg_str srvname; // If not empty, enables server name
>>>      verification
>>>       > - struct mg_fs *fs; // FS API for reading certificate files
>>>       > + struct mg_str ca; // PEM or DER
>>>       > + struct mg_str cert; // PEM or DER
>>>       > + struct mg_str key; // PEM or DER
>>>       > + struct mg_str name; // If not empty, enable host name verification
>>>       > + int skip_verification; // Skip certificate and host name
>>>      verification
>>>       > };
>>>       >
>>>       > -void mg_tls_init(struct mg_connection *, const struct
>>>      mg_tls_opts *);
>>>       > +void mg_tls_init(struct mg_connection *, const struct
>>>      mg_tls_opts *opts);
>>>       > void mg_tls_free(struct mg_connection *);
>>>       > long mg_tls_send(struct mg_connection *, const void *buf, size_t
>>>      len);
>>>       > long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
>>>       > size_t mg_tls_pending(struct mg_connection *);
>>>       > void mg_tls_handshake(struct mg_connection *);
>>>       >
>>>       > +// Private
>>>       > +void mg_tls_ctx_init(struct mg_mgr *);
>>>       > +void mg_tls_ctx_free(struct mg_mgr *);
>>>       > +
>>>       > +// Low-level IO primives used by TLS layer
>>>       > +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
>>>       > +long mg_io_send(struct mg_connection *c, const void *buf, size_t
>>>      len);
>>>       > +long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
>>>       >
>>>       >
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_MBEDTLS
>>>       > +
>>>       > +#if MG_TLS == MG_TLS_MBED
>>>       > #include <mbedtls/debug.h>
>>>       > #include <mbedtls/net_sockets.h>
>>>       > #include <mbedtls/ssl.h>
>>>       > +#include <mbedtls/ssl_ticket.h>
>>>       > +
>>>       > +struct mg_tls_ctx {
>>>       > + int dummy;
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + mbedtls_ssl_ticket_context tickets;
>>>       > +#endif
>>>       > +};
>>>       >
>>>       > struct mg_tls {
>>>       > - char *cafile; // CA certificate path
>>>       > mbedtls_x509_crt ca; // Parsed CA certificate
>>>       > mbedtls_x509_crt cert; // Parsed certificate
>>>       > + mbedtls_pk_context pk; // Private key context
>>>       > mbedtls_ssl_context ssl; // SSL/TLS context
>>>       > mbedtls_ssl_config conf; // SSL-TLS config
>>>       > - mbedtls_pk_context pk; // Private key context
>>>       > +#ifdef MBEDTLS_SSL_SESSION_TICKETS
>>>       > + mbedtls_ssl_ticket_context ticket; // Session tickets context
>>>       > +#endif
>>>       > };
>>>       > #endif
>>>       >
>>>       >
>>>       > -#if MG_ENABLE_OPENSSL
>>>       > +#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
>>>       >
>>>       > -#ifdef CONFIG_SSL_IMPL_WOLFSSL
>>>       > -#include <wolfssl/openssl/err.h>
>>>       > -#include <wolfssl/openssl/ssl.h>
>>>       > -#else
>>>       > #include <openssl/err.h>
>>>       > #include <openssl/ssl.h>
>>>       > -#endif
>>>       >
>>>       > struct mg_tls {
>>>       > + BIO_METHOD *bm;
>>>       > SSL_CTX *ctx;
>>>       > SSL *ssl;
>>>       > };
>>>       > @@ -1296,29 +2281,81 @@ int64_t mg_sntp_parse(const unsigned char
>>>      *buf, size_t len);
>>>       > #define MQTT_CMD_DISCONNECT 14
>>>       > #define MQTT_CMD_AUTH 15
>>>       >
>>>       > +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
>>>       > +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
>>>       > +#define MQTT_PROP_CONTENT_TYPE 0x03
>>>       > +#define MQTT_PROP_RESPONSE_TOPIC 0x08
>>>       > +#define MQTT_PROP_CORRELATION_DATA 0x09
>>>       > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
>>>       > +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
>>>       > +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
>>>       > +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
>>>       > +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
>>>       > +#define MQTT_PROP_AUTHENTICATION_DATA 0x16
>>>       > +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
>>>       > +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
>>>       > +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
>>>       > +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
>>>       > +#define MQTT_PROP_SERVER_REFERENCE 0x1C
>>>       > +#define MQTT_PROP_REASON_STRING 0x1F
>>>       > +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
>>>       > +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
>>>       > +#define MQTT_PROP_TOPIC_ALIAS 0x23
>>>       > +#define MQTT_PROP_MAXIMUM_QOS 0x24
>>>       > +#define MQTT_PROP_RETAIN_AVAILABLE 0x25
>>>       > +#define MQTT_PROP_USER_PROPERTY 0x26
>>>       > +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
>>>       > +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
>>>       > +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
>>>       > +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
>>>       > +
>>>       > +enum {
>>>       > + MQTT_PROP_TYPE_BYTE,
>>>       > + MQTT_PROP_TYPE_STRING,
>>>       > + MQTT_PROP_TYPE_STRING_PAIR,
>>>       > + MQTT_PROP_TYPE_BINARY_DATA,
>>>       > + MQTT_PROP_TYPE_VARIABLE_INT,
>>>       > + MQTT_PROP_TYPE_INT,
>>>       > + MQTT_PROP_TYPE_SHORT
>>>       > +};
>>>       > +
>>>       > enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
>>>       >
>>>       > +struct mg_mqtt_prop {
>>>       > + uint8_t id; // Enumerated at MQTT5 Reference
>>>       > + uint32_t iv; // Integer value for 8-, 16-, 32-bit integers types
>>>       > + struct mg_str key; // Non-NULL only for user property type
>>>       > + struct mg_str val; // Non-NULL only for UTF-8 types and user
>>>      properties
>>>       > +};
>>>       > +
>>>       > struct mg_mqtt_opts {
>>>       > - struct mg_str user; // Username, can be empty
>>>       > - struct mg_str pass; // Password, can be empty
>>>       > - struct mg_str client_id; // Client ID
>>>       > - struct mg_str will_topic; // Will topic
>>>       > - struct mg_str will_message; // Will message
>>>       > - uint8_t will_qos; // Will message quality of service
>>>       > - uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4.
>>>       > - uint16_t keepalive; // Keep-alive timer in seconds
>>>       > - bool will_retain; // Retain last will
>>>       > - bool clean; // Use clean session, 0 or 1
>>>       > + struct mg_str user; // Username, can be empty
>>>       > + struct mg_str pass; // Password, can be empty
>>>       > + struct mg_str client_id; // Client ID
>>>       > + struct mg_str topic; // message/subscription topic
>>>       > + struct mg_str message; // message content
>>>       > + uint8_t qos; // message quality of service
>>>       > + uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4
>>>       > + uint16_t keepalive; // Keep-alive timer in seconds
>>>       > + uint16_t retransmit_id; // For PUBLISH, init to 0
>>>       > + bool retain; // Retain flag
>>>       > + bool clean; // Clean session flag
>>>       > + struct mg_mqtt_prop *props; // MQTT5 props array
>>>       > + size_t num_props; // number of props
>>>       > + struct mg_mqtt_prop *will_props; // Valid only for CONNECT
>>>      packet (MQTT5)
>>>       > + size_t num_will_props; // Number of will props
>>>       > };
>>>       >
>>>       > struct mg_mqtt_message {
>>>       > - struct mg_str topic; // Parsed topic
>>>       > - struct mg_str data; // Parsed message
>>>       > - struct mg_str dgram; // Whole MQTT datagram, including headers
>>>       > - uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP,
>>>      SUBACK, PUBLISH
>>>       > - uint8_t cmd; // MQTT command, one of MQTT_CMD_*
>>>       > - uint8_t qos; // Quality of service
>>>       > - uint8_t ack; // Connack return code. 0 - success
>>>       > + struct mg_str topic; // Parsed topic for PUBLISH
>>>       > + struct mg_str data; // Parsed message for PUBLISH
>>>       > + struct mg_str dgram; // Whole MQTT packet, including headers
>>>       > + uint16_t id; // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK,
>>>      PUBLISH
>>>       > + uint8_t cmd; // MQTT command, one of MQTT_CMD_*
>>>       > + uint8_t qos; // Quality of service
>>>       > + uint8_t ack; // CONNACK return code, 0 = success
>>>       > + size_t props_start; // Offset to the start of the properties
>>>      (MQTT5)
>>>       > + size_t props_size; // Length of the properties
>>>       > };
>>>       >
>>>       > struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char
>>>      *url,
>>>       > @@ -1327,15 +2364,16 @@ struct mg_connection
>>>      *mg_mqtt_connect(struct mg_mgr *, const char *url,
>>>       > struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const
>>>      char *url,
>>>       > mg_event_handler_t fn, void *fn_data);
>>>       > void mg_mqtt_login(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts);
>>>       > -void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
>>>       > - struct mg_str data, int qos, bool retain);
>>>       > -void mg_mqtt_sub(struct mg_connection *, struct mg_str topic,
>>>      int qos);
>>>       > +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct
>>>      mg_mqtt_opts *opts);
>>>       > +void mg_mqtt_sub(struct mg_connection *, const struct
>>>      mg_mqtt_opts *opts);
>>>       > int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct
>>>      mg_mqtt_message *);
>>>       > void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd,
>>>      uint8_t flags,
>>>       > uint32_t len);
>>>       > void mg_mqtt_ping(struct mg_connection *);
>>>       > void mg_mqtt_pong(struct mg_connection *);
>>>       > -void mg_mqtt_disconnect(struct mg_connection *);
>>>       > +void mg_mqtt_disconnect(struct mg_connection *, const struct
>>>      mg_mqtt_opts *);
>>>       > +size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct
>>>      mg_mqtt_prop *,
>>>       > + size_t ofs);
>>>       >
>>>       >
>>>       >
>>>       > @@ -1387,6 +2425,7 @@ size_t mg_dns_parse_rr(const uint8_t *buf,
>>>      size_t len, size_t ofs,
>>>       > enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2,
>>>      MG_JSON_NOT_FOUND = -3 };
>>>       > int mg_json_get(struct mg_str json, const char *path, int *toklen);
>>>       >
>>>       > +struct mg_str mg_json_get_tok(struct mg_str json, const char
>>>      *path);
>>>       > bool mg_json_get_num(struct mg_str json, const char *path, double
>>>      *v);
>>>       > bool mg_json_get_bool(struct mg_str json, const char *path, bool
>>>      *v);
>>>       > long mg_json_get_long(struct mg_str json, const char *path, long
>>>      dflt);
>>>       > @@ -1394,6 +2433,10 @@ char *mg_json_get_str(struct mg_str json,
>>>      const char *path);
>>>       > char *mg_json_get_hex(struct mg_str json, const char *path, int
>>>      *len);
>>>       > char *mg_json_get_b64(struct mg_str json, const char *path, int
>>>      *len);
>>>       >
>>>       > +bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
>>>       > +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str
>>>      *key,
>>>       > + struct mg_str *val);
>>>       > +
>>>       >
>>>       >
>>>       >
>>>       > @@ -1426,96 +2469,342 @@ void mg_rpc_vok(struct mg_rpc_req *,
>>>      const char *fmt, va_list *ap);
>>>       > void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt,
>>>      ...);
>>>       > void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt,
>>>      va_list *);
>>>       > void mg_rpc_list(struct mg_rpc_req *r);
>>>       > +// Copyright (c) 2023 Cesanta Software Limited
>>>       > +// All rights reserved
>>>       >
>>>       >
>>>       >
>>>       >
>>>       >
>>>       > -struct mip_if; // MIP network interface
>>>       > +#define MG_OTA_NONE 0 // No OTA support
>>>       > +#define MG_OTA_FLASH 1 // OTA via an internal flash
>>>       > +#define MG_OTA_ESP32 2 // ESP32 OTA implementation
>>>       > +#define MG_OTA_CUSTOM 100 // Custom implementation
>>>       >
>>>       > -struct mip_driver {
>>>       > - bool (*init)(struct mip_if *); // Initialise driver
>>>       > - size_t (*tx)(const void *, size_t, struct mip_if *); //
>>>      Transmit frame
>>>       > - size_t (*rx)(void *buf, size_t len, struct mip_if *); //
>>>      Receive frame (poll)
>>>       > - bool (*up)(struct mip_if *); // Up/down status
>>>       > -};
>>>       > +#ifndef MG_OTA
>>>       > +#define MG_OTA MG_OTA_NONE
>>>       > +#endif
>>>       > +
>>>       > +#if defined(__GNUC__) && !defined(__APPLE__)
>>>       > +#define MG_IRAM __attribute__((section(".iram")))
>>>       > +#else
>>>       > +#define MG_IRAM
>>>       > +#endif
>>>       >
>>>       > -// Receive queue - single producer, single consumer queue.
>>>      Interrupt-based
>>>       > -// drivers copy received frames to the queue in interrupt
>>>      context. mip_poll()
>>>       > -// function runs in event loop context, reads from the queue
>>>       > -struct queue {
>>>       > - uint8_t *buf;
>>>       > - size_t len;
>>>       > - volatile size_t tail, head;
>>>       > +// Firmware update API
>>>       > +bool mg_ota_begin(size_t new_firmware_size); // Start writing
>>>       > +bool mg_ota_write(const void *buf, size_t len); // Write chunk,
>>>      aligned to 1k
>>>       > +bool mg_ota_end(void); // Stop writing
>>>       > +
>>>       > +enum {
>>>       > + MG_OTA_UNAVAILABLE = 0, // No OTA information is present
>>>       > + MG_OTA_FIRST_BOOT = 1, // Device booting the first time after
>>>      the OTA
>>>       > + MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback
>>>       > + MG_OTA_COMMITTED = 3 // The firmware is good
>>>       > };
>>>       > +enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 };
>>>       > +
>>>       > +int mg_ota_status(int firmware); // Return firmware status MG_OTA_*
>>>       > +uint32_t mg_ota_crc32(int firmware); // Return firmware checksum
>>>       > +uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp,
>>>      UNIX UTC epoch
>>>       > +size_t mg_ota_size(int firmware); // Firmware size
>>>       > +
>>>       > +bool mg_ota_commit(void); // Commit current firmware
>>>       > +bool mg_ota_rollback(void); // Rollback to the previous firmware
>>>       > +MG_IRAM void mg_ota_boot(void); // Bootloader function
>>>       > +// Copyright (c) 2023 Cesanta Software Limited
>>>       > +// All rights reserved
>>>       >
>>>       > -#define MIP_ARP_ENTRIES 5 // Number of ARP cache entries.
>>>      Maximum 21
>>>       > -#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES) // ARP cache size
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#define MG_DEVICE_NONE 0 // Dummy system
>>>       > +
>>>       > +#define MG_DEVICE_STM32H5 1 // STM32 H5
>>>       > +#define MG_DEVICE_STM32H7 2 // STM32 H7
>>>       > +#define MG_DEVICE_CH32V307 100 // WCH CH32V307
>>>       > +#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
>>>       > +#define MG_DEVICE_RT1020 300 // IMXRT1020
>>>       > +#define MG_DEVICE_RT1060 301 // IMXRT1060
>>>       > +#define MG_DEVICE_CUSTOM 1000 // Custom implementation
>>>       > +
>>>       > +#ifndef MG_DEVICE
>>>       > +#define MG_DEVICE MG_DEVICE_NONE
>>>       > +#endif
>>>       > +
>>>       > +// Flash information
>>>       > +void *mg_flash_start(void); // Return flash start address
>>>       > +size_t mg_flash_size(void); // Return flash size
>>>       > +size_t mg_flash_sector_size(void); // Return flash sector size
>>>       > +size_t mg_flash_write_align(void); // Return flash write align,
>>>      minimum 4
>>>       > +int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
>>>       > +
>>>       > +// Write, erase, swap bank
>>>       > +bool mg_flash_write(void *addr, const void *buf, size_t len);
>>>       > +bool mg_flash_erase(void *sector);
>>>       > +bool mg_flash_swap_bank(void);
>>>       > +
>>>       > +// Convenience functions to store data on a flash sector with
>>>      wear levelling
>>>       > +// If `sector` is NULL, then the last sector of flash is used
>>>       > +bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t
>>>      len);
>>>       > +bool mg_flash_save(void *sector, uint32_t key, const void *buf,
>>>      size_t len);
>>>       > +
>>>       > +void mg_device_reset(void); // Reboot device immediately
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
>>>       > +struct mg_tcpip_if; // Mongoose TCP/IP network interface
>>>       > +
>>>       > +struct mg_tcpip_driver {
>>>       > + bool (*init)(struct mg_tcpip_if *); // Init driver
>>>       > + size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); //
>>>      Transmit frame
>>>       > + size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); //
>>>      Receive frame
>>>       > + bool (*up)(struct mg_tcpip_if *); // Up/down status
>>>       > +};
>>>       >
>>>       > // Network interface
>>>       > -struct mip_if {
>>>       > - uint8_t mac[6]; // MAC address. Must be set to a valid MAC
>>>       > - uint32_t ip, mask, gw; // IP address, mask, default gateway
>>>       > - struct mg_str rx; // Output (TX) buffer
>>>       > - struct mg_str tx; // Input (RX) buffer
>>>       > - bool enable_dhcp_client; // Enable DCHP client
>>>       > - bool enable_dhcp_server; // Enable DCHP server
>>>       > - struct mip_driver *driver; // Low level driver
>>>       > - void *driver_data; // Driver-specific data
>>>       > - struct mg_mgr *mgr; // Mongoose event manager
>>>       > - struct queue queue; // Set queue.len for interrupt based drivers
>>>       > +struct mg_tcpip_if {
>>>       > + uint8_t mac[6]; // MAC address. Must be set to a valid MAC
>>>       > + uint32_t ip, mask, gw; // IP address, mask, default gateway
>>>       > + struct mg_str tx; // Output (TX) buffer
>>>       > + bool enable_dhcp_client; // Enable DCHP client
>>>       > + bool enable_dhcp_server; // Enable DCHP server
>>>       > + bool enable_get_gateway; // DCHP server sets client as gateway
>>>       > + bool enable_crc32_check; // Do a CRC check on RX frames and
>>>      strip it
>>>       > + bool enable_mac_check; // Do a MAC check on RX frames
>>>       > + struct mg_tcpip_driver *driver; // Low level driver
>>>       > + void *driver_data; // Driver-specific data
>>>       > + struct mg_mgr *mgr; // Mongoose event manager
>>>       > + struct mg_queue recv_queue; // Receive queue
>>>       > + uint16_t mtu; // Interface MTU
>>>       > +#define MG_TCPIP_MTU_DEFAULT 1500
>>>       >
>>>       > // Internal state, user can use it but should not change it
>>>       > - uint64_t now; // Current time
>>>       > - uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
>>>       > - uint64_t lease_expire; // Lease expiration time
>>>       > - uint8_t arp_cache[MIP_ARP_CS]; // Each entry is 12 bytes
>>>       > - uint16_t eport; // Next ephemeral port
>>>       > - uint16_t dropped; // Number of dropped frames
>>>       > - uint8_t state; // Current state
>>>       > -#define MIP_STATE_DOWN 0 // Interface is down
>>>       > -#define MIP_STATE_UP 1 // Interface is up
>>>       > -#define MIP_STATE_READY 2 // Interface is up and has IP
>>>       > + uint8_t gwmac[6]; // Router's MAC
>>>       > + uint64_t now; // Current time
>>>       > + uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state
>>>       > + uint64_t lease_expire; // Lease expiration time, in ms
>>>       > + uint16_t eport; // Next ephemeral port
>>>       > + volatile uint32_t ndrop; // Number of received, but dropped frames
>>>       > + volatile uint32_t nrecv; // Number of received frames
>>>       > + volatile uint32_t nsent; // Number of transmitted frames
>>>       > + volatile uint32_t nerr; // Number of driver errors
>>>       > + uint8_t state; // Current state
>>>       > +#define MG_TCPIP_STATE_DOWN 0 // Interface is down
>>>       > +#define MG_TCPIP_STATE_UP 1 // Interface is up
>>>       > +#define MG_TCPIP_STATE_REQ 2 // Interface is up and has
>>>      requested an IP
>>>       > +#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP
>>>      assigned
>>>       > };
>>>       >
>>>       > -void mip_init(struct mg_mgr *, struct mip_if *);
>>>       > -void mip_free(struct mip_if *);
>>>       > -void mip_qwrite(void *buf, size_t len, struct mip_if *ifp);
>>>       > -size_t mip_qread(void *buf, struct mip_if *ifp);
>>>       > -// conveniency rx function for IRQ-driven drivers
>>>       > -size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp);
>>>       > -
>>>       > -extern struct mip_driver mip_driver_stm32;
>>>       > -extern struct mip_driver mip_driver_w5500;
>>>       > -extern struct mip_driver mip_driver_tm4c;
>>>       > +void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
>>>       > +void mg_tcpip_free(struct mg_tcpip_if *);
>>>       > +void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if
>>>      *ifp);
>>>       > +
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32f;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_same54;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_cmsis;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_ra;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc;
>>>       > +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7;
>>>       >
>>>       > // Drivers that require SPI, can use this SPI abstraction
>>>       > -struct mip_spi {
>>>       > +struct mg_tcpip_spi {
>>>       > void *spi; // Opaque SPI bus descriptor
>>>       > void (*begin)(void *); // SPI begin: slave select low
>>>       > void (*end)(void *); // SPI end: slave select high
>>>       > uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1
>>>      byte, read reply
>>>       > };
>>>       > +#endif
>>>       >
>>>       > -#ifdef MIP_QPROFILE
>>>       > -enum {
>>>       > - QP_IRQTRIGGERED = 0, // payload is number of interrupts so far
>>>       > - QP_FRAMEPUSHED, // available space in the frame queue
>>>       > - QP_FRAMEPOPPED, // available space in the frame queue
>>>       > - QP_FRAMEDONE, // available space in the frame queue
>>>       > - QP_FRAMEDROPPED, // number of dropped frames
>>>       > - QP_QUEUEOVF // profiling queue is full, payload is number of
>>>      frame drops
>>>       > +
>>>       > +
>>>       > +// Macros to record timestamped events that happens with a
>>>      connection.
>>>       > +// They are saved into a c->prof IO buffer, each event is a name
>>>      and a 32-bit
>>>       > +// timestamp in milliseconds since connection init time.
>>>       > +//
>>>       > +// Test (run in two separate terminals):
>>>       > +// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1
>>>       > +// curl localhost:8000
>>>       > +// Output:
>>>       > +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile:
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE
>>>       > +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE
>>>       > +//
>>>       > +// Usage:
>>>       > +// Enable profiling by setting MG_ENABLE_PROFILE=1
>>>       > +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like
>>>      to measure
>>>       > +
>>>       > +#if MG_ENABLE_PROFILE
>>>       > +struct mg_profitem {
>>>       > + const char *name; // Event name
>>>       > + uint32_t timestamp; // Milliseconds since connection creation
>>>      (MG_EV_OPEN)
>>>       > };
>>>       >
>>>       > -void qp_mark(unsigned int type, int len);
>>>       > -void qp_log(void); // timestamp, type, payload
>>>       > -void qp_init(void);
>>>       > +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items
>>>      wih to realloc
>>>       > +
>>>       > +// Adding a profile item to the c->prof. Must be as fast as
>>>      possible.
>>>       > +// Reallocation of the c->prof iobuf is not desirable here,
>>>      that's why we
>>>       > +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY.
>>>       > +// This macro just inits and copies 8 bytes, and calls mg_millis(),
>>>       > +// which should be fast enough.
>>>       > +#define MG_PROF_ADD(c, name_) \
>>>       > + do { \
>>>       > + struct mg_iobuf *io = &c->prof; \
>>>       > + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \
>>>       > + struct mg_profitem item = {name_, (uint32_t) mg_millis() -
>>>      inittime}; \
>>>       > + mg_iobuf_add(io, io->len, &item, sizeof(item)); \
>>>       > + } while (0)
>>>       > +
>>>       > +// Initialising profile for a new connection. Not time sensitive
>>>       > +#define MG_PROF_INIT(c) \
>>>       > + do { \
>>>       > + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \
>>>       > + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \
>>>       > + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \
>>>       > + } while (0)
>>>       > +
>>>       > +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof)
>>>       > +
>>>       > +// Dumping the profile. Not time sensitive
>>>       > +#define MG_PROF_DUMP(c) \
>>>       > + do { \
>>>       > + struct mg_iobuf *io = &c->prof; \
>>>       > + struct mg_profitem *p = (struct mg_profitem *) io->buf; \
>>>       > + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \
>>>       > + MG_INFO(("%lu profile:", c->id)); \
>>>       > + while (p < e) { \
>>>       > + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \
>>>       > + p++; \
>>>       > + } \
>>>       > + } while (0)
>>>       > +
>>>       > #else
>>>       > -#define qp_mark(a, b)
>>>       > +#define MG_PROF_INIT(c)
>>>       > +#define MG_PROF_FREE(c)
>>>       > +#define MG_PROF_ADD(c, name)
>>>       > +#define MG_PROF_DUMP(c)
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) &&
>>>      MG_ENABLE_DRIVER_CMSIS
>>>       > +
>>>       > +#include "Driver_ETH_MAC.h" // keep this include
>>>       > +#include "Driver_ETH_PHY.h" // keep this include
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) &&
>>>      MG_ENABLE_DRIVER_IMXRT
>>>       > +
>>>       > +struct mg_tcpip_driver_imxrt_data {
>>>       > + // MDC clock divider. MDC clock is derived from IPS Bus clock
>>>      (ipg_clk),
>>>       > + // must not exceed 2.5MHz. Configuration for clock range
>>>      2.36~2.50 MHz
>>>       > + // 37.5.1.8.2, Table 37-46 : f = ipg_clk / (2(mdc_cr + 1))
>>>       > + // ipg_clk mdc_cr VALUE
>>>       > + // --------------------------
>>>       > + // -1 <-- TODO() tell driver to guess the value
>>>       > + // 25 MHz 4
>>>       > + // 33 MHz 6
>>>       > + // 40 MHz 7
>>>       > + // 50 MHz 9
>>>       > + // 66 MHz 13
>>>       > + int mdc_cr; // Valid values: -1 to 63
>>>       > +
>>>       > + uint8_t phy_addr; // PHY address
>>>       > +};
>>>       > +
>>>       > +#ifndef MG_TCPIP_PHY_ADDR
>>>       > +#define MG_TCPIP_PHY_ADDR 2
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 24
>>>       > +#endif
>>>       > +
>>>       > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>>>       > + do { \
>>>       > + static struct mg_tcpip_driver_imxrt_data driver_data_; \
>>>       > + static struct mg_tcpip_if mif_; \
>>>       > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>>>       > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>>>       > + mif_.ip = MG_TCPIP_IP; \
>>>       > + mif_.mask = MG_TCPIP_MASK; \
>>>       > + mif_.gw = MG_TCPIP_GW; \
>>>       > + mif_.driver = &mg_tcpip_driver_imxrt; \
>>>       > + mif_.driver_data = &driver_data_; \
>>>       > + MG_SET_MAC_ADDRESS(mif_.mac); \
>>>       > + mg_tcpip_init(mgr, &mif_); \
>>>       > + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \
>>>       > + } while (0)
>>>       > +
>>>       > #endif
>>>       >
>>>       >
>>>       > -struct mip_driver_stm32_data {
>>>       > +
>>>       > +
>>>       > +struct mg_phy {
>>>       > + uint16_t (*read_reg)(uint8_t addr, uint8_t reg);
>>>       > + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value);
>>>       > +};
>>>       > +
>>>       > +// PHY configuration settings, bitmask
>>>       > +enum {
>>>       > + MG_PHY_LEDS_ACTIVE_HIGH =
>>>       > + (1 << 0), // Set if PHY LEDs are connected to ground
>>>       > + MG_PHY_CLOCKS_MAC =
>>>       > + (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY
>>>       > +};
>>>       > +
>>>       > +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M };
>>>       > +
>>>       > +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config);
>>>       > +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex,
>>>       > + uint8_t *speed);
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) &&
>>>      MG_ENABLE_DRIVER_RA
>>>       > +
>>>       > +struct mg_tcpip_driver_ra_data {
>>>       > + // MDC clock "divider". MDC clock is software generated,
>>>       > + uint32_t clock; // core clock frequency in Hz
>>>       > + uint16_t irqno; // IRQn, R_ICU->IELSR[irqno]
>>>       > + uint8_t phy_addr; // PHY address
>>>       > +};
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) &&
>>>      MG_ENABLE_DRIVER_SAME54
>>>       > +
>>>       > +struct mg_tcpip_driver_same54_data {
>>>       > + int mdc_cr;
>>>       > +};
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 5
>>>       > +#endif
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
>>>       > + MG_ENABLE_DRIVER_STM32F
>>>       > +
>>>       > +struct mg_tcpip_driver_stm32f_data {
>>>       > // MDC clock divider. MDC clock is derived from HCLK, must not
>>>      exceed 2.5MHz
>>>       > // HCLK range DIVIDER mdc_cr VALUE
>>>       > // -------------------------------------
>>>       > @@ -1528,10 +2817,88 @@ struct mip_driver_stm32_data {
>>>       > // 216-310 MHz HCLK/124 5
>>>       > // 110, 111 Reserved
>>>       > int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>>>       > +
>>>       > + uint8_t phy_addr; // PHY address
>>>       > +};
>>>       > +
>>>       > +#ifndef MG_TCPIP_PHY_ADDR
>>>       > +#define MG_TCPIP_PHY_ADDR 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 4
>>>       > +#endif
>>>       > +
>>>       > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>>>       > + do { \
>>>       > + static struct mg_tcpip_driver_stm32f_data driver_data_; \
>>>       > + static struct mg_tcpip_if mif_; \
>>>       > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>>>       > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>>>       > + mif_.ip = MG_TCPIP_IP; \
>>>       > + mif_.mask = MG_TCPIP_MASK; \
>>>       > + mif_.gw = MG_TCPIP_GW; \
>>>       > + mif_.driver = &mg_tcpip_driver_stm32f; \
>>>       > + mif_.driver_data = &driver_data_; \
>>>       > + MG_SET_MAC_ADDRESS(mif_.mac); \
>>>       > + mg_tcpip_init(mgr, &mif_); \
>>>       > + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \
>>>       > + } while (0)
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
>>>       > + MG_ENABLE_DRIVER_STM32H
>>>       > +
>>>       > +struct mg_tcpip_driver_stm32h_data {
>>>       > + // MDC clock divider. MDC clock is derived from HCLK, must not
>>>      exceed 2.5MHz
>>>       > + // HCLK range DIVIDER mdc_cr VALUE
>>>       > + // -------------------------------------
>>>       > + // -1 <-- tell driver to guess the value
>>>       > + // 60-100 MHz HCLK/42 0
>>>       > + // 100-150 MHz HCLK/62 1
>>>       > + // 20-35 MHz HCLK/16 2
>>>       > + // 35-60 MHz HCLK/26 3
>>>       > + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI
>>>       > + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI
>>>       > + // 110, 111 Reserved
>>>       > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>>>       > +
>>>       > + uint8_t phy_addr; // PHY address
>>>       > + uint8_t phy_conf; // PHY config
>>>       > };
>>>       >
>>>       > +#ifndef MG_TCPIP_PHY_ADDR
>>>       > +#define MG_TCPIP_PHY_ADDR 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 4
>>>       > +#endif
>>>       > +
>>>       > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>>>       > + do { \
>>>       > + static struct mg_tcpip_driver_stm32h_data driver_data_; \
>>>       > + static struct mg_tcpip_if mif_; \
>>>       > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>>>       > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>>>       > + mif_.ip = MG_TCPIP_IP; \
>>>       > + mif_.mask = MG_TCPIP_MASK; \
>>>       > + mif_.gw = MG_TCPIP_GW; \
>>>       > + mif_.driver = &mg_tcpip_driver_stm32h; \
>>>       > + mif_.driver_data = &driver_data_; \
>>>       > + MG_SET_MAC_ADDRESS(mif_.mac); \
>>>       > + mg_tcpip_init(mgr, &mif_); \
>>>       > + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
>>>       > + } while (0)
>>>       > +
>>>       > +#endif
>>>       > +
>>>       >
>>>       > -struct mip_driver_tm4c_data {
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) &&
>>>      MG_ENABLE_DRIVER_TM4C
>>>       > +
>>>       > +struct mg_tcpip_driver_tm4c_data {
>>>       > // MDC clock divider. MDC clock is derived from SYSCLK, must not
>>>      exceed 2.5MHz
>>>       > // SYSCLK range DIVIDER mdc_cr VALUE
>>>       > // -------------------------------------
>>>       > @@ -1544,6 +2911,102 @@ struct mip_driver_tm4c_data {
>>>       > int mdc_cr; // Valid values: -1, 0, 1, 2, 3
>>>       > };
>>>       >
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 1
>>>       > +#endif
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) &&
>>>      MG_ENABLE_DRIVER_W5500
>>>       > +
>>>       > +#undef MG_ENABLE_TCPIP_DRIVER_INIT
>>>       > +#define MG_ENABLE_TCPIP_DRIVER_INIT 0
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) &&
>>>      MG_ENABLE_DRIVER_XMC7
>>>       > +
>>>       > +struct mg_tcpip_driver_xmc7_data {
>>>       > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>>>       > + uint8_t phy_addr;
>>>       > +};
>>>       > +
>>>       > +#ifndef MG_TCPIP_PHY_ADDR
>>>       > +#define MG_TCPIP_PHY_ADDR 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 3
>>>       > +#endif
>>>       > +
>>>       > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>>>       > + do { \
>>>       > + static struct mg_tcpip_driver_xmc7_data driver_data_; \
>>>       > + static struct mg_tcpip_if mif_; \
>>>       > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>>>       > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>>>       > + mif_.ip = MG_TCPIP_IP; \
>>>       > + mif_.mask = MG_TCPIP_MASK; \
>>>       > + mif_.gw = MG_TCPIP_GW; \
>>>       > + mif_.driver = &mg_tcpip_driver_xmc7; \
>>>       > + mif_.driver_data = &driver_data_; \
>>>       > + MG_SET_MAC_ADDRESS(mif_.mac); \
>>>       > + mg_tcpip_init(mgr, &mif_); \
>>>       > + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \
>>>       > + } while (0)
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > +
>>>       > +
>>>       > +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) &&
>>>      MG_ENABLE_DRIVER_XMC
>>>       > +
>>>       > +struct mg_tcpip_driver_xmc_data {
>>>       > + // 13.2.8.1 Station Management Functions
>>>       > + // MDC clock divider (). MDC clock is derived from ETH MAC clock
>>>       > + // It must not exceed 2.5MHz
>>>       > + // ETH Clock range DIVIDER mdc_cr VALUE
>>>       > + // --------------------------------------------
>>>       > + // -1 <-- tell driver to guess the value
>>>       > + // 60-100 MHz ETH Clock/42 0
>>>       > + // 100-150 MHz ETH Clock/62 1
>>>       > + // 20-35 MHz ETH Clock/16 2
>>>       > + // 35-60 MHz ETH Clock/26 3
>>>       > + // 150-250 MHz ETH Clock/102 4
>>>       > + // 250-300 MHz ETH Clock/124 5
>>>       > + // 110, 111 Reserved
>>>       > + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
>>>       > + uint8_t phy_addr;
>>>       > +};
>>>       > +
>>>       > +#ifndef MG_TCPIP_PHY_ADDR
>>>       > +#define MG_TCPIP_PHY_ADDR 0
>>>       > +#endif
>>>       > +
>>>       > +#ifndef MG_DRIVER_MDC_CR
>>>       > +#define MG_DRIVER_MDC_CR 4
>>>       > +#endif
>>>       > +
>>>       > +#define MG_TCPIP_DRIVER_INIT(mgr) \
>>>       > + do { \
>>>       > + static struct mg_tcpip_driver_xmc_data driver_data_; \
>>>       > + static struct mg_tcpip_if mif_; \
>>>       > + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
>>>       > + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
>>>       > + mif_.ip = MG_TCPIP_IP; \
>>>       > + mif_.mask = MG_TCPIP_MASK; \
>>>       > + mif_.gw = MG_TCPIP_GW; \
>>>       > + mif_.driver = &mg_tcpip_driver_xmc; \
>>>       > + mif_.driver_data = &driver_data_; \
>>>       > + MG_SET_MAC_ADDRESS(mif_.mac); \
>>>       > + mg_tcpip_init(mgr, &mif_); \
>>>       > + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \
>>>       > + } while (0)
>>>       > +
>>>       > +#endif
>>>       > +
>>>       > #ifdef __cplusplus
>>>       > }
>>>       > #endif
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "swupdate" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to swupdate+unsubscribe@googlegroups.com
>>> <mailto:swupdate+unsubscribe@googlegroups.com>.
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com <https://groups.google.com/d/msgid/swupdate/8a33707f-02fb-4938-b1c0-ce9e2dc26514n%40googlegroups.com?utm_medium=email&utm_source=footer>.
Michael Glembotzki July 14, 2024, 1:40 p.m. UTC | #6
Hi Stefano,

You are referring to this report, right?
https://groups.google.com/g/swupdate/c/zkVQxvgLpH8

It just seems to be one defect in mongoose_interface.c,
or have I missed something? I will fix it.

I will also report the other findings to the mongoose project.

Best regards
Michael 
Stefano Babic schrieb am Sonntag, 14. Juli 2024 um 11:18:14 UTC+2:

>
Michael Glembotzki July 16, 2024, 7:28 a.m. UTC | #7
Hi Stefano,

> It just seems to be one defect in mongoose_interface.c,
> or have I missed something? I will fix it.

Null pointer dereference in mongoose_interface.c is fixed. I updated also my
github branch: 
https://github.com/linkjumper/swupdate/tree/feature/migl/mongoose_update_V3


> I will also report the other findings to the mongoose project.

The maintainers of mongoose do not want to fix the findings in mongoose:
https://github.com/cesanta/mongoose/issues/2834
Would you like us to have the warnings fixed in swupdate? However,
these will reappear with each Mongoose update.

Best regards
Michael
Stefano Babic July 16, 2024, 7:32 a.m. UTC | #8
Hi Michael,

On 16.07.24 09:28, Michael Glembotzki wrote:
> Hi Stefano,
>
>  > It just seems to be one defect in mongoose_interface.c,
>  > or have I missed something? I will fix it.
>
> Null pointer dereference in mongoose_interface.c is fixed. I updated also my
> github branch:
> https://github.com/linkjumper/swupdate/tree/feature/migl/mongoose_update_V3
>

Thanks !

>
>  > I will also report the other findings to the mongoose project.
>
> The maintainers of mongoose do not want to fix the findings in mongoose:
> https://github.com/cesanta/mongoose/issues/2834

I know this..

> Would you like us to have the warnings fixed in swupdate? However,
> these will reappear with each Mongoose update.

No, this is rather a waste of efforts.

Best regards,
Stefano
diff mbox series

Patch

diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
index 83d8fe18..3fc0e14a 100644
--- a/mongoose/mongoose.c
+++ b/mongoose/mongoose.c
@@ -1,5 +1,5 @@ 
 // Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2022 Cesanta Software Limited
+// Copyright (c) 2013-2024 Cesanta Software Limited
 // All rights reserved
 //
 // This software is dual-licensed: you can redistribute it and/or modify
@@ -15,7 +15,7 @@ 
 // Alternatively, you can license this software under a commercial
 // license, as set out in https://www.mongoose.ws/licensing/
 //
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only or commercial
 
 #include "mongoose.h"
 
@@ -24,8 +24,7 @@ 
 #endif
 
 
-
-static int mg_b64idx(int c) {
+static int mg_base64_encode_single(int c) {
   if (c < 26) {
     return c + 'A';
   } else if (c < 52) {
@@ -37,7 +36,7 @@  static int mg_b64idx(int c) {
   }
 }
 
-static int mg_b64rev(int c) {
+static int mg_base64_decode_single(int c) {
   if (c >= 'A' && c <= 'Z') {
     return c - 'A';
   } else if (c >= 'a' && c <= 'z') {
@@ -55,24 +54,24 @@  static int mg_b64rev(int c) {
   }
 }
 
-int mg_base64_update(unsigned char ch, char *to, int n) {
-  int rem = (n & 3) % 3;
+size_t mg_base64_update(unsigned char ch, char *to, size_t n) {
+  unsigned long rem = (n & 3) % 3;
   if (rem == 0) {
-    to[n] = (char) mg_b64idx(ch >> 2);
+    to[n] = (char) mg_base64_encode_single(ch >> 2);
     to[++n] = (char) ((ch & 3) << 4);
   } else if (rem == 1) {
-    to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
+    to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 4));
     to[++n] = (char) ((ch & 15) << 2);
   } else {
-    to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
-    to[++n] = (char) mg_b64idx(ch & 63);
+    to[n] = (char) mg_base64_encode_single(to[n] | (ch >> 6));
+    to[++n] = (char) mg_base64_encode_single(ch & 63);
     n++;
   }
   return n;
 }
 
-int mg_base64_final(char *to, int n) {
-  int saved = n;
+size_t mg_base64_final(char *to, size_t n) {
+  size_t saved = n;
   // printf("---[%.*s]\n", n, to);
   if (n & 3) n = mg_base64_update(0, to, n);
   if ((saved & 3) == 2) n--;
@@ -82,20 +81,27 @@  int mg_base64_final(char *to, int n) {
   return n;
 }
 
-int mg_base64_encode(const unsigned char *p, int n, char *to) {
-  int i, len = 0;
+size_t mg_base64_encode(const unsigned char *p, size_t n, char *to, size_t dl) {
+  size_t i, len = 0;
+  if (dl > 0) to[0] = '\0';
+  if (dl < ((n / 3) + (n % 3 ? 1 : 0)) * 4 + 1) return 0;
   for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
   len = mg_base64_final(to, len);
   return len;
 }
 
-int mg_base64_decode(const char *src, int n, char *dst) {
+size_t mg_base64_decode(const char *src, size_t n, char *dst, size_t dl) {
   const char *end = src == NULL ? NULL : src + n;  // Cannot add to NULL
-  int len = 0;
+  size_t len = 0;
+  if (dl < n / 4 * 3 + 1) goto fail;
   while (src != NULL && src + 3 < end) {
-    int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = mg_b64rev(src[2]),
-        d = mg_b64rev(src[3]);
-    if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0;
+    int a = mg_base64_decode_single(src[0]),
+        b = mg_base64_decode_single(src[1]),
+        c = mg_base64_decode_single(src[2]),
+        d = mg_base64_decode_single(src[3]);
+    if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) {
+      goto fail;
+    }
     dst[len++] = (char) ((a << 2) | (b >> 4));
     if (src[2] != '=') {
       dst[len++] = (char) ((b << 4) | (c >> 2));
@@ -105,1942 +111,2915 @@  int mg_base64_decode(const char *src, int n, char *dst) {
   }
   dst[len] = '\0';
   return len;
+fail:
+  if (dl > 0) dst[0] = '\0';
+  return 0;
 }
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/dns.c"
+#line 1 "src/device_ch32v307.c"
 #endif
 
 
 
+#if MG_DEVICE == MG_DEVICE_CH32V307
+// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html
 
+#define FLASH_BASE 0x40022000
+#define FLASH_ACTLR (FLASH_BASE + 0)
+#define FLASH_KEYR (FLASH_BASE + 4)
+#define FLASH_OBKEYR (FLASH_BASE + 8)
+#define FLASH_STATR (FLASH_BASE + 12)
+#define FLASH_CTLR (FLASH_BASE + 16)
+#define FLASH_ADDR (FLASH_BASE + 20)
+#define FLASH_OBR (FLASH_BASE + 28)
+#define FLASH_WPR (FLASH_BASE + 32)
 
-
-
-struct dns_data {
-  struct dns_data *next;
-  struct mg_connection *c;
-  uint64_t expire;
-  uint16_t txnid;
-};
-
-static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
-                         struct mg_dns *, bool);
-
-static void mg_dns_free(struct mg_connection *c, struct dns_data *d) {
-  LIST_DELETE(struct dns_data,
-              (struct dns_data **) &c->mgr->active_dns_requests, d);
-  free(d);
+void *mg_flash_start(void) {
+  return (void *) 0x08000000;
 }
-
-void mg_resolve_cancel(struct mg_connection *c) {
-  struct dns_data *tmp, *d = (struct dns_data *) c->mgr->active_dns_requests;
-  for (; d != NULL; d = tmp) {
-    tmp = d->next;
-    if (d->c == c) mg_dns_free(c, d);
+size_t mg_flash_size(void) {
+  return 480 * 1024;  // First 320k is 0-wait
+}
+size_t mg_flash_sector_size(void) {
+  return 4096;
+}
+size_t mg_flash_write_align(void) {
+  return 4;
+}
+int mg_flash_bank(void) {
+  return 0;
+}
+void mg_device_reset(void) {
+  *((volatile uint32_t *) 0xbeef0000) |= 1U << 7;  // NVIC_SystemReset()
+}
+static void flash_unlock(void) {
+  static bool unlocked;
+  if (unlocked == false) {
+    MG_REG(FLASH_KEYR) = 0x45670123;
+    MG_REG(FLASH_KEYR) = 0xcdef89ab;
+    unlocked = true;
   }
 }
+static void flash_wait(void) {
+  while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0;
+}
 
-static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
-                                      char *to, size_t tolen, size_t j,
-                                      int depth) {
-  size_t i = 0;
-  if (tolen > 0 && depth == 0) to[0] = '\0';
-  if (depth > 5) return 0;
-  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
-  while (ofs + i + 1 < len) {
-    size_t n = s[ofs + i];
-    if (n == 0) {
-      i++;
-      break;
-    }
-    if (n & 0xc0) {
-      size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
-      // MG_INFO(("PTR %lx", (unsigned long) ptr));
-      if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
-          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
-        return 0;
-      i += 2;
-      break;
-    }
-    if (ofs + i + n + 1 >= len) return 0;
-    if (j > 0) {
-      if (j < tolen) to[j] = '.';
-      j++;
-    }
-    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
-    j += n;
-    i += n + 1;
-    if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
-    // MG_INFO(("--> [%s]", to));
-  }
-  if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
-  return i;
+bool mg_flash_erase(void *addr) {
+  //MG_INFO(("%p", addr));
+  flash_unlock();
+  flash_wait();
+  MG_REG(FLASH_ADDR) = (uint32_t) addr;
+  MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6);  // PER | STRT;
+  flash_wait();
+  return true;
 }
 
-static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
-                                char *dst, size_t dstlen) {
-  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
+static bool is_page_boundary(const void *addr) {
+  uint32_t val = (uint32_t) addr;
+  return (val & (mg_flash_sector_size() - 1)) == 0;
 }
 
-size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
-                       bool is_question, struct mg_dns_rr *rr) {
-  const uint8_t *s = buf + ofs, *e = &buf[len];
+bool mg_flash_write(void *addr, const void *buf, size_t len) {
+  //MG_INFO(("%p %p %lu", addr, buf, len));
+  //mg_hexdump(buf, len);
+  flash_unlock();
+  const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2];
+  uint16_t *dst = (uint16_t *) addr;
+  MG_REG(FLASH_CTLR) |= MG_BIT(0);  // Set PG
+  //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR)));
+  while (src < end) {
+    if (is_page_boundary(dst)) mg_flash_erase(dst);
+    *dst++ = *src++;
+    flash_wait();
+  }
+  MG_REG(FLASH_CTLR) &= ~MG_BIT(0);  // Clear PG
+  return true;
+}
+#endif
 
-  memset(rr, 0, sizeof(*rr));
-  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
-  if (len > 512) return 0;  //  Too large, we don't expect that
-  if (s >= e) return 0;     //  Overflow
+#ifdef MG_ENABLE_LINES
+#line 1 "src/device_dummy.c"
+#endif
 
-  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
-    return 0;
-  s += rr->nlen + 4;
-  if (s > e) return 0;
-  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
-  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
-  if (is_question) return (size_t) (rr->nlen + 4);
 
-  s += 6;
-  if (s > e) return 0;
-  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
-  if (s + rr->alen > e) return 0;
-  return (size_t) (rr->nlen + rr->alen + 10);
+#if MG_DEVICE == MG_DEVICE_NONE
+void *mg_flash_start(void) {
+  return NULL;
+}
+size_t mg_flash_size(void) {
+  return 0;
+}
+size_t mg_flash_sector_size(void) {
+  return 0;
+}
+size_t mg_flash_write_align(void) {
+  return 0;
+}
+int mg_flash_bank(void) {
+  return 0;
+}
+bool mg_flash_erase(void *location) {
+  (void) location;
+  return false;
+}
+bool mg_flash_swap_bank(void) {
+  return true;
+}
+bool mg_flash_write(void *addr, const void *buf, size_t len) {
+  (void) addr, (void) buf, (void) len;
+  return false;
+}
+void mg_device_reset(void) {
 }
+#endif
 
-bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
-  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
-  struct mg_dns_rr rr;
-  size_t i, n, ofs = sizeof(*h);
-  memset(dm, 0, sizeof(*dm));
+#ifdef MG_ENABLE_LINES
+#line 1 "src/device_flash.c"
+#endif
 
-  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
-  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
-  if (mg_ntohs(h->num_answers) > 10) return 0;   // Sanity
-  dm->txnid = mg_ntohs(h->txnid);
 
-  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
-    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
-    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
-    ofs += n;
+#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \
+    MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
+// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1)
+// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an
+// object, we pad it at the end for alignment.
+//
+// Objects in the flash sector are stored sequentially:
+// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ......
+//
+// In order to get to the next object, read its size, then align up.
+
+// Traverse the list of saved objects
+size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) {
+  size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p;
+  uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
+  if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
+    if (size) *size = (size_t) p32[0];
+    if (key) *key = p32[1];
+    aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
+    if (left < aligned_size) aligned_size = 0;  // Out of bounds, fail
   }
-  for (i = 0; i < mg_ntohs(h->num_answers); i++) {
-    if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
-    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
-    // dm->name));
-    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
-    ofs += n;
-
-    if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
-      dm->addr.is_ip6 = false;
-      memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
-      dm->resolved = true;
-      break;  // Return success
-    } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
-      dm->addr.is_ip6 = true;
-      memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
-      dm->resolved = true;
-      break;  // Return success
+  return aligned_size;
+}
+
+// Return the last sector of Bank 2
+static char *flash_last_sector(void) {
+  size_t ss = mg_flash_sector_size(), size = mg_flash_size();
+  char *base = (char *) mg_flash_start(), *last = base + size - ss;
+  if (mg_flash_bank() == 2) last -= size / 2;
+  return last;
+}
+
+// Find a saved object with a given key
+bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
+  char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL;
+  size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
+  bool ok = false;
+  if (s == NULL) s = flash_last_sector();
+  if (s < base || s >= base + mg_flash_size()) {
+    MG_ERROR(("%p is outsize of flash", sector));
+  } else if (((s - base) % ss) != 0) {
+    MG_ERROR(("%p is not a sector boundary", sector));
+  } else {
+    uint32_t k, scanned = 0;
+    while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
+      // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key));
+      // mg_hexdump(s + ofs, n);
+      if (k == key && sz == len) {
+        res = s + ofs + sizeof(uint32_t) * 2;
+        memcpy(buf, res, len);  // Copy object
+        ok = true;              // Keep scanning for the newer versions of it
+      }
+      ofs += n, scanned++;
     }
+    MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res));
   }
-  return true;
+  return ok;
 }
 
-static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
-                   void *fn_data) {
-  struct dns_data *d, *tmp;
-  if (ev == MG_EV_POLL) {
-    uint64_t now = *(uint64_t *) ev_data;
-    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
-         d = tmp) {
-      tmp = d->next;
-      // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
-      if (now > d->expire) mg_error(d->c, "DNS timeout");
+// For all saved objects in the sector, delete old versions of objects
+static void mg_flash_sector_cleanup(char *sector) {
+  // Buffer all saved objects into an IO buffer (backed by RAM)
+  // erase sector, and re-save them.
+  struct mg_iobuf io = {0, 0, 0, 2048};
+  size_t ss = mg_flash_sector_size();
+  size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
+  uint32_t key;
+  // Traverse all objects
+  MG_DEBUG(("Cleaning up sector %p", sector));
+  while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) {
+    // Delete an old copy of this object in the cache
+    for (size_t o = 0; o < io.len; o += size2 + hs) {
+      uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
+      size2 = *(uint32_t *) (io.buf + o);
+      if (k == key) {
+        mg_iobuf_del(&io, o, size2 + hs);
+        break;
+      }
     }
-  } else if (ev == MG_EV_READ) {
-    struct mg_dns_message dm;
-    int resolved = 0;
-    if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
-      MG_ERROR(("Unexpected DNS response:"));
-      mg_hexdump(c->recv.buf, c->recv.len);
-    } else {
-      // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
-      for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
-           d = tmp) {
-        tmp = d->next;
-        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
-        if (dm.txnid != d->txnid) continue;
-        if (d->c->is_resolving) {
-          if (dm.resolved) {
-            dm.addr.port = d->c->rem.port;  // Save port
-            d->c->rem = dm.addr;            // Copy resolved address
-            MG_DEBUG(
-                ("%lu %s is %I", d->c->id, dm.name, d->c->rem.is_ip6 ? 16 : 4,
-                 d->c->rem.is_ip6 ? &d->c->rem.ip6 : (void *) &d->c->rem.ip));
-            mg_connect_resolved(d->c);
-#if MG_ENABLE_IPV6
-          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
-                     c->mgr->use_dns6 == false) {
-            struct mg_str x = mg_str(dm.name);
-            mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
-#endif
-          } else {
-            mg_error(d->c, "%s DNS lookup failed", dm.name);
+    // And add the new copy
+    mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
+    ofs += n;
+  }
+  // All objects are cached in RAM now
+  if (mg_flash_erase(sector)) {  // Erase sector. If successful,
+    for (ofs = 0; ofs < io.len; ofs += size + hs) {  // Traverse cached objects
+      size = *(uint32_t *) (io.buf + ofs);
+      key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
+      mg_flash_save(sector, key, io.buf + ofs + hs, size);  // Save to flash
+    }
+  }
+  mg_iobuf_free(&io);
+}
+
+// Save an object with a given key - append to the end of an object list
+bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
+  char *base = (char *) mg_flash_start(), *s = (char *) sector;
+  size_t ss = mg_flash_sector_size(), ofs = 0, n;
+  bool ok = false;
+  if (s == NULL) s = flash_last_sector();
+  if (s < base || s >= base + mg_flash_size()) {
+    MG_ERROR(("%p is outsize of flash", sector));
+  } else if (((s - base) % ss) != 0) {
+    MG_ERROR(("%p is not a sector boundary", sector));
+  } else {
+    char ab[mg_flash_write_align()];  // Aligned write block
+    uint32_t hdr[2] = {(uint32_t) len, key};
+    size_t needed = sizeof(hdr) + len;
+    size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
+    while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
+
+    // If there is not enough space left, cleanup sector and re-eval ofs
+    if (ofs + needed_aligned >= ss) {
+      mg_flash_sector_cleanup(s);
+      ofs = 0;
+      while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
+    }
+
+    if (ofs + needed_aligned <= ss) {
+      // Enough space to save this object
+      if (sizeof(ab) < sizeof(hdr)) {
+        // Flash write granularity is 32 bit or less, write with no buffering
+        ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
+        if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
+      } else {
+        // Flash granularity is sizeof(hdr) or more. We need to save in
+        // 3 chunks: initial block, bulk, rest. This is because we have
+        // two memory chunks to write: hdr and buf, on aligned boundaries.
+        n = sizeof(ab) - sizeof(hdr);      // Initial chunk that we write
+        if (n > len) n = len;              // is
+        memset(ab, 0xff, sizeof(ab));      // initialized to all-one
+        memcpy(ab, hdr, sizeof(hdr));      // contains the header (key + size)
+        memcpy(ab + sizeof(hdr), buf, n);  // and an initial part of buf
+        MG_INFO(("saving initial block of %lu", sizeof(ab)));
+        ok = mg_flash_write(s + ofs, ab, sizeof(ab));
+        if (ok && len > n) {
+          size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
+          if (n2 > 0) {
+            MG_INFO(("saving bulk, %lu", n2));
+            ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
+          }
+          if (ok && len > n) {
+            size_t n3 = len - n - n2;
+            if (n3 > sizeof(ab)) n3 = sizeof(ab);
+            memset(ab, 0xff, sizeof(ab));
+            memcpy(ab, (char *) buf + n + n2, n3);
+            MG_INFO(("saving rest, %lu", n3));
+            ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
           }
-        } else {
-          MG_ERROR(("%lu already resolved", d->c->id));
         }
-        mg_dns_free(c, d);
-        resolved = 1;
       }
-    }
-    if (!resolved) MG_ERROR(("stray DNS reply"));
-    c->recv.len = 0;
-  } else if (ev == MG_EV_CLOSE) {
-    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
-         d = tmp) {
-      tmp = d->next;
-      mg_error(d->c, "DNS error");
-      mg_dns_free(c, d);
+      MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
+                s + ofs, key, ok));
+      MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
+    } else {
+      MG_ERROR(("Sector is full"));
     }
   }
-  (void) fn_data;
+  return ok;
+}
+#else
+bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
+  (void) sector, (void) key, (void) buf, (void) len;
+  return false;
+}
+bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
+  (void) sector, (void) key, (void) buf, (void) len;
+  return false;
 }
+#endif
 
-static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
-                        uint16_t txnid, bool ipv6) {
-  struct {
-    struct mg_dns_header header;
-    uint8_t data[256];
-  } pkt;
-  size_t i, n;
-  memset(&pkt, 0, sizeof(pkt));
-  pkt.header.txnid = mg_htons(txnid);
-  pkt.header.flags = mg_htons(0x100);
-  pkt.header.num_questions = mg_htons(1);
-  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
-    if (name->ptr[i] == '.' || i >= name->len) {
-      pkt.data[n] = (uint8_t) (i - n);
-      memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
-      n = i + 1;
-    }
-    if (i >= name->len) break;
+#ifdef MG_ENABLE_LINES
+#line 1 "src/device_imxrt.c"
+#endif
+
+
+
+#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
+
+struct mg_flexspi_lut_seq {
+  uint8_t seqNum;
+  uint8_t seqId;
+  uint16_t reserved;
+};
+
+struct mg_flexspi_mem_config {
+  uint32_t tag;
+  uint32_t version;
+  uint32_t reserved0;
+  uint8_t readSampleClkSrc;
+  uint8_t csHoldTime;
+  uint8_t csSetupTime;
+  uint8_t columnAddressWidth;
+  uint8_t deviceModeCfgEnable;
+  uint8_t deviceModeType;
+  uint16_t waitTimeCfgCommands;
+  struct mg_flexspi_lut_seq deviceModeSeq;
+  uint32_t deviceModeArg;
+  uint8_t configCmdEnable;
+  uint8_t configModeType[3];
+  struct mg_flexspi_lut_seq configCmdSeqs[3];
+  uint32_t reserved1;
+  uint32_t configCmdArgs[3];
+  uint32_t reserved2;
+  uint32_t controllerMiscOption;
+  uint8_t deviceType;
+  uint8_t sflashPadType;
+  uint8_t serialClkFreq;
+  uint8_t lutCustomSeqEnable;
+  uint32_t reserved3[2];
+  uint32_t sflashA1Size;
+  uint32_t sflashA2Size;
+  uint32_t sflashB1Size;
+  uint32_t sflashB2Size;
+  uint32_t csPadSettingOverride;
+  uint32_t sclkPadSettingOverride;
+  uint32_t dataPadSettingOverride;
+  uint32_t dqsPadSettingOverride;
+  uint32_t timeoutInMs;
+  uint32_t commandInterval;
+  uint16_t dataValidTime[2];
+  uint16_t busyOffset;
+  uint16_t busyBitPolarity;
+  uint32_t lookupTable[64];
+  struct mg_flexspi_lut_seq lutCustomSeq[12];
+  uint32_t reserved4[4];
+};
+
+struct mg_flexspi_nor_config {
+  struct mg_flexspi_mem_config memConfig;
+  uint32_t pageSize;
+  uint32_t sectorSize;
+  uint8_t ipcmdSerialClkFreq;
+  uint8_t isUniformBlockSize;
+  uint8_t reserved0[2];
+  uint8_t serialNorType;
+  uint8_t needExitNoCmdMode;
+  uint8_t halfClkForNonReadCmd;
+  uint8_t needRestoreNoCmdMode;
+  uint32_t blockSize;
+  uint32_t reserve2[11];
+};
+
+/* FLEXSPI memory config block related defintions */
+#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL)      // ascii "FCFB" Big Endian
+#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL)  // V1.4.0
+
+#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1)                                      \
+  (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \
+   MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1))
+
+#define MG_CMD_SDR 0x01
+#define MG_CMD_DDR 0x21
+#define MG_DUMMY_SDR 0x0C
+#define MG_DUMMY_DDR 0x2C
+#define MG_RADDR_SDR 0x02
+#define MG_RADDR_DDR 0x22
+#define MG_READ_SDR 0x09
+#define MG_READ_DDR 0x29
+#define MG_WRITE_SDR 0x08
+#define MG_WRITE_DDR 0x28
+#define MG_STOP 0
+
+#define MG_FLEXSPI_1PAD 0
+#define MG_FLEXSPI_2PAD 1
+#define MG_FLEXSPI_4PAD 2
+#define MG_FLEXSPI_8PAD 3
+
+#define MG_FLEXSPI_QSPI_LUT                                                                        \
+  {                                                                                                \
+    [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD,     \
+                             0x18),                                                                \
+    [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD,    \
+                             0x04),                                                                \
+    [4 * 1 + 0] =                                                                                  \
+        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \
+    [4 * 3 + 0] =                                                                                  \
+        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0),      \
+    [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR,              \
+                                     MG_FLEXSPI_1PAD, 0x18),                                       \
+    [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR,              \
+                                     MG_FLEXSPI_1PAD, 0x18),                                       \
+    [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR,              \
+                                     MG_FLEXSPI_1PAD, 0x18),                                       \
+    [4 * 9 + 1] =                                                                                  \
+        MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0),    \
+    [4 * 11 + 0] =                                                                                 \
+        MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0),      \
   }
-  memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
-  n += 5;
-  if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
-  // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
-  // n += 6;
-  return mg_send(c, &pkt, sizeof(pkt.header) + n);
+
+#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU)
+#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U)
+#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U)
+#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U)
+#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U)
+#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U)
+
+#define FLEXSPI_NOR_INSTANCE 0
+
+#if MG_DEVICE == MG_DEVICE_RT1020
+struct mg_flexspi_nor_driver_interface {
+  uint32_t version;
+  int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
+  int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr,
+                 const uint32_t *src);
+  uint32_t reserved;
+  int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start,
+               uint32_t lengthInBytes);
+  uint32_t reserved2;
+  int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase,
+                    uint32_t seqNumber);
+  int (*xfer)(uint32_t instance, char *xfer);
+  void (*clear_cache)(uint32_t instance);
+};
+#elif MG_DEVICE == MG_DEVICE_RT1060
+struct mg_flexspi_nor_driver_interface {
+  uint32_t version;
+  int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
+  int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr,
+                 const uint32_t *src);
+  int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config);
+  int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start,
+               uint32_t lengthInBytes);
+  int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr,
+              uint32_t lengthInBytes);
+  void (*clear_cache)(uint32_t instance);
+  int (*xfer)(uint32_t instance, char *xfer);
+  int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase,
+                    uint32_t seqNumber);
+  int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option);
+};
+#endif
+
+#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \
+                          (*(uint32_t*)0x0020001c + 16)))
+
+static bool s_flash_irq_disabled;
+
+MG_IRAM void *mg_flash_start(void) {
+  return (void *) 0x60000000;
+}
+MG_IRAM size_t mg_flash_size(void) {
+  return 8 * 1024 * 1024;
+}
+MG_IRAM size_t mg_flash_sector_size(void) {
+  return 4 * 1024;  // 4k
+}
+MG_IRAM size_t mg_flash_write_align(void) {
+  return 256;
+}
+MG_IRAM int mg_flash_bank(void) {
+  return 0;
 }
 
-static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
-                         struct mg_dns *dnsc, bool ipv6) {
-  struct dns_data *d = NULL;
-  if (dnsc->url == NULL) {
-    mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
-  } else if (dnsc->c == NULL) {
-    dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
-    if (dnsc->c != NULL) {
-      dnsc->c->pfn = dns_cb;
-      // dnsc->c->is_hexdumping = 1;
-    }
+MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
+  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
+  volatile char *p = (char *) dst;
+  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
+}
+
+// Note: the get_config function below works both for RT1020 and 1060
+#if MG_DEVICE == MG_DEVICE_RT1020
+MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) {
+  struct mg_flexspi_nor_config default_config = {
+      .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
+                    .version = MG_FLEXSPI_CFG_BLK_VERSION,
+                    .readSampleClkSrc = 1,  // ReadSampleClk_LoopbackFromDqsPad
+                    .csHoldTime = 3,
+                    .csSetupTime = 3,
+                    .controllerMiscOption = MG_BIT(4),
+                    .deviceType = 1,  // serial NOR
+                    .sflashPadType = 4,
+                    .serialClkFreq = 7,  // 133MHz
+                    .sflashA1Size = 8 * 1024 * 1024,
+                    .lookupTable = MG_FLEXSPI_QSPI_LUT},
+      .pageSize = 256,
+      .sectorSize = 4 * 1024,
+      .ipcmdSerialClkFreq = 1,
+      .blockSize = 64 * 1024,
+      .isUniformBlockSize = false};
+
+  *config = default_config;
+  return 0;
+}
+#else
+MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) {
+  uint32_t options[] = {0xc0000000, 0x00};
+
+  MG_ARM_DISABLE_IRQ();
+  uint32_t status =
+      flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options);
+  if (!s_flash_irq_disabled) {
+    MG_ARM_ENABLE_IRQ();
   }
-  if (dnsc->c == NULL) {
-    mg_error(c, "resolver");
-  } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
-    mg_error(c, "resolve OOM");
-  } else {
-    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
-    d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
-    d->next = (struct dns_data *) c->mgr->active_dns_requests;
-    c->mgr->active_dns_requests = d;
-    d->expire = mg_millis() + (uint64_t) ms;
-    d->c = c;
-    c->is_resolving = 1;
-    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
-                name->ptr, &dnsc->url, d->txnid));
-    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
-      mg_error(dnsc->c, "DNS send");
-    }
+  if (status) {
+    MG_ERROR(("Failed to extract flash configuration: status %u", status));
   }
+  return status;
 }
+#endif
 
-void mg_resolve(struct mg_connection *c, const char *url) {
-  struct mg_str host = mg_url_host(url);
-  c->rem.port = mg_htons(mg_url_port(url));
-  if (mg_aton(host, &c->rem)) {
-    // host is an IP address, do not fire name resolution
-    mg_connect_resolved(c);
-  } else {
-    // host is not an IP, send DNS resolution request
-    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
-    mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
+MG_IRAM bool mg_flash_erase(void *addr) {
+  struct mg_flexspi_nor_config config;
+  if (flexspi_nor_get_config(&config) != 0) {
+    return false;
+  }
+  if (flash_page_start(addr) == false) {
+    MG_ERROR(("%p is not on a sector boundary", addr));
+    return false;
+  }
+
+  void *dst = (void *)((char *) addr - (char *) mg_flash_start());
+
+  // Note: Interrupts must be disabled before any call to the ROM API on RT1020
+  // and 1060
+  MG_ARM_DISABLE_IRQ();
+  bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst,
+                                mg_flash_sector_size()) == 0);
+  if (!s_flash_irq_disabled) {
+    MG_ARM_ENABLE_IRQ();  // Reenable them after the call
   }
+  MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail"));
+  return ok;
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/event.c"
-#endif
+MG_IRAM bool mg_flash_swap_bank(void) {
+  return true;
+}
+
+static inline void spin(volatile uint32_t count) {
+  while (count--) (void) 0;
+}
+
+static inline void flash_wait(void) {
+  while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0)
+    spin(1);
+}
 
+MG_IRAM static void *flash_code_location(void) {
+  return (void *) ((char *) mg_flash_start() + 0x2000);
+}
 
+MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
+  struct mg_flexspi_nor_config config;
+  if (flexspi_nor_get_config(&config) != 0) {
+    return false;
+  }
+  if ((len % mg_flash_write_align()) != 0) {
+    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
+    return false;
+  }
 
+  if ((char *) addr < (char *) mg_flash_start()) {
+    MG_ERROR(("Invalid flash write address: %p", addr));
+    return false;
+  }
 
+  uint32_t *dst = (uint32_t *) addr;
+  uint32_t *src = (uint32_t *) buf;
+  uint32_t *end = (uint32_t *) ((char *) buf + len);
+  bool ok = true;
+
+  // Note: If we overwrite the flash irq section of the image, we must also
+  // make sure interrupts are disabled and are not reenabled until we write
+  // this sector with another irq table.
+  if ((char *) addr == (char *) flash_code_location()) {
+    s_flash_irq_disabled = true;
+    MG_ARM_DISABLE_IRQ();
+  }
 
-void mg_call(struct mg_connection *c, int ev, void *ev_data) {
-  // Run user-defined handler first, in order to give it an ability
-  // to intercept processing (e.g. clean input buffer) before the
-  // protocol handler kicks in
-  if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
-  if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
+  while (ok && src < end) {
+    if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
+      break;
+    }
+    uint32_t status;
+    uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start();
+    if ((char *) buf >= (char *) mg_flash_start()) {
+      // If we copy from FLASH to FLASH, then we first need to copy the source
+      // to RAM
+      size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t);
+      uint32_t tmp[tmp_buf_size];
+
+      for (size_t i = 0; i < tmp_buf_size; i++) {
+        flash_wait();
+        tmp[i] = src[i];
+      }
+      MG_ARM_DISABLE_IRQ();
+      status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
+                                    (uint32_t) dst_ofs, tmp);
+    } else {
+      MG_ARM_DISABLE_IRQ();
+      status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config,
+                                    (uint32_t) dst_ofs, src);
+    }
+    if (!s_flash_irq_disabled) {
+      MG_ARM_ENABLE_IRQ();
+    }
+    src = (uint32_t *) ((char *) src + mg_flash_write_align());
+    dst = (uint32_t *) ((char *) dst + mg_flash_write_align());
+    if (status != 0) {
+      ok = false;
+    }
+  }
+  MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail"));
+  return ok;
 }
 
-void mg_error(struct mg_connection *c, const char *fmt, ...) {
-  char buf[64];
-  va_list ap;
-  va_start(ap, fmt);
-  mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
-  va_end(ap);
-  MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
-  c->is_closing = 1;             // Set is_closing before sending MG_EV_CALL
-  mg_call(c, MG_EV_ERROR, buf);  // Let user handler to override it
+MG_IRAM void mg_device_reset(void) {
+  MG_DEBUG(("Resetting device..."));
+  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
 }
 
+#endif
+
 #ifdef MG_ENABLE_LINES
-#line 1 "src/fmt.c"
+#line 1 "src/device_stm32h5.c"
 #endif
 
 
 
+#if MG_DEVICE == MG_DEVICE_STM32H5
 
-static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
-  struct mg_iobuf *io = (struct mg_iobuf *) param;
-  if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
-  if (io->len + 2 <= io->size) {
-    io->buf[io->len++] = (uint8_t) ch;
-    io->buf[io->len] = 0;
-  } else if (io->len < io->size) {
-    io->buf[io->len++] = 0;  // Guarantee to 0-terminate
-  }
-}
+#define FLASH_BASE 0x40022000          // Base address of the flash controller
+#define FLASH_KEYR (FLASH_BASE + 0x4)  // See RM0481 7.11
+#define FLASH_OPTKEYR (FLASH_BASE + 0xc)
+#define FLASH_OPTCR (FLASH_BASE + 0x1c)
+#define FLASH_NSSR (FLASH_BASE + 0x20)
+#define FLASH_NSCR (FLASH_BASE + 0x28)
+#define FLASH_NSCCR (FLASH_BASE + 0x30)
+#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50)
+#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54)
 
-static void mg_putchar_iobuf_static(char ch, void *param) {
-  mg_pfn_iobuf_private(ch, param, false);
+void *mg_flash_start(void) {
+  return (void *) 0x08000000;
 }
-
-void mg_pfn_iobuf(char ch, void *param) {
-  mg_pfn_iobuf_private(ch, param, true);
+size_t mg_flash_size(void) {
+  return 2 * 1024 * 1024;  // 2Mb
 }
-
-size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
-  struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
-  size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
-  if (n < len) buf[n] = '\0';
-  return n;
+size_t mg_flash_sector_size(void) {
+  return 8 * 1024;  // 8k
 }
-
-size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
-  va_list ap;
-  size_t n;
-  va_start(ap, fmt);
-  n = mg_vsnprintf(buf, len, fmt, &ap);
-  va_end(ap);
-  return n;
+size_t mg_flash_write_align(void) {
+  return 16;  // 128 bit
 }
-
-char *mg_vmprintf(const char *fmt, va_list *ap) {
-  struct mg_iobuf io = {0, 0, 0, 256};
-  mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
-  return (char *) io.buf;
+int mg_flash_bank(void) {
+  return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
 }
 
-char *mg_mprintf(const char *fmt, ...) {
-  char *s;
-  va_list ap;
-  va_start(ap, fmt);
-  s = mg_vmprintf(fmt, &ap);
-  va_end(ap);
-  return s;
+static void flash_unlock(void) {
+  static bool unlocked = false;
+  if (unlocked == false) {
+    MG_REG(FLASH_KEYR) = 0x45670123;
+    MG_REG(FLASH_KEYR) = 0Xcdef89ab;
+    MG_REG(FLASH_OPTKEYR) = 0x08192a3b;
+    MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f;
+    unlocked = true;
+  }
 }
 
-size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
-  size_t len = 0;
-  va_list ap;
-  va_start(ap, fmt);
-  len = mg_vxprintf(out, ptr, fmt, &ap);
-  va_end(ap);
-  return len;
+static int flash_page_start(volatile uint32_t *dst) {
+  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
+  volatile char *p = (char *) dst;
+  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
 }
 
-static bool is_digit(int c) {
-  return c >= '0' && c <= '9';
+static bool flash_is_err(void) {
+  return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17);  // RM0481 7.11.9
 }
 
-static int addexp(char *buf, int e, int sign) {
-  int n = 0;
-  buf[n++] = 'e';
-  buf[n++] = (char) sign;
-  if (e > 400) return 0;
-  if (e < 10) buf[n++] = '0';
-  if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
-  if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
-  buf[n++] = (char) (e + '0');
-  return n;
+static void flash_wait(void) {
+  while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) &&
+         (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) {
+    (void) 0;
+  }
 }
 
-static int xisinf(double x) {
-  union {
-    double f;
-    uint64_t u;
-  } ieee754 = {x};
-  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
-         ((unsigned) ieee754.u == 0);
+static void flash_clear_err(void) {
+  flash_wait();                                    // Wait until ready
+  MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U);  // Clear all errors
 }
 
-static int xisnan(double x) {
-  union {
-    double f;
-    uint64_t u;
-  } ieee754 = {x};
-  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
-             ((unsigned) ieee754.u != 0) >
-         0x7ff00000;
+static bool flash_bank_is_swapped(void) {
+  return MG_REG(FLASH_OPTCR) & MG_BIT(31);  // RM0481 7.11.8
 }
 
-static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width) {
-  char buf[40];
-  int i, s = 0, n = 0, e = 0;
-  double t, mul, saved;
-  if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
-  if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf");
-  if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
-  if (d < 0.0) d = -d, buf[s++] = '-';
-
-  // Round
-  saved = d;
-  mul = 1.0;
-  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
-  while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
-  for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
-  d += t;
-  // Calculate exponent, and 'mul' for scientific representation
-  mul = 1.0;
-  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
-  while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
-  // printf(" --> %g %d %g %g\n", saved, e, t, mul);
-
-  if (e >= width) {
-    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
-    // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
-    n += addexp(buf + s + n, e, '+');
-    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
-  } else if (e <= -width) {
-    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
-    // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
-    n += addexp(buf + s + n, -e, '-');
-    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
+bool mg_flash_erase(void *location) {
+  bool ok = false;
+  if (flash_page_start(location) == false) {
+    MG_ERROR(("%p is not on a sector boundary"));
   } else {
-    for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
-      int ch = (int) (d / t);
-      if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
-      d -= ch * t;
-      t /= 10.0;
-    }
-    // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
-    if (n == 0) buf[s++] = '0';
-    while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
-    if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
-    // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
-    for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
-      int ch = (int) (d / t);
-      buf[s + n++] = (char) (ch + '0');
-      d -= ch * t;
-      t /= 10.0;
+    uintptr_t diff = (char *) location - (char *) mg_flash_start();
+    uint32_t sector = diff / mg_flash_sector_size();
+    uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value
+    flash_unlock();
+    flash_clear_err();
+    MG_REG(FLASH_NSCR) = 0;
+    if ((sector < 128 && flash_bank_is_swapped()) ||
+        (sector > 127 && !flash_bank_is_swapped())) {
+      MG_REG(FLASH_NSCR) |= MG_BIT(31);  // Set FLASH_CR_BKSEL
     }
+    if (sector > 127) sector -= 128;
+    MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6);  // Erase | sector_num
+    MG_REG(FLASH_NSCR) |= MG_BIT(5);                  // Start erasing
+    flash_wait();
+    ok = !flash_is_err();
+    MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location,
+              ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR)));
+    // mg_hexdump(location, 32);
+    MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR
   }
-  while (n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeros
-  if (n > 0 && buf[s + n - 1] == '.') n--;     // Trim trailing dot
-  n += s;
-  if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
-  buf[n] = '\0';
-  return mg_snprintf(dst, dstlen, "%s", buf);
+  return ok;
 }
 
-static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) {
-  const char *letters = "0123456789abcdef";
-  uint64_t v = (uint64_t) val;
-  size_t s = 0, n, i;
-  if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
-  // This loop prints a number in reverse order. I guess this is because we
-  // write numbers from right to left: least significant digit comes last.
-  // Maybe because we use Arabic numbers, and Arabs write RTL?
-  if (is_hex) {
-    for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
-  } else {
-    for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
+bool mg_flash_swap_bank(void) {
+  uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
+  flash_unlock();
+  flash_clear_err();
+  // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
+  MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
+  // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
+  MG_REG(FLASH_OPTCR) |= MG_BIT(1);  // OPTSTART
+  while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
+  return true;
+}
+
+bool mg_flash_write(void *addr, const void *buf, size_t len) {
+  if ((len % mg_flash_write_align()) != 0) {
+    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
+    return false;
   }
-  // Reverse a string
-  for (i = 0; i < n / 2; i++) {
-    char t = buf[s + i];
-    buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
+  uint32_t *dst = (uint32_t *) addr;
+  uint32_t *src = (uint32_t *) buf;
+  uint32_t *end = (uint32_t *) ((char *) buf + len);
+  bool ok = true;
+  flash_unlock();
+  flash_clear_err();
+  MG_ARM_DISABLE_IRQ();
+  // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr));
+  MG_REG(FLASH_NSCR) = MG_BIT(1);  // Set programming flag
+  while (ok && src < end) {
+    if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
+    *(volatile uint32_t *) dst++ = *src++;
+    flash_wait();
+    if (flash_is_err()) ok = false;
   }
-  if (val == 0) buf[n++] = '0';  // Handle special case
-  return n + s;
+  MG_ARM_ENABLE_IRQ();
+  MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
+            flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR),
+            MG_REG(FLASH_NSSR)));
+  MG_REG(FLASH_NSCR) = 0;  // Clear flags
+  return ok;
 }
 
-static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
-                   size_t len) {
-  size_t i = 0;
-  while (i < len && buf[i] != '\0') out(buf[i++], ptr);
-  return i;
+void mg_device_reset(void) {
+  // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
+  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
 }
+#endif
 
-static char mg_esc(int c, bool esc) {
-  const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
-  for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
-    if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
+#ifdef MG_ENABLE_LINES
+#line 1 "src/device_stm32h7.c"
+#endif
+
+
+
+#if MG_DEVICE == MG_DEVICE_STM32H7
+
+#define FLASH_BASE1 0x52002000  // Base address for bank1
+#define FLASH_BASE2 0x52002100  // Base address for bank2
+#define FLASH_KEYR 0x04         // See RM0433 4.9.2
+#define FLASH_OPTKEYR 0x08
+#define FLASH_OPTCR 0x18
+#define FLASH_SR 0x10
+#define FLASH_CR 0x0c
+#define FLASH_CCR 0x14
+#define FLASH_OPTSR_CUR 0x1c
+#define FLASH_OPTSR_PRG 0x20
+#define FLASH_SIZE_REG 0x1ff1e880
+
+MG_IRAM void *mg_flash_start(void) {
+  return (void *) 0x08000000;
+}
+MG_IRAM size_t mg_flash_size(void) {
+  return MG_REG(FLASH_SIZE_REG) * 1024;
+}
+MG_IRAM size_t mg_flash_sector_size(void) {
+  return 128 * 1024;  // 128k
+}
+MG_IRAM size_t mg_flash_write_align(void) {
+  return 32;  // 256 bit
+}
+MG_IRAM int mg_flash_bank(void) {
+  if (mg_flash_size() < 2 * 1024 * 1024) return 0;  // No dual bank support
+  return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1;
+}
+
+MG_IRAM static void flash_unlock(void) {
+  static bool unlocked = false;
+  if (unlocked == false) {
+    MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123;
+    MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab;
+    if (mg_flash_bank() > 0) {
+      MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123;
+      MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab;
+    }
+    MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b;  // opt reg is "shared"
+    MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f;  // thus unlock once
+    unlocked = true;
   }
-  return 0;
 }
 
-static char mg_escape(int c) {
-  return mg_esc(c, true);
+MG_IRAM static bool flash_page_start(volatile uint32_t *dst) {
+  char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
+  volatile char *p = (char *) dst;
+  return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0;
 }
 
-static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
-                   size_t len) {
-  size_t i = 0, extra = 0;
-  for (i = 0; i < len && buf[i] != '\0'; i++) {
-    char c = mg_escape(buf[i]);
-    if (c) {
-      out('\\', ptr), out(c, ptr), extra++;
-    } else {
-      out(buf[i], ptr);
-    }
-  }
-  return i + extra;
+MG_IRAM static bool flash_is_err(uint32_t bank) {
+  return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17);  // RM0433 4.9.5
 }
 
-static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf,
-                   size_t len) {
-  size_t n = 2;
-  out('"', ptr);
-  n += qcpy(out, ptr, buf, len);
-  out('"', ptr);
-  return n;
+MG_IRAM static void flash_wait(uint32_t bank) {
+  while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0;
 }
 
-static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t *buf,
-                   size_t len) {
-  size_t i, n = 0;
-  const char *t =
-      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-  out('"', ptr), n++;
-  for (i = 0; i < len; i += 3) {
-    uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
-            c3 = i + 2 < len ? buf[i + 2] : 0;
-    char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
-    if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
-    if (i + 2 < len) tmp[3] = t[c3 & 63];
-    n += scpy(out, ptr, tmp, sizeof(tmp));
-  }
-  out('"', ptr), n++;
-  return n;
+MG_IRAM static void flash_clear_err(uint32_t bank) {
+  flash_wait(bank);                                      // Wait until ready
+  MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U);  // Clear all errors
 }
 
-size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
-                   va_list *ap) {
-  size_t i = 0, n = 0;
-  while (fmt[i] != '\0') {
-    if (fmt[i] == '%') {
-      size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */;
-      char pad = ' ', minus = 0, c = fmt[++i];
-      if (c == '#') x++, c = fmt[++i];
-      if (c == '-') minus++, c = fmt[++i];
-      if (c == '0') pad = '0', c = fmt[++i];
-      while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
-      if (c == '.') {
-        c = fmt[++i];
-        if (c == '*') {
-          pr = (size_t) va_arg(*ap, int);
-          c = fmt[++i];
-        } else {
-          pr = 0;
-          while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
-        }
-      }
-      while (c == 'h') c = fmt[++i];  // Treat h and hh as int
-      if (c == 'l') {
-        is_long++, c = fmt[++i];
-        if (c == 'l') is_long++, c = fmt[++i];
-      }
-      if (c == 'p') x = 1, is_long = 1;
-      if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
-          c == 'g' || c == 'f') {
-        bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
-        char tmp[40];
-        size_t xl = x ? 2 : 0;
-        if (c == 'g' || c == 'f') {
-          double v = va_arg(*ap, double);
-          if (pr == ~0U) pr = 6;
-          k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
-        } else if (is_long == 2) {
-          int64_t v = va_arg(*ap, int64_t);
-          k = mg_lld(tmp, v, s, h);
-        } else if (is_long == 1) {
-          long v = va_arg(*ap, long);
-          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
-        } else {
-          int v = va_arg(*ap, int);
-          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
-        }
-        for (j = 0; j < xl && w > 0; j++) w--;
-        for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
-          n += scpy(out, param, &pad, 1);
-        n += scpy(out, param, (char *) "0x", xl);
-        for (j = 0; pad == '0' && k < w && j + k < w; j++)
-          n += scpy(out, param, &pad, 1);
-        n += scpy(out, param, tmp, k);
-        for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
-          n += scpy(out, param, &pad, 1);
-      } else if (c == 'M') {
-        mg_pm_t f = va_arg(*ap, mg_pm_t);
-        n += f(out, param, ap);
-      } else if (c == 'c') {
-        int ch = va_arg(*ap, int);
-        out((char) ch, param);
-        n++;
-      } else if (c == 'H') {
-        // Print hex-encoded double-quoted string
-        size_t bl = (size_t) va_arg(*ap, int);
-        uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
-        const char *hex = "0123456789abcdef";
-        n += scpy(out, param, (char *) &dquote, 1);
-        for (j = 0; j < bl; j++) {
-          n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
-          n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
-        }
-        n += scpy(out, param, (char *) &dquote, 1);
-      } else if (c == 'I') {
-        // Print IPv4 or IPv6 address
-        size_t len = (size_t) va_arg(*ap, int);  // Length 16 means IPv6 address
-        uint8_t *buf = va_arg(*ap, uint8_t *);   // Pointer to the IP address
-        if (len == 6) {
-          uint16_t *p = (uint16_t *) buf;
-          n += mg_xprintf(out, param, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]),
-                          mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]),
-                          mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]),
-                          mg_htons(p[7]));
-        } else {
-          n += mg_xprintf(out, param, "%d.%d.%d.%d", (int) buf[0], (int) buf[1],
-                          (int) buf[2], (int) buf[3]);
-        }
-      } else if (c == 'A') {
-        // Print hardware addresses (currently Ethernet MAC)
-        uint8_t *buf = va_arg(*ap, uint8_t *);  // Pointer to the hw address
-        n += mg_xprintf(out, param, "%02x:%02x:%02x:%02x:%02x:%02x",
-                        (int) buf[0], (int) buf[1], (int) buf[2], (int) buf[3],
-                        (int) buf[4], (int) buf[5]);
-      } else if (c == 'V') {
-        // Print base64-encoded double-quoted string
-        size_t len = (size_t) va_arg(*ap, int);
-        uint8_t *buf = va_arg(*ap, uint8_t *);
-        n += bcpy(out, param, buf, len);
-      } else if (c == 's' || c == 'Q' || c == 'q') {
-        char *p = va_arg(*ap, char *);
-        size_t (*f)(void (*)(char, void *), void *, char *, size_t) = scpy;
-        if (c == 'Q') f = Qcpy;
-        if (c == 'q') f = qcpy;
-        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
-        for (j = 0; !minus && pr < w && j + pr < w; j++)
-          n += f(out, param, &pad, 1);
-        n += f(out, param, p, pr);
-        for (j = 0; minus && pr < w && j + pr < w; j++)
-          n += f(out, param, &pad, 1);
-      } else if (c == '%') {
-        out('%', param);
-        n++;
-      } else {
-        out('%', param);
-        out(c, param);
-        n += 2;
-      }
-      i++;
-    } else {
-      out(fmt[i], param), n++, i++;
-    }
-  }
-  return n;
+MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) {
+  return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31);  // RM0433 4.9.7
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/fs.c"
-#endif
-
-
-
-struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) {
-  struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
-  if (fd != NULL) {
-    fd->fd = fs->op(path, flags);
-    fd->fs = fs;
-    if (fd->fd == NULL) {
-      free(fd);
-      fd = NULL;
-    }
-  }
-  return fd;
+// Figure out flash bank based on the address
+MG_IRAM static uint32_t flash_bank(void *addr) {
+  size_t ofs = (char *) addr - (char *) mg_flash_start();
+  if (mg_flash_bank() == 0) return FLASH_BASE1;
+  return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2;
 }
 
-void mg_fs_close(struct mg_fd *fd) {
-  if (fd != NULL) {
-    fd->fs->cl(fd->fd);
-    free(fd);
+MG_IRAM bool mg_flash_erase(void *addr) {
+  bool ok = false;
+  if (flash_page_start(addr) == false) {
+    MG_ERROR(("%p is not on a sector boundary", addr));
+  } else {
+    uintptr_t diff = (char *) addr - (char *) mg_flash_start();
+    uint32_t sector = diff / mg_flash_sector_size();
+    uint32_t bank = flash_bank(addr);
+    uint32_t saved_cr = MG_REG(bank + FLASH_CR);  // Save CR value
+
+    flash_unlock();
+    if (sector > 7) sector -= 8;
+
+    flash_clear_err(bank);
+    MG_REG(bank + FLASH_CR) = MG_BIT(5);             // 32-bit write parallelism
+    MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U;  // Sector to erase
+    MG_REG(bank + FLASH_CR) |= MG_BIT(2);            // Sector erase bit
+    MG_REG(bank + FLASH_CR) |= MG_BIT(7);            // Start erasing
+    ok = !flash_is_err(bank);
+    MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
+              ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
+              MG_REG(bank + FLASH_SR)));
+    MG_REG(bank + FLASH_CR) = saved_cr;  // Restore CR
   }
+  return ok;
 }
 
-char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) {
-  struct mg_fd *fd;
-  char *data = NULL;
-  size_t size = 0;
-  fs->st(path, &size, NULL);
-  if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
-    data = (char *) calloc(1, size + 1);
-    if (data != NULL) {
-      if (fs->rd(fd->fd, data, size) != size) {
-        free(data);
-        data = NULL;
-      } else {
-        data[size] = '\0';
-        if (sizep != NULL) *sizep = size;
-      }
-    }
-    mg_fs_close(fd);
-  }
-  return data;
+MG_IRAM bool mg_flash_swap_bank(void) {
+  if (mg_flash_bank() == 0) return true;
+  uint32_t bank = FLASH_BASE1;
+  uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
+  flash_unlock();
+  flash_clear_err(bank);
+  // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
+  MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
+  // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
+  MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1);  // OPTSTART
+  while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
+  return true;
 }
 
-bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
-                   size_t len) {
-  bool result = false;
-  struct mg_fd *fd;
-  char tmp[MG_PATH_MAX];
-  mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
-  if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
-    result = fs->wr(fd->fd, buf, len) == len;
-    mg_fs_close(fd);
-    if (result) {
-      fs->rm(path);
-      fs->mv(tmp, path);
-    } else {
-      fs->rm(tmp);
-    }
+MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
+  if ((len % mg_flash_write_align()) != 0) {
+    MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
+    return false;
   }
-  return result;
+  uint32_t bank = flash_bank(addr);
+  uint32_t *dst = (uint32_t *) addr;
+  uint32_t *src = (uint32_t *) buf;
+  uint32_t *end = (uint32_t *) ((char *) buf + len);
+  bool ok = true;
+  flash_unlock();
+  flash_clear_err(bank);
+  MG_REG(bank + FLASH_CR) = MG_BIT(1);   // Set programming flag
+  MG_REG(bank + FLASH_CR) |= MG_BIT(5);  // 32-bit write parallelism
+  MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
+  MG_ARM_DISABLE_IRQ();
+  while (ok && src < end) {
+    if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
+    *(volatile uint32_t *) dst++ = *src++;
+    flash_wait(bank);
+    if (flash_is_err(bank)) ok = false;
+  }
+  MG_ARM_ENABLE_IRQ();
+  MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
+            ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
+            MG_REG(bank + FLASH_SR)));
+  MG_REG(bank + FLASH_CR) &= ~MG_BIT(1);  // Clear programming flag
+  return ok;
 }
 
-bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
-  va_list ap;
-  char *data;
-  bool result = false;
-  va_start(ap, fmt);
-  data = mg_vmprintf(fmt, &ap);
-  va_end(ap);
-  result = mg_file_write(fs, path, data, strlen(data));
-  free(data);
-  return result;
+MG_IRAM void mg_device_reset(void) {
+  // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk);
+  *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
 }
+#endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/fs_fat.c"
+#line 1 "src/dns.c"
 #endif
 
 
 
-#if MG_ENABLE_FATFS
-#include <ff.h>
 
-static int mg_days_from_epoch(int y, int m, int d) {
-  y -= m <= 2;
-  int era = y / 400;
-  int yoe = y - era * 400;
-  int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
-  int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
-  return era * 146097 + doe - 719468;
-}
 
-static time_t mg_timegm(const struct tm *t) {
-  int year = t->tm_year + 1900;
-  int month = t->tm_mon;  // 0-11
-  if (month > 11) {
-    year += month / 12;
-    month %= 12;
-  } else if (month < 0) {
-    int years_diff = (11 - month) / 12;
-    year -= years_diff;
-    month += 12 * years_diff;
-  }
-  int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
-  return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
-}
 
-static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
-  struct tm tm;
-  memset(&tm, 0, sizeof(struct tm));
-  tm.tm_sec = (ftime << 1) & 0x3e;
-  tm.tm_min = ((ftime >> 5) & 0x3f);
-  tm.tm_hour = ((ftime >> 11) & 0x1f);
-  tm.tm_mday = (fdate & 0x1f);
-  tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
-  tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
-  return mg_timegm(&tm);
-}
 
-static int ff_stat(const char *path, size_t *size, time_t *mtime) {
-  FILINFO fi;
-  if (path[0] == '\0') {
-    if (size) *size = 0;
-    if (mtime) *mtime = 0;
-    return MG_FS_DIR;
-  } else if (f_stat(path, &fi) == 0) {
-    if (size) *size = (size_t) fi.fsize;
-    if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
-    return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0);
-  } else {
-    return 0;
-  }
-}
 
-static void ff_list(const char *dir, void (*fn)(const char *, void *),
-                    void *userdata) {
-  DIR d;
-  FILINFO fi;
-  if (f_opendir(&d, dir) == FR_OK) {
-    while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
-      if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
-      fn(fi.fname, userdata);
-    }
-    f_closedir(&d);
-  }
-}
+struct dns_data {
+  struct dns_data *next;
+  struct mg_connection *c;
+  uint64_t expire;
+  uint16_t txnid;
+};
 
-static void *ff_open(const char *path, int flags) {
-  FIL f;
-  unsigned char mode = FA_READ;
-  if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
-  if (f_open(&f, path, mode) == 0) {
-    FIL *fp = calloc(1, sizeof(*fp));
-    memcpy(fp, &f, sizeof(*fp));
-    return fp;
-  } else {
-    return NULL;
-  }
+static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
+                         struct mg_dns *, bool);
+
+static void mg_dns_free(struct dns_data **head, struct dns_data *d) {
+  LIST_DELETE(struct dns_data, head, d);
+  free(d);
 }
 
-static void ff_close(void *fp) {
-  if (fp != NULL) {
-    f_close((FIL *) fp);
-    free(fp);
+void mg_resolve_cancel(struct mg_connection *c) {
+  struct dns_data *tmp, *d;
+  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
+  for (d = *head; d != NULL; d = tmp) {
+    tmp = d->next;
+    if (d->c == c) mg_dns_free(head, d);
   }
 }
 
-static size_t ff_read(void *fp, void *buf, size_t len) {
-  UINT n = 0, misalign = ((size_t) buf) & 3;
-  if (misalign) {
-    char aligned[4];
-    f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
-    memcpy(buf, aligned, n);
-  } else {
-    f_read((FIL *) fp, buf, len, &n);
+static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
+                                      char *to, size_t tolen, size_t j,
+                                      int depth) {
+  size_t i = 0;
+  if (tolen > 0 && depth == 0) to[0] = '\0';
+  if (depth > 5) return 0;
+  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
+  while (ofs + i + 1 < len) {
+    size_t n = s[ofs + i];
+    if (n == 0) {
+      i++;
+      break;
+    }
+    if (n & 0xc0) {
+      size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
+      // MG_INFO(("PTR %lx", (unsigned long) ptr));
+      if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
+          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
+        return 0;
+      i += 2;
+      break;
+    }
+    if (ofs + i + n + 1 >= len) return 0;
+    if (j > 0) {
+      if (j < tolen) to[j] = '.';
+      j++;
+    }
+    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
+    j += n;
+    i += n + 1;
+    if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
+    // MG_INFO(("--> [%s]", to));
   }
-  return n;
+  if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
+  return i;
 }
 
-static size_t ff_write(void *fp, const void *buf, size_t len) {
-  UINT n = 0;
-  return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
+static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
+                                char *dst, size_t dstlen) {
+  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
 }
 
-static size_t ff_seek(void *fp, size_t offset) {
-  f_lseek((FIL *) fp, offset);
-  return offset;
+size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
+                       bool is_question, struct mg_dns_rr *rr) {
+  const uint8_t *s = buf + ofs, *e = &buf[len];
+
+  memset(rr, 0, sizeof(*rr));
+  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
+  if (len > 512) return 0;  //  Too large, we don't expect that
+  if (s >= e) return 0;     //  Overflow
+
+  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
+    return 0;
+  s += rr->nlen + 4;
+  if (s > e) return 0;
+  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
+  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
+  if (is_question) return (size_t) (rr->nlen + 4);
+
+  s += 6;
+  if (s > e) return 0;
+  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
+  if (s + rr->alen > e) return 0;
+  return (size_t) (rr->nlen + rr->alen + 10);
 }
 
-static bool ff_rename(const char *from, const char *to) {
-  return f_rename(from, to) == FR_OK;
+bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
+  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
+  struct mg_dns_rr rr;
+  size_t i, n, num_answers, ofs = sizeof(*h);
+  memset(dm, 0, sizeof(*dm));
+
+  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
+  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
+  num_answers = mg_ntohs(h->num_answers);
+  if (num_answers > 10) {
+    MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers));
+    num_answers = 10;  // Sanity cap
+  }
+  dm->txnid = mg_ntohs(h->txnid);
+
+  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
+    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
+    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
+    ofs += n;
+  }
+  for (i = 0; i < num_answers; i++) {
+    if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
+    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
+    // dm->name));
+    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
+    ofs += n;
+
+    if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
+      dm->addr.is_ip6 = false;
+      memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
+      dm->resolved = true;
+      break;  // Return success
+    } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
+      dm->addr.is_ip6 = true;
+      memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
+      dm->resolved = true;
+      break;  // Return success
+    }
+  }
+  return true;
 }
 
-static bool ff_remove(const char *path) {
-  return f_unlink(path) == FR_OK;
+static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
+  struct dns_data *d, *tmp;
+  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
+  if (ev == MG_EV_POLL) {
+    uint64_t now = *(uint64_t *) ev_data;
+    for (d = *head; d != NULL; d = tmp) {
+      tmp = d->next;
+      // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
+      if (now > d->expire) mg_error(d->c, "DNS timeout");
+    }
+  } else if (ev == MG_EV_READ) {
+    struct mg_dns_message dm;
+    int resolved = 0;
+    if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
+      MG_ERROR(("Unexpected DNS response:"));
+      mg_hexdump(c->recv.buf, c->recv.len);
+    } else {
+      // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
+      for (d = *head; d != NULL; d = tmp) {
+        tmp = d->next;
+        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
+        if (dm.txnid != d->txnid) continue;
+        if (d->c->is_resolving) {
+          if (dm.resolved) {
+            dm.addr.port = d->c->rem.port;  // Save port
+            d->c->rem = dm.addr;            // Copy resolved address
+            MG_DEBUG(
+                ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem));
+            mg_connect_resolved(d->c);
+#if MG_ENABLE_IPV6
+          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
+                     c->mgr->use_dns6 == false) {
+            struct mg_str x = mg_str(dm.name);
+            mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
+#endif
+          } else {
+            mg_error(d->c, "%s DNS lookup failed", dm.name);
+          }
+        } else {
+          MG_ERROR(("%lu already resolved", d->c->id));
+        }
+        mg_dns_free(head, d);
+        resolved = 1;
+      }
+    }
+    if (!resolved) MG_ERROR(("stray DNS reply"));
+    c->recv.len = 0;
+  } else if (ev == MG_EV_CLOSE) {
+    for (d = *head; d != NULL; d = tmp) {
+      tmp = d->next;
+      mg_error(d->c, "DNS error");
+      mg_dns_free(head, d);
+    }
+  }
 }
 
-static bool ff_mkdir(const char *path) {
-  return f_mkdir(path) == FR_OK;
+static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
+                        uint16_t txnid, bool ipv6) {
+  struct {
+    struct mg_dns_header header;
+    uint8_t data[256];
+  } pkt;
+  size_t i, n;
+  memset(&pkt, 0, sizeof(pkt));
+  pkt.header.txnid = mg_htons(txnid);
+  pkt.header.flags = mg_htons(0x100);
+  pkt.header.num_questions = mg_htons(1);
+  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
+    if (name->buf[i] == '.' || i >= name->len) {
+      pkt.data[n] = (uint8_t) (i - n);
+      memcpy(&pkt.data[n + 1], name->buf + n, i - n);
+      n = i + 1;
+    }
+    if (i >= name->len) break;
+  }
+  memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
+  n += 5;
+  if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
+  // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
+  // n += 6;
+  return mg_send(c, &pkt, sizeof(pkt.header) + n);
 }
 
-struct mg_fs mg_fs_fat = {ff_stat,  ff_list, ff_open,   ff_close,  ff_read,
-                          ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
-#endif
+static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
+                         struct mg_dns *dnsc, bool ipv6) {
+  struct dns_data *d = NULL;
+  if (dnsc->url == NULL) {
+    mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
+  } else if (dnsc->c == NULL) {
+    dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
+    if (dnsc->c != NULL) {
+      dnsc->c->pfn = dns_cb;
+      // dnsc->c->is_hexdumping = 1;
+    }
+  }
+  if (dnsc->c == NULL) {
+    mg_error(c, "resolver");
+  } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
+    mg_error(c, "resolve OOM");
+  } else {
+    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
+    d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
+    d->next = (struct dns_data *) c->mgr->active_dns_requests;
+    c->mgr->active_dns_requests = d;
+    d->expire = mg_millis() + (uint64_t) ms;
+    d->c = c;
+    c->is_resolving = 1;
+    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
+                name->buf, dnsc->url, d->txnid));
+    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
+      mg_error(dnsc->c, "DNS send");
+    }
+  }
+}
+
+void mg_resolve(struct mg_connection *c, const char *url) {
+  struct mg_str host = mg_url_host(url);
+  c->rem.port = mg_htons(mg_url_port(url));
+  if (mg_aton(host, &c->rem)) {
+    // host is an IP address, do not fire name resolution
+    mg_connect_resolved(c);
+  } else {
+    // host is not an IP, send DNS resolution request
+    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
+    mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
+  }
+}
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/fs_packed.c"
+#line 1 "src/event.c"
 #endif
 
 
 
 
-struct packed_file {
-  const char *data;
-  size_t size;
-  size_t pos;
-};
 
-const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
-const char *mg_unlist(size_t no);
 
-#if MG_ENABLE_PACKED_FS
-#else
-const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
-  (void) path, (void) size, (void) mtime;
-  return NULL;
-}
-const char *mg_unlist(size_t no) {
-  (void) no;
-  return NULL;
-}
+void mg_call(struct mg_connection *c, int ev, void *ev_data) {
+#if MG_ENABLE_PROFILE
+  const char *names[] = {
+      "EV_ERROR",    "EV_OPEN",      "EV_POLL",      "EV_RESOLVE",
+      "EV_CONNECT",  "EV_ACCEPT",    "EV_TLS_HS",    "EV_READ",
+      "EV_WRITE",    "EV_CLOSE",     "EV_HTTP_MSG",  "EV_HTTP_CHUNK",
+      "EV_WS_OPEN",  "EV_WS_MSG",    "EV_WS_CTL",    "EV_MQTT_CMD",
+      "EV_MQTT_MSG", "EV_MQTT_OPEN", "EV_SNTP_TIME", "EV_USER"};
+  if (ev != MG_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) {
+    MG_PROF_ADD(c, names[ev]);
+  }
 #endif
-
-static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
-  // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
-  return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
-         (n == 0 || path[n] == '/' || path[n - 1] == '/');
+  // Fire protocol handler first, user handler second. See #2559
+  if (c->pfn != NULL) c->pfn(c, ev, ev_data);
+  if (c->fn != NULL) c->fn(c, ev, ev_data);
 }
 
-static int packed_stat(const char *path, size_t *size, time_t *mtime) {
-  const char *p;
-  size_t i, n = strlen(path);
-  if (mg_unpack(path, size, mtime)) return MG_FS_READ;  // Regular file
-  // Scan all files. If `path` is a dir prefix for any of them, it's a dir
-  for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
-    if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
-  }
-  return 0;
-}
-
-static void packed_list(const char *dir, void (*fn)(const char *, void *),
-                        void *userdata) {
-  char buf[MG_PATH_MAX], tmp[sizeof(buf)];
-  const char *path, *begin, *end;
-  size_t i, n = strlen(dir);
-  tmp[0] = '\0';  // Previously listed entry
-  for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
-    if (!is_dir_prefix(dir, n, path)) continue;
-    begin = &path[n + 1];
-    end = strchr(begin, '/');
-    if (end == NULL) end = begin + strlen(begin);
-    mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
-    buf[sizeof(buf) - 1] = '\0';
-    // If this entry has been already listed, skip
-    // NOTE: we're assuming that file list is sorted alphabetically
-    if (strcmp(buf, tmp) == 0) continue;
-    fn(buf, userdata);  // Not yet listed, call user function
-    strcpy(tmp, buf);   // And save this entry as listed
-  }
-}
-
-static void *packed_open(const char *path, int flags) {
-  size_t size = 0;
-  const char *data = mg_unpack(path, &size, NULL);
-  struct packed_file *fp = NULL;
-  if (data == NULL) return NULL;
-  if (flags & MG_FS_WRITE) return NULL;
-  fp = (struct packed_file *) calloc(1, sizeof(*fp));
-  fp->size = size;
-  fp->data = data;
-  return (void *) fp;
+void mg_error(struct mg_connection *c, const char *fmt, ...) {
+  char buf[64];
+  va_list ap;
+  va_start(ap, fmt);
+  mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
+  va_end(ap);
+  MG_ERROR(("%lu %ld %s", c->id, c->fd, buf));
+  c->is_closing = 1;             // Set is_closing before sending MG_EV_CALL
+  mg_call(c, MG_EV_ERROR, buf);  // Let user handler override it
 }
 
-static void packed_close(void *fp) {
-  if (fp != NULL) free(fp);
-}
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fmt.c"
+#endif
 
-static size_t packed_read(void *fd, void *buf, size_t len) {
-  struct packed_file *fp = (struct packed_file *) fd;
-  if (fp->pos + len > fp->size) len = fp->size - fp->pos;
-  memcpy(buf, &fp->data[fp->pos], len);
-  fp->pos += len;
-  return len;
-}
 
-static size_t packed_write(void *fd, const void *buf, size_t len) {
-  (void) fd, (void) buf, (void) len;
-  return 0;
-}
 
-static size_t packed_seek(void *fd, size_t offset) {
-  struct packed_file *fp = (struct packed_file *) fd;
-  fp->pos = offset;
-  if (fp->pos > fp->size) fp->pos = fp->size;
-  return fp->pos;
-}
 
-static bool packed_rename(const char *from, const char *to) {
-  (void) from, (void) to;
-  return false;
+static bool is_digit(int c) {
+  return c >= '0' && c <= '9';
 }
 
-static bool packed_remove(const char *path) {
-  (void) path;
-  return false;
+static int addexp(char *buf, int e, int sign) {
+  int n = 0;
+  buf[n++] = 'e';
+  buf[n++] = (char) sign;
+  if (e > 400) return 0;
+  if (e < 10) buf[n++] = '0';
+  if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
+  if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
+  buf[n++] = (char) (e + '0');
+  return n;
 }
 
-static bool packed_mkdir(const char *path) {
-  (void) path;
-  return false;
+static int xisinf(double x) {
+  union {
+    double f;
+    uint64_t u;
+  } ieee754 = {x};
+  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
+         ((unsigned) ieee754.u == 0);
 }
 
-struct mg_fs mg_fs_packed = {
-    packed_stat,  packed_list, packed_open,   packed_close,  packed_read,
-    packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/fs_posix.c"
-#endif
-
-
-#if MG_ENABLE_FILE
-
-#ifndef MG_STAT_STRUCT
-#define MG_STAT_STRUCT stat
-#endif
-
-#ifndef MG_STAT_FUNC
-#define MG_STAT_FUNC stat
-#endif
-
-static int p_stat(const char *path, size_t *size, time_t *mtime) {
-#if !defined(S_ISDIR)
-  MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
-  return 0;
-#else
-#if MG_ARCH == MG_ARCH_WIN32
-  struct _stati64 st;
-  wchar_t tmp[MG_PATH_MAX];
-  MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
-  if (_wstati64(tmp, &st) != 0) return 0;
-#else
-  struct MG_STAT_STRUCT st;
-  if (MG_STAT_FUNC(path, &st) != 0) return 0;
-#endif
-  if (size) *size = (size_t) st.st_size;
-  if (mtime) *mtime = st.st_mtime;
-  return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
-#endif
+static int xisnan(double x) {
+  union {
+    double f;
+    uint64_t u;
+  } ieee754 = {x};
+  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
+             ((unsigned) ieee754.u != 0) >
+         0x7ff00000;
 }
 
-#if MG_ARCH == MG_ARCH_WIN32
-struct dirent {
-  char d_name[MAX_PATH];
-};
-
-typedef struct win32_dir {
-  HANDLE handle;
-  WIN32_FIND_DATAW info;
-  struct dirent result;
-} DIR;
+static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) {
+  char buf[40];
+  int i, s = 0, n = 0, e = 0;
+  double t, mul, saved;
+  if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
+  if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf");
+  if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
+  if (d < 0.0) d = -d, buf[s++] = '-';
 
-int gettimeofday(struct timeval *tv, void *tz) {
-  FILETIME ft;
-  unsigned __int64 tmpres = 0;
+  // Round
+  saved = d;
+  mul = 1.0;
+  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
+  while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
+  for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
+  d += t;
+  // Calculate exponent, and 'mul' for scientific representation
+  mul = 1.0;
+  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
+  while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
+  // printf(" --> %g %d %g %g\n", saved, e, t, mul);
 
-  if (tv != NULL) {
-    GetSystemTimeAsFileTime(&ft);
-    tmpres |= ft.dwHighDateTime;
-    tmpres <<= 32;
-    tmpres |= ft.dwLowDateTime;
-    tmpres /= 10;  // convert into microseconds
-    tmpres -= (int64_t) 11644473600000000;
-    tv->tv_sec = (long) (tmpres / 1000000UL);
-    tv->tv_usec = (long) (tmpres % 1000000UL);
+  if (e >= width && width > 1) {
+    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
+    // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
+    n += addexp(buf + s + n, e, '+');
+    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
+  } else if (e <= -width && width > 1) {
+    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
+    // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
+    n += addexp(buf + s + n, -e, '-');
+    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
+  } else {
+    for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
+      int ch = (int) (d / t);
+      if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
+      d -= ch * t;
+      t /= 10.0;
+    }
+    // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
+    if (n == 0) buf[s++] = '0';
+    while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
+    if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
+    // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
+    for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
+      int ch = (int) (d / t);
+      buf[s + n++] = (char) (ch + '0');
+      d -= ch * t;
+      t /= 10.0;
+    }
   }
-  (void) tz;
-  return 0;
+  while (tz && n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeroes
+  if (n > 0 && buf[s + n - 1] == '.') n--;           // Trim trailing dot
+  n += s;
+  if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
+  buf[n] = '\0';
+  return mg_snprintf(dst, dstlen, "%s", buf);
 }
 
-static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
-  int ret;
-  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
-  strncpy(buf, path, sizeof(buf));
-  buf[sizeof(buf) - 1] = '\0';
-  // Trim trailing slashes. Leave backslash for paths like "X:\"
-  p = buf + strlen(buf) - 1;
-  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
-  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
-  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
-  // Convert back to Unicode. If doubly-converted string does not match the
-  // original, something is fishy, reject.
-  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
-                      NULL, NULL);
-  if (strcmp(buf, buf2) != 0) {
-    wbuf[0] = L'\0';
-    ret = 0;
+static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) {
+  const char *letters = "0123456789abcdef";
+  uint64_t v = (uint64_t) val;
+  size_t s = 0, n, i;
+  if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
+  // This loop prints a number in reverse order. I guess this is because we
+  // write numbers from right to left: least significant digit comes last.
+  // Maybe because we use Arabic numbers, and Arabs write RTL?
+  if (is_hex) {
+    for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
+  } else {
+    for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
   }
-  return ret;
+  // Reverse a string
+  for (i = 0; i < n / 2; i++) {
+    char t = buf[s + i];
+    buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
+  }
+  if (val == 0) buf[n++] = '0';  // Handle special case
+  return n + s;
 }
 
-DIR *opendir(const char *name) {
-  DIR *d = NULL;
-  wchar_t wpath[MAX_PATH];
-  DWORD attrs;
+static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
+                          size_t len) {
+  size_t i = 0;
+  while (i < len && buf[i] != '\0') out(buf[i++], ptr);
+  return i;
+}
 
-  if (name == NULL) {
-    SetLastError(ERROR_BAD_ARGUMENTS);
-  } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
-    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-  } else {
-    to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
-    attrs = GetFileAttributesW(wpath);
-    if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
-      (void) wcscat(wpath, L"\\*");
-      d->handle = FindFirstFileW(wpath, &d->info);
-      d->result.d_name[0] = '\0';
+size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
+  size_t len = 0;
+  va_list ap;
+  va_start(ap, fmt);
+  len = mg_vxprintf(out, ptr, fmt, &ap);
+  va_end(ap);
+  return len;
+}
+
+size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt,
+                   va_list *ap) {
+  size_t i = 0, n = 0;
+  while (fmt[i] != '\0') {
+    if (fmt[i] == '%') {
+      size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */;
+      char pad = ' ', minus = 0, c = fmt[++i];
+      if (c == '#') x++, c = fmt[++i];
+      if (c == '-') minus++, c = fmt[++i];
+      if (c == '0') pad = '0', c = fmt[++i];
+      while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
+      if (c == '.') {
+        c = fmt[++i];
+        if (c == '*') {
+          pr = (size_t) va_arg(*ap, int);
+          c = fmt[++i];
+        } else {
+          pr = 0;
+          while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
+        }
+      }
+      while (c == 'h') c = fmt[++i];  // Treat h and hh as int
+      if (c == 'l') {
+        is_long++, c = fmt[++i];
+        if (c == 'l') is_long++, c = fmt[++i];
+      }
+      if (c == 'p') x = 1, is_long = 1;
+      if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
+          c == 'g' || c == 'f') {
+        bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
+        char tmp[40];
+        size_t xl = x ? 2 : 0;
+        if (c == 'g' || c == 'f') {
+          double v = va_arg(*ap, double);
+          if (pr == ~0U) pr = 6;
+          k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr, c == 'g');
+        } else if (is_long == 2) {
+          int64_t v = va_arg(*ap, int64_t);
+          k = mg_lld(tmp, v, s, h);
+        } else if (is_long == 1) {
+          long v = va_arg(*ap, long);
+          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
+        } else {
+          int v = va_arg(*ap, int);
+          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
+        }
+        for (j = 0; j < xl && w > 0; j++) w--;
+        for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+        n += scpy(out, param, (char *) "0x", xl);
+        for (j = 0; pad == '0' && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+        n += scpy(out, param, tmp, k);
+        for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+      } else if (c == 'm' || c == 'M') {
+        mg_pm_t f = va_arg(*ap, mg_pm_t);
+        if (c == 'm') out('"', param);
+        n += f(out, param, ap);
+        if (c == 'm') n += 2, out('"', param);
+      } else if (c == 'c') {
+        int ch = va_arg(*ap, int);
+        out((char) ch, param);
+        n++;
+      } else if (c == 's') {
+        char *p = va_arg(*ap, char *);
+        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
+        for (j = 0; !minus && pr < w && j + pr < w; j++)
+          n += scpy(out, param, &pad, 1);
+        n += scpy(out, param, p, pr);
+        for (j = 0; minus && pr < w && j + pr < w; j++)
+          n += scpy(out, param, &pad, 1);
+      } else if (c == '%') {
+        out('%', param);
+        n++;
+      } else {
+        out('%', param);
+        out(c, param);
+        n += 2;
+      }
+      i++;
     } else {
-      free(d);
-      d = NULL;
+      out(fmt[i], param), n++, i++;
     }
   }
-  return d;
+  return n;
 }
 
-int closedir(DIR *d) {
-  int result = 0;
-  if (d != NULL) {
-    if (d->handle != INVALID_HANDLE_VALUE)
-      result = FindClose(d->handle) ? 0 : -1;
-    free(d);
-  } else {
-    result = -1;
-    SetLastError(ERROR_BAD_ARGUMENTS);
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs.c"
+#endif
+
+
+
+
+struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) {
+  struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
+  if (fd != NULL) {
+    fd->fd = fs->op(path, flags);
+    fd->fs = fs;
+    if (fd->fd == NULL) {
+      free(fd);
+      fd = NULL;
+    }
   }
-  return result;
+  return fd;
 }
 
-struct dirent *readdir(DIR *d) {
-  struct dirent *result = NULL;
-  if (d != NULL) {
-    memset(&d->result, 0, sizeof(d->result));
-    if (d->handle != INVALID_HANDLE_VALUE) {
-      result = &d->result;
-      WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
-                          sizeof(result->d_name), NULL, NULL);
-      if (!FindNextFileW(d->handle, &d->info)) {
-        FindClose(d->handle);
-        d->handle = INVALID_HANDLE_VALUE;
-      }
-    } else {
-      SetLastError(ERROR_FILE_NOT_FOUND);
-    }
-  } else {
-    SetLastError(ERROR_BAD_ARGUMENTS);
+void mg_fs_close(struct mg_fd *fd) {
+  if (fd != NULL) {
+    fd->fs->cl(fd->fd);
+    free(fd);
   }
-  return result;
 }
-#endif
 
-static void p_list(const char *dir, void (*fn)(const char *, void *),
-                   void *userdata) {
-#if MG_ENABLE_DIRLIST
-  struct dirent *dp;
-  DIR *dirp;
-  if ((dirp = (opendir(dir))) == NULL) return;
-  while ((dp = readdir(dirp)) != NULL) {
-    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
-    fn(dp->d_name, userdata);
+struct mg_str mg_file_read(struct mg_fs *fs, const char *path) {
+  struct mg_str result = {NULL, 0};
+  void *fp;
+  fs->st(path, &result.len, NULL);
+  if ((fp = fs->op(path, MG_FS_READ)) != NULL) {
+    result.buf = (char *) calloc(1, result.len + 1);
+    if (result.buf != NULL &&
+        fs->rd(fp, (void *) result.buf, result.len) != result.len) {
+      free((void *) result.buf);
+      result.buf = NULL;
+    }
+    fs->cl(fp);
   }
-  closedir(dirp);
-#else
-  (void) dir, (void) fn, (void) userdata;
-#endif
+  if (result.buf == NULL) result.len = 0;
+  return result;
 }
 
-static void *p_open(const char *path, int flags) {
-  const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
-#if MG_ARCH == MG_ARCH_WIN32
-  wchar_t b1[MG_PATH_MAX], b2[10];
-  MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
-  MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
-  return (void *) _wfopen(b1, b2);
-#else
-  return (void *) fopen(path, mode);
-#endif
+bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
+                   size_t len) {
+  bool result = false;
+  struct mg_fd *fd;
+  char tmp[MG_PATH_MAX];
+  mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
+  if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
+    result = fs->wr(fd->fd, buf, len) == len;
+    mg_fs_close(fd);
+    if (result) {
+      fs->rm(path);
+      fs->mv(tmp, path);
+    } else {
+      fs->rm(tmp);
+    }
+  }
+  return result;
 }
 
-static void p_close(void *fp) {
-  fclose((FILE *) fp);
+bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
+  va_list ap;
+  char *data;
+  bool result = false;
+  va_start(ap, fmt);
+  data = mg_vmprintf(fmt, &ap);
+  va_end(ap);
+  result = mg_file_write(fs, path, data, strlen(data));
+  free(data);
+  return result;
 }
 
-static size_t p_read(void *fp, void *buf, size_t len) {
-  return fread(buf, 1, len, (FILE *) fp);
+// This helper function allows to scan a filesystem in a sequential way,
+// without using callback function:
+//      char buf[100] = "";
+//      while (mg_fs_ls(&mg_fs_posix, "./", buf, sizeof(buf))) {
+//        ...
+static void mg_fs_ls_fn(const char *filename, void *param) {
+  struct mg_str *s = (struct mg_str *) param;
+  if (s->buf[0] == '\0') {
+    mg_snprintf((char *) s->buf, s->len, "%s", filename);
+  } else if (strcmp(s->buf, filename) == 0) {
+    ((char *) s->buf)[0] = '\0';  // Fetch next file
+  }
 }
 
-static size_t p_write(void *fp, const void *buf, size_t len) {
-  return fwrite(buf, 1, len, (FILE *) fp);
+bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len) {
+  struct mg_str s = {buf, len};
+  fs->ls(path, mg_fs_ls_fn, &s);
+  return buf[0] != '\0';
 }
 
-static size_t p_seek(void *fp, size_t offset) {
-#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) ||  \
-    (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
-    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
-  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
-#else
-  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs_fat.c"
 #endif
-  return (size_t) ftell((FILE *) fp);
-}
 
-static bool p_rename(const char *from, const char *to) {
-  return rename(from, to) == 0;
-}
 
-static bool p_remove(const char *path) {
-  return remove(path) == 0;
-}
 
-static bool p_mkdir(const char *path) {
-  return mkdir(path, 0775) == 0;
-}
+#if MG_ENABLE_FATFS
+#include <ff.h>
 
-#else
+static int mg_days_from_epoch(int y, int m, int d) {
+  y -= m <= 2;
+  int era = y / 400;
+  int yoe = y - era * 400;
+  int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
+  int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
+  return era * 146097 + doe - 719468;
+}
 
-static int p_stat(const char *path, size_t *size, time_t *mtime) {
-  (void) path, (void) size, (void) mtime;
-  return 0;
+static time_t mg_timegm(const struct tm *t) {
+  int year = t->tm_year + 1900;
+  int month = t->tm_mon;  // 0-11
+  if (month > 11) {
+    year += month / 12;
+    month %= 12;
+  } else if (month < 0) {
+    int years_diff = (11 - month) / 12;
+    year -= years_diff;
+    month += 12 * years_diff;
+  }
+  int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
+  return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
 }
-static void p_list(const char *path, void (*fn)(const char *, void *),
-                   void *userdata) {
-  (void) path, (void) fn, (void) userdata;
-}
-static void *p_open(const char *path, int flags) {
-  (void) path, (void) flags;
-  return NULL;
-}
-static void p_close(void *fp) {
-  (void) fp;
-}
-static size_t p_read(void *fd, void *buf, size_t len) {
-  (void) fd, (void) buf, (void) len;
-  return 0;
-}
-static size_t p_write(void *fd, const void *buf, size_t len) {
-  (void) fd, (void) buf, (void) len;
-  return 0;
-}
-static size_t p_seek(void *fd, size_t offset) {
-  (void) fd, (void) offset;
-  return (size_t) ~0;
-}
-static bool p_rename(const char *from, const char *to) {
-  (void) from, (void) to;
-  return false;
-}
-static bool p_remove(const char *path) {
-  (void) path;
-  return false;
-}
-static bool p_mkdir(const char *path) {
-  (void) path;
-  return false;
-}
-#endif
-
-struct mg_fs mg_fs_posix = {p_stat,  p_list, p_open,   p_close,  p_read,
-                            p_write, p_seek, p_rename, p_remove, p_mkdir};
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/http.c"
-#endif
-
-
-
-
-
-
-
-
-
-
-
-
-// Chunk deletion marker is the MSB in the "processed" counter
-#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
-
-// Multipart POST example:
-// --xyz
-// Content-Disposition: form-data; name="val"
-//
-// abcdef
-// --xyz
-// Content-Disposition: form-data; name="foo"; filename="a.txt"
-// Content-Type: text/plain
-//
-// hello world
-//
-// --xyz--
-size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
-                              struct mg_http_part *part) {
-  struct mg_str cd = mg_str_n("Content-Disposition", 19);
-  const char *s = body.ptr;
-  size_t b = ofs, h1, h2, b1, b2, max = body.len;
-
-  // Init part params
-  if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
-
-  // Skip boundary
-  while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
-  if (b <= ofs || b + 2 >= max) return 0;
-  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
-
-  // Skip headers
-  h1 = h2 = b + 2;
-  for (;;) {
-    while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
-    if (h2 == h1) break;
-    if (h2 + 2 >= max) return 0;
-    // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
-    if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
-        mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
-      struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
-      part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
-      part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
-    }
-    h1 = h2 = h2 + 2;
-  }
-  b1 = b2 = h2 + 2;
-  while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
-                                           memcmp(&s[b2 + 2], s, b - ofs) == 0))
-    b2++;
 
-  if (b2 + 2 >= max) return 0;
-  if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
-  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
-  return b2 + 2;
+static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
+  struct tm tm;
+  memset(&tm, 0, sizeof(struct tm));
+  tm.tm_sec = (ftime << 1) & 0x3e;
+  tm.tm_min = ((ftime >> 5) & 0x3f);
+  tm.tm_hour = ((ftime >> 11) & 0x1f);
+  tm.tm_mday = (fdate & 0x1f);
+  tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
+  tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
+  return mg_timegm(&tm);
 }
 
-void mg_http_bauth(struct mg_connection *c, const char *user,
-                   const char *pass) {
-  struct mg_str u = mg_str(user), p = mg_str(pass);
-  size_t need = c->send.len + 36 + (u.len + p.len) * 2;
-  if (c->send.size < need) mg_iobuf_resize(&c->send, need);
-  if (c->send.size >= need) {
-    int i, n = 0;
-    char *buf = (char *) &c->send.buf[c->send.len];
-    memcpy(buf, "Authorization: Basic ", 21);  // DON'T use mg_send!
-    for (i = 0; i < (int) u.len; i++) {
-      n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
-    }
-    if (p.len > 0) {
-      n = mg_base64_update(':', buf + 21, n);
-      for (i = 0; i < (int) p.len; i++) {
-        n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
-      }
-    }
-    n = mg_base64_final(buf + 21, n);
-    c->send.len += 21 + (size_t) n + 2;
-    memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
+static int ff_stat(const char *path, size_t *size, time_t *mtime) {
+  FILINFO fi;
+  if (path[0] == '\0') {
+    if (size) *size = 0;
+    if (mtime) *mtime = 0;
+    return MG_FS_DIR;
+  } else if (f_stat(path, &fi) == 0) {
+    if (size) *size = (size_t) fi.fsize;
+    if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
+    return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0);
   } else {
-    MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
+    return 0;
   }
 }
 
-struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
-  struct mg_str k, v, result = mg_str_n(NULL, 0);
-  while (mg_split(&buf, &k, &v, '&')) {
-    if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) {
-      result = v;
-      break;
+static void ff_list(const char *dir, void (*fn)(const char *, void *),
+                    void *userdata) {
+  DIR d;
+  FILINFO fi;
+  if (f_opendir(&d, dir) == FR_OK) {
+    while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
+      if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
+      fn(fi.fname, userdata);
     }
+    f_closedir(&d);
   }
-  return result;
 }
 
-int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
-                    size_t dst_len) {
-  int len;
-  if (dst == NULL || dst_len == 0) {
-    len = -2;  // Bad destination
-  } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
-    len = -1;  // Bad source
-    dst[0] = '\0';
-  } else {
-    struct mg_str v = mg_http_var(*buf, mg_str(name));
-    if (v.ptr == NULL) {
-      len = -4;  // Name does not exist
-    } else {
-      len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
-      if (len < 0) len = -3;  // Failed to decode
+static void *ff_open(const char *path, int flags) {
+  FIL f;
+  unsigned char mode = FA_READ;
+  if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
+  if (f_open(&f, path, mode) == 0) {
+    FIL *fp;
+    if ((fp = calloc(1, sizeof(*fp))) != NULL) {
+      memcpy(fp, &f, sizeof(*fp));
+      return fp;
     }
   }
-  return len;
+  return NULL;
 }
 
-static bool isx(int c) {
-  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
-         (c >= 'A' && c <= 'F');
+static void ff_close(void *fp) {
+  if (fp != NULL) {
+    f_close((FIL *) fp);
+    free(fp);
+  }
 }
 
-int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
-                  int is_form_url_encoded) {
-  size_t i, j;
-  for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
-    if (src[i] == '%') {
-      // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
-      if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
-        mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
-        i += 2;
-      } else {
-        return -1;
-      }
-    } else if (is_form_url_encoded && src[i] == '+') {
-      dst[j] = ' ';
-    } else {
-      dst[j] = src[i];
-    }
+static size_t ff_read(void *fp, void *buf, size_t len) {
+  UINT n = 0, misalign = ((size_t) buf) & 3;
+  if (misalign) {
+    char aligned[4];
+    f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
+    memcpy(buf, aligned, n);
+  } else {
+    f_read((FIL *) fp, buf, len, &n);
   }
-  if (j < dst_len) dst[j] = '\0';  // Null-terminate the destination
-  return i >= src_len && j < dst_len ? (int) j : -1;
+  return n;
 }
 
-static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' '; }
-
-int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
-  size_t i;
-  for (i = 0; i < buf_len; i++) {
-    if (!isok(buf[i])) return -1;
-    if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
-        (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
-      return (int) i + 1;
-  }
-  return 0;
+static size_t ff_write(void *fp, const void *buf, size_t len) {
+  UINT n = 0;
+  return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
 }
 
-static const char *skip(const char *s, const char *e, const char *d,
-                        struct mg_str *v) {
-  v->ptr = s;
-  while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
-  v->len = (size_t) (s - v->ptr);
-  while (s < e && strchr(d, *s) != NULL) s++;
-  return s;
+static size_t ff_seek(void *fp, size_t offset) {
+  f_lseek((FIL *) fp, offset);
+  return offset;
 }
 
-struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
-  size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
-  for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
-    struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
-    if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
-  }
-  return NULL;
+static bool ff_rename(const char *from, const char *to) {
+  return f_rename(from, to) == FR_OK;
 }
 
-static void mg_http_parse_headers(const char *s, const char *end,
-                                  struct mg_http_header *h, int max_headers) {
-  int i;
-  for (i = 0; i < max_headers; i++) {
-    struct mg_str k, v, tmp;
-    const char *he = skip(s, end, "\n", &tmp);
-    s = skip(s, he, ": \r\n", &k);
-    s = skip(s, he, "\r\n", &v);
-    if (k.len == tmp.len) continue;
-    while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--;  // Trim spaces
-    if (k.len == 0) break;
-    // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
-    //(int) k.len, k.ptr, (int) v.len, v.ptr));
-    h[i].name = k;
-    h[i].value = v;
-  }
+static bool ff_remove(const char *path) {
+  return f_unlink(path) == FR_OK;
 }
 
-int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
-  int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
-  const char *end = s == NULL ? NULL : s + req_len, *qs;  // Cannot add to NULL
-  struct mg_str *cl;
+static bool ff_mkdir(const char *path) {
+  return f_mkdir(path) == FR_OK;
+}
 
-  memset(hm, 0, sizeof(*hm));
-  if (req_len <= 0) return req_len;
+struct mg_fs mg_fs_fat = {ff_stat,  ff_list, ff_open,   ff_close,  ff_read,
+                          ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
+#endif
 
-  hm->message.ptr = hm->head.ptr = s;
-  hm->body.ptr = end;
-  hm->head.len = (size_t) req_len;
-  hm->chunk.ptr = end;
-  hm->message.len = hm->body.len = (size_t) ~0;  // Set body length to infinite
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs_packed.c"
+#endif
 
-  // Parse request line
-  s = skip(s, end, " ", &hm->method);
-  s = skip(s, end, " ", &hm->uri);
-  s = skip(s, end, "\r\n", &hm->proto);
 
-  // Sanity check. Allow protocol/reason to be empty
-  if (hm->method.len == 0 || hm->uri.len == 0) return -1;
 
-  // If URI contains '?' character, setup query string
-  if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) {
-    hm->query.ptr = qs + 1;
-    hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
-    hm->uri.len = (size_t) (qs - hm->uri.ptr);
-  }
 
-  mg_http_parse_headers(s, end, hm->headers,
-                        sizeof(hm->headers) / sizeof(hm->headers[0]));
-  if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
-    hm->body.len = (size_t) mg_to64(*cl);
-    hm->message.len = (size_t) req_len + hm->body.len;
-  }
+struct packed_file {
+  const char *data;
+  size_t size;
+  size_t pos;
+};
 
-  // mg_http_parse() is used to parse both HTTP requests and HTTP
-  // responses. If HTTP response does not have Content-Length set, then
-  // body is read until socket is closed, i.e. body.len is infinite (~0).
-  //
-  // For HTTP requests though, according to
-  // http://tools.ietf.org/html/rfc7231#section-8.1.3,
-  // only POST and PUT methods have defined body semantics.
-  // Therefore, if Content-Length is not specified and methods are
-  // not one of PUT or POST, set body length to 0.
-  //
-  // So, if it is HTTP request, and Content-Length is not set,
-  // and method is not (PUT or POST) then reset body length to zero.
-  is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
-  if (hm->body.len == (size_t) ~0 && !is_response &&
-      mg_vcasecmp(&hm->method, "PUT") != 0 &&
-      mg_vcasecmp(&hm->method, "POST") != 0) {
-    hm->body.len = 0;
-    hm->message.len = (size_t) req_len;
-  }
+#if MG_ENABLE_PACKED_FS
+#else
+const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
+  *size = 0, *mtime = 0;
+  (void) path;
+  return NULL;
+}
+const char *mg_unlist(size_t no) {
+  (void) no;
+  return NULL;
+}
+#endif
 
-  // The 204 (No content) responses also have 0 body length
-  if (hm->body.len == (size_t) ~0 && is_response &&
-      mg_vcasecmp(&hm->uri, "204") == 0) {
-    hm->body.len = 0;
-    hm->message.len = (size_t) req_len;
-  }
+struct mg_str mg_unpacked(const char *path) {
+  size_t len = 0;
+  const char *buf = mg_unpack(path, &len, NULL);
+  return mg_str_n(buf, len);
+}
 
-  return req_len;
+static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
+  // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
+  return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
+         (n == 0 || path[n] == '/' || path[n - 1] == '/');
 }
 
-static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
-                                  va_list *ap) {
-  size_t len = c->send.len;
-  mg_send(c, "        \r\n", 10);
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  if (c->send.len >= len + 10) {
-    mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
-    c->send.buf[len + 8] = '\r';
-    if (c->send.len == len + 10) c->is_resp = 0;  // Last chunk, reset marker
+static int packed_stat(const char *path, size_t *size, time_t *mtime) {
+  const char *p;
+  size_t i, n = strlen(path);
+  if (mg_unpack(path, size, mtime)) return MG_FS_READ;  // Regular file
+  // Scan all files. If `path` is a dir prefix for any of them, it's a dir
+  for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
+    if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
   }
-  mg_send(c, "\r\n", 2);
+  return 0;
 }
 
-void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_http_vprintf_chunk(c, fmt, &ap);
-  va_end(ap);
+static void packed_list(const char *dir, void (*fn)(const char *, void *),
+                        void *userdata) {
+  char buf[MG_PATH_MAX], tmp[sizeof(buf)];
+  const char *path, *begin, *end;
+  size_t i, n = strlen(dir);
+  tmp[0] = '\0';  // Previously listed entry
+  for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
+    if (!is_dir_prefix(dir, n, path)) continue;
+    begin = &path[n + 1];
+    end = strchr(begin, '/');
+    if (end == NULL) end = begin + strlen(begin);
+    mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
+    buf[sizeof(buf) - 1] = '\0';
+    // If this entry has been already listed, skip
+    // NOTE: we're assuming that file list is sorted alphabetically
+    if (strcmp(buf, tmp) == 0) continue;
+    fn(buf, userdata);  // Not yet listed, call user function
+    strcpy(tmp, buf);   // And save this entry as listed
+  }
 }
 
-void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
-  mg_printf(c, "%lx\r\n", (unsigned long) len);
-  mg_send(c, buf, len);
-  mg_send(c, "\r\n", 2);
-  if (len == 0) c->is_resp = 0;
+static void *packed_open(const char *path, int flags) {
+  size_t size = 0;
+  const char *data = mg_unpack(path, &size, NULL);
+  struct packed_file *fp = NULL;
+  if (data == NULL) return NULL;
+  if (flags & MG_FS_WRITE) return NULL;
+  if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) {
+    fp->size = size;
+    fp->data = data;
+  }
+  return (void *) fp;
 }
 
-// clang-format off
-static const char *mg_http_status_code_str(int status_code) {
-  switch (status_code) {
-    case 100: return "Continue";
-    case 201: return "Created";
-    case 202: return "Accepted";
-    case 204: return "No Content";
-    case 206: return "Partial Content";
-    case 301: return "Moved Permanently";
-    case 302: return "Found";
-    case 304: return "Not Modified";
-    case 400: return "Bad Request";
-    case 401: return "Unauthorized";
-    case 403: return "Forbidden";
-    case 404: return "Not Found";
-    case 418: return "I'm a teapot";
-    case 500: return "Internal Server Error";
-    case 501: return "Not Implemented";
-    default: return "OK";
-  }
+static void packed_close(void *fp) {
+  if (fp != NULL) free(fp);
 }
-// clang-format on
 
-void mg_http_reply(struct mg_connection *c, int code, const char *headers,
-                   const char *fmt, ...) {
-  va_list ap;
-  size_t len;
-  mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length:           \r\n\r\n", code,
-            mg_http_status_code_str(code), headers == NULL ? "" : headers);
-  len = c->send.len;
-  va_start(ap, fmt);
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
-  va_end(ap);
-  if (c->send.len > 15) {
-    mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
-                (unsigned long) (c->send.len - len));
-    c->is_resp = 0;
-    c->send.buf[len - 4] = '\r';  // Change ending 0 to space
-  }
-  c->is_resp = 0;
+static size_t packed_read(void *fd, void *buf, size_t len) {
+  struct packed_file *fp = (struct packed_file *) fd;
+  if (fp->pos + len > fp->size) len = fp->size - fp->pos;
+  memcpy(buf, &fp->data[fp->pos], len);
+  fp->pos += len;
+  return len;
 }
 
-static void http_cb(struct mg_connection *, int, void *, void *);
-static void restore_http_cb(struct mg_connection *c) {
-  mg_fs_close((struct mg_fd *) c->pfn_data);
-  c->pfn_data = NULL;
-  c->pfn = http_cb;
-  c->is_resp = 0;
+static size_t packed_write(void *fd, const void *buf, size_t len) {
+  (void) fd, (void) buf, (void) len;
+  return 0;
 }
 
-char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
-char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
-  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
-  return buf;
+static size_t packed_seek(void *fd, size_t offset) {
+  struct packed_file *fp = (struct packed_file *) fd;
+  fp->pos = offset;
+  if (fp->pos > fp->size) fp->pos = fp->size;
+  return fp->pos;
 }
 
-static void static_cb(struct mg_connection *c, int ev, void *ev_data,
-                      void *fn_data) {
-  if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
-    struct mg_fd *fd = (struct mg_fd *) fn_data;
-    // Read to send IO buffer directly, avoid extra on-stack buffer
-    size_t n, max = MG_IO_SIZE, space;
-    size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
-                                     sizeof(size_t) * sizeof(size_t)];
-    if (c->send.size < max) mg_iobuf_resize(&c->send, max);
-    if (c->send.len >= c->send.size) return;  // Rate limit
-    if ((space = c->send.size - c->send.len) > *cl) space = *cl;
-    n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
-    c->send.len += n;
-    *cl -= n;
-    if (n == 0) restore_http_cb(c);
-  } else if (ev == MG_EV_CLOSE) {
-    restore_http_cb(c);
-  }
-  (void) ev_data;
+static bool packed_rename(const char *from, const char *to) {
+  (void) from, (void) to;
+  return false;
 }
 
-// Known mime types. Keep it outside guess_content_type() function, since
-// some environments don't like it defined there.
-// clang-format off
-static struct mg_str s_known_types[] = {
-    MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
-    MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
-    MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
-    MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
-    MG_C_STR("gif"), MG_C_STR("image/gif"),
-    MG_C_STR("png"), MG_C_STR("image/png"),
-    MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
-    MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
-    MG_C_STR("woff"), MG_C_STR("font/woff"),
-    MG_C_STR("ttf"), MG_C_STR("font/ttf"),
-    MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
-    MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
-    MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
-    MG_C_STR("csv"), MG_C_STR("text/csv"),
-    MG_C_STR("doc"), MG_C_STR("application/msword"),
-    MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
-    MG_C_STR("gz"), MG_C_STR("application/gzip"),
-    MG_C_STR("ico"), MG_C_STR("image/x-icon"),
-    MG_C_STR("json"), MG_C_STR("application/json"),
-    MG_C_STR("mov"), MG_C_STR("video/quicktime"),
-    MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
-    MG_C_STR("mp4"), MG_C_STR("video/mp4"),
-    MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
-    MG_C_STR("pdf"), MG_C_STR("application/pdf"),
-    MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
-    MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
-    MG_C_STR("wav"), MG_C_STR("audio/wav"),
-    MG_C_STR("webp"), MG_C_STR("image/webp"),
-    MG_C_STR("zip"), MG_C_STR("application/zip"),
-    MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
-    {0, 0},
-};
-// clang-format on
+static bool packed_remove(const char *path) {
+  (void) path;
+  return false;
+}
 
-static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
-  struct mg_str k, v, s = mg_str(extra);
-  size_t i = 0;
+static bool packed_mkdir(const char *path) {
+  (void) path;
+  return false;
+}
 
-  // Shrink path to its extension only
-  while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
-  path.ptr += path.len - i;
-  path.len = i;
+struct mg_fs mg_fs_packed = {
+    packed_stat,  packed_list, packed_open,   packed_close,  packed_read,
+    packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
 
-  // Process user-provided mime type overrides, if any
-  while (mg_commalist(&s, &k, &v)) {
-    if (mg_strcmp(path, k) == 0) return v;
-  }
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs_posix.c"
+#endif
 
-  // Process built-in mime types
-  for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
-    if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1];
-  }
 
-  return mg_str("text/plain; charset=utf-8");
-}
+#if MG_ENABLE_POSIX_FS
 
-static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
-  size_t i, numparsed = 0;
-  // MG_INFO(("%.*s", (int) s->len, s->ptr));
-  for (i = 0; i + 6 < s->len; i++) {
-    if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
-      struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
-      if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
-      *a = mg_to64(p);
-      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
-      while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
-      if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
-      *b = mg_to64(p);
-      if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
-      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
-      break;
-    }
-  }
-  return (int) numparsed;
-}
+#ifndef MG_STAT_STRUCT
+#define MG_STAT_STRUCT stat
+#endif
 
-void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
-                        const char *path,
-                        const struct mg_http_serve_opts *opts) {
-  char etag[64], tmp[MG_PATH_MAX];
-  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path, MG_FS_READ);
-  size_t size = 0;
-  time_t mtime = 0;
-  struct mg_str *inm = NULL;
-  struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
-  bool gzip = false;
+#ifndef MG_STAT_FUNC
+#define MG_STAT_FUNC stat
+#endif
 
-  // If file does not exist, we try to open file PATH.gz - and if such
-  // pre-compressed .gz file exists, serve it with the Content-Encoding: gzip
-  // Note - we ignore Accept-Encoding, cause we don't have a choice
-  if (fd == NULL) {
-    MG_DEBUG(("NULL [%s]", path));
-    mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
-    if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
-      gzip = true;
-      path = tmp;
-    } else if (opts->page404 != NULL) {
-      // No precompressed file, serve 404
-      fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
-      mime = guess_content_type(mg_str(path), opts->mime_types);
-      path = opts->page404;
+static int p_stat(const char *path, size_t *size, time_t *mtime) {
+#if !defined(S_ISDIR)
+  MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
+  return 0;
+#else
+#if MG_ARCH == MG_ARCH_WIN32
+  struct _stati64 st;
+  wchar_t tmp[MG_PATH_MAX];
+  MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
+  if (_wstati64(tmp, &st) != 0) return 0;
+  // If path is a symlink, windows reports 0 in st.st_size.
+  // Get a real file size by opening it and jumping to the end
+  if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
+    FILE *fp = _wfopen(tmp, L"rb");
+    if (fp != NULL) {
+      fseek(fp, 0, SEEK_END);
+      if (ftell(fp) > 0) st.st_size = ftell(fp);  // Use _ftelli64 on win10+
+      fclose(fp);
     }
   }
+#else
+  struct MG_STAT_STRUCT st;
+  if (MG_STAT_FUNC(path, &st) != 0) return 0;
+#endif
+  if (size) *size = (size_t) st.st_size;
+  if (mtime) *mtime = st.st_mtime;
+  return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
+#endif
+}
 
-  if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
-    mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
-    mg_fs_close(fd);
-    // NOTE: mg_http_etag() call should go first!
-  } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
-             (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
-             mg_vcasecmp(inm, etag) == 0) {
-    mg_fs_close(fd);
-    mg_http_reply(c, 304, opts->extra_headers, "");
-  } else {
-    int n, status = 200;
-    char range[100];
-    int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
+#if MG_ARCH == MG_ARCH_WIN32
+struct dirent {
+  char d_name[MAX_PATH];
+};
 
-    // Handle Range header
-    struct mg_str *rh = mg_http_get_header(hm, "Range");
-    range[0] = '\0';
-    if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
-      // If range is specified like "400-", set second limit to content len
-      if (n == 1) r2 = cl - 1;
-      if (r1 > r2 || r2 >= cl) {
-        status = 416;
-        cl = 0;
-        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
-                    (int64_t) size);
-      } else {
-        status = 206;
-        cl = r2 - r1 + 1;
-        mg_snprintf(range, sizeof(range),
-                    "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
-                    (int64_t) size);
-        fs->sk(fd->fd, (size_t) r1);
-      }
-    }
-    mg_printf(c,
-              "HTTP/1.1 %d %s\r\n"
-              "Content-Type: %.*s\r\n"
-              "Etag: %s\r\n"
-              "Content-Length: %llu\r\n"
-              "%s%s%s\r\n",
-              status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
-              etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
-              opts->extra_headers ? opts->extra_headers : "");
-    if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
-      c->is_draining = 1;
-      c->is_resp = 0;
-      mg_fs_close(fd);
-    } else {
-      // Track to-be-sent content length at the end of c->data, aligned
-      size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
-                                        sizeof(size_t) * sizeof(size_t)];
-      c->pfn = static_cb;
-      c->pfn_data = fd;
-      *clp = (size_t) cl;
-    }
+typedef struct win32_dir {
+  HANDLE handle;
+  WIN32_FIND_DATAW info;
+  struct dirent result;
+} DIR;
+
+#if 0
+int gettimeofday(struct timeval *tv, void *tz) {
+  FILETIME ft;
+  unsigned __int64 tmpres = 0;
+
+  if (tv != NULL) {
+    GetSystemTimeAsFileTime(&ft);
+    tmpres |= ft.dwHighDateTime;
+    tmpres <<= 32;
+    tmpres |= ft.dwLowDateTime;
+    tmpres /= 10;  // convert into microseconds
+    tmpres -= (int64_t) 11644473600000000;
+    tv->tv_sec = (long) (tmpres / 1000000UL);
+    tv->tv_usec = (long) (tmpres % 1000000UL);
   }
+  (void) tz;
+  return 0;
 }
+#endif
 
-struct printdirentrydata {
-  struct mg_connection *c;
-  struct mg_http_message *hm;
-  const struct mg_http_serve_opts *opts;
-  const char *dir;
-};
+static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
+  int ret;
+  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
+  strncpy(buf, path, sizeof(buf));
+  buf[sizeof(buf) - 1] = '\0';
+  // Trim trailing slashes. Leave backslash for paths like "X:\"
+  p = buf + strlen(buf) - 1;
+  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
+  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
+  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
+  // Convert back to Unicode. If doubly-converted string does not match the
+  // original, something is fishy, reject.
+  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
+                      NULL, NULL);
+  if (strcmp(buf, buf2) != 0) {
+    wbuf[0] = L'\0';
+    ret = 0;
+  }
+  return ret;
+}
 
-static void printdirentry(const char *name, void *userdata) {
-  struct printdirentrydata *d = (struct printdirentrydata *) userdata;
-  struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
-  size_t size = 0;
-  time_t t = 0;
-  char path[MG_PATH_MAX], sz[40], mod[40];
-  int flags, n = 0;
+DIR *opendir(const char *name) {
+  DIR *d = NULL;
+  wchar_t wpath[MAX_PATH];
+  DWORD attrs;
 
-  // MG_DEBUG(("[%s] [%s]", d->dir, name));
-  if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
-      sizeof(path)) {
-    MG_ERROR(("%s truncated", name));
-  } else if ((flags = fs->st(path, &size, &t)) == 0) {
-    MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
+  if (name == NULL) {
+    SetLastError(ERROR_BAD_ARGUMENTS);
+  } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
+    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
   } else {
-    const char *slash = flags & MG_FS_DIR ? "/" : "";
-    if (flags & MG_FS_DIR) {
-      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
+    to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
+    attrs = GetFileAttributesW(wpath);
+    if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+      (void) wcscat(wpath, L"\\*");
+      d->handle = FindFirstFileW(wpath, &d->info);
+      d->result.d_name[0] = '\0';
     } else {
-      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
+      free(d);
+      d = NULL;
     }
-#if defined(MG_HTTP_DIRLIST_TIME)
-    char time_str[30];
-    struct tm *time_info = localtime(&t);
-    strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
-    mg_snprintf(mod, sizeof(mod), "%s", time_str);
-#elif defined(MG_HTTP_DIRLIST_TIME_UTC)
-    char time_str[30];
-    struct tm *time_info = gmtime(&t);
-    strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
-    mg_snprintf(mod, sizeof(mod), "%s", time_str);
-#else
-    mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
-#endif
-    n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
-    mg_printf(d->c,
-              "  <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
-              "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
-              n, path, slash, name, slash, (unsigned long) t, mod,
-              flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
   }
+  return d;
 }
 
-static void listdir(struct mg_connection *c, struct mg_http_message *hm,
-                    const struct mg_http_serve_opts *opts, char *dir) {
-  const char *sort_js_code =
-      "<script>function srt(tb, sc, so, d) {"
-      "var tr = Array.prototype.slice.call(tb.rows, 0),"
-      "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
-      "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
-      "t1 = a.cells[2].getAttribute('name'), "
-      "t2 = b.cells[2].getAttribute('name'); "
-      "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
-      "n1 ? parseInt(n2) - parseInt(n1) : "
-      "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
-  const char *sort_js_code2 =
-      "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
-      "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
-      "};"
-      "window.onload = function() {"
-      "var tb = document.getElementById('tb');"
-      "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
-      "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
-      "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
-      "sc = c; ev.preventDefault();}};"
-      "srt(tb, sc, so, true);"
-      "}"
-      "</script>";
-  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct printdirentrydata d = {c, hm, opts, dir};
-  char tmp[10], buf[MG_PATH_MAX];
-  size_t off, n;
-  int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0);
-  struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
-
-  mg_printf(c,
-            "HTTP/1.1 200 OK\r\n"
-            "Content-Type: text/html; charset=utf-8\r\n"
-            "%s"
-            "Content-Length:         \r\n\r\n",
-            opts->extra_headers == NULL ? "" : opts->extra_headers);
-  off = c->send.len;  // Start of body
-  mg_printf(c,
-            "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
-            "<style>th,td {text-align: left; padding-right: 1em; "
-            "font-family: monospace; }</style></head>"
-            "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
-            "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
-            "<a href=\"#\" rel=\"1\">Modified</a></th>"
-            "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
-            "<tr><td colspan=\"3\"><hr></td></tr>"
-            "</thead>"
-            "<tbody id=\"tb\">\n",
-            (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len,
-            uri.ptr);
-  mg_printf(c, "%s",
-            "  <tr><td><a href=\"..\">..</a></td>"
-            "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
-
-  fs->ls(dir, printdirentry, &d);
-  mg_printf(c,
-            "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
-            "</table><address>Mongoose v.%s</address></body></html>\n",
-            MG_VERSION);
-  n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
-  if (n > sizeof(tmp)) n = 0;
-  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
-  c->is_resp = 0;                          // Mark response end
+int closedir(DIR *d) {
+  int result = 0;
+  if (d != NULL) {
+    if (d->handle != INVALID_HANDLE_VALUE)
+      result = FindClose(d->handle) ? 0 : -1;
+    free(d);
+  } else {
+    result = -1;
+    SetLastError(ERROR_BAD_ARGUMENTS);
+  }
+  return result;
 }
 
-// Resolve requested file into `path` and return its fs->st() result
-static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
-                        struct mg_fs *fs, struct mg_str url, struct mg_str dir,
-                        char *path, size_t path_size) {
-  int flags, tmp;
-  // Append URI to the root_dir, and sanitize it
-  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr);
-  if (n > path_size) n = path_size;
-  path[path_size - 1] = '\0';
-  if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
-  mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n,
-                path_size - n, 0);
-  path[path_size - 1] = '\0';  // Double-check
-  mg_remove_double_dots(path);
-  n = strlen(path);
-  while (n > 1 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
-  flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL);
-  MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path,
-              flags));
-  if (flags == 0) {
-    // Do nothing - let's caller decide
-  } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
-             hm->uri.ptr[hm->uri.len - 1] != '/') {
-    mg_printf(c,
-              "HTTP/1.1 301 Moved\r\n"
-              "Location: %.*s/\r\n"
-              "Content-Length: 0\r\n"
+struct dirent *readdir(DIR *d) {
+  struct dirent *result = NULL;
+  if (d != NULL) {
+    memset(&d->result, 0, sizeof(d->result));
+    if (d->handle != INVALID_HANDLE_VALUE) {
+      result = &d->result;
+      WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
+                          sizeof(result->d_name), NULL, NULL);
+      if (!FindNextFileW(d->handle, &d->info)) {
+        FindClose(d->handle);
+        d->handle = INVALID_HANDLE_VALUE;
+      }
+    } else {
+      SetLastError(ERROR_FILE_NOT_FOUND);
+    }
+  } else {
+    SetLastError(ERROR_BAD_ARGUMENTS);
+  }
+  return result;
+}
+#endif
+
+static void p_list(const char *dir, void (*fn)(const char *, void *),
+                   void *userdata) {
+#if MG_ENABLE_DIRLIST
+  struct dirent *dp;
+  DIR *dirp;
+  if ((dirp = (opendir(dir))) == NULL) return;
+  while ((dp = readdir(dirp)) != NULL) {
+    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
+    fn(dp->d_name, userdata);
+  }
+  closedir(dirp);
+#else
+  (void) dir, (void) fn, (void) userdata;
+#endif
+}
+
+static void *p_open(const char *path, int flags) {
+#if MG_ARCH == MG_ARCH_WIN32
+  const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
+  wchar_t b1[MG_PATH_MAX], b2[10];
+  MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
+  MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
+  return (void *) _wfopen(b1, b2);
+#else
+  const char *mode = flags == MG_FS_READ ? "rbe" : "a+be";  // e for CLOEXEC
+  return (void *) fopen(path, mode);
+#endif
+}
+
+static void p_close(void *fp) {
+  fclose((FILE *) fp);
+}
+
+static size_t p_read(void *fp, void *buf, size_t len) {
+  return fread(buf, 1, len, (FILE *) fp);
+}
+
+static size_t p_write(void *fp, const void *buf, size_t len) {
+  return fwrite(buf, 1, len, (FILE *) fp);
+}
+
+static size_t p_seek(void *fp, size_t offset) {
+#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) ||  \
+    (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
+    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
+  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
+#else
+  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
+#endif
+  return (size_t) ftell((FILE *) fp);
+}
+
+static bool p_rename(const char *from, const char *to) {
+  return rename(from, to) == 0;
+}
+
+static bool p_remove(const char *path) {
+  return remove(path) == 0;
+}
+
+static bool p_mkdir(const char *path) {
+  return mkdir(path, 0775) == 0;
+}
+
+#else
+
+static int p_stat(const char *path, size_t *size, time_t *mtime) {
+  (void) path, (void) size, (void) mtime;
+  return 0;
+}
+static void p_list(const char *path, void (*fn)(const char *, void *),
+                   void *userdata) {
+  (void) path, (void) fn, (void) userdata;
+}
+static void *p_open(const char *path, int flags) {
+  (void) path, (void) flags;
+  return NULL;
+}
+static void p_close(void *fp) {
+  (void) fp;
+}
+static size_t p_read(void *fd, void *buf, size_t len) {
+  (void) fd, (void) buf, (void) len;
+  return 0;
+}
+static size_t p_write(void *fd, const void *buf, size_t len) {
+  (void) fd, (void) buf, (void) len;
+  return 0;
+}
+static size_t p_seek(void *fd, size_t offset) {
+  (void) fd, (void) offset;
+  return (size_t) ~0;
+}
+static bool p_rename(const char *from, const char *to) {
+  (void) from, (void) to;
+  return false;
+}
+static bool p_remove(const char *path) {
+  (void) path;
+  return false;
+}
+static bool p_mkdir(const char *path) {
+  (void) path;
+  return false;
+}
+#endif
+
+struct mg_fs mg_fs_posix = {p_stat,  p_list, p_open,   p_close,  p_read,
+                            p_write, p_seek, p_rename, p_remove, p_mkdir};
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/http.c"
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+static int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
+  int diff = 0;
+  if (len > 0) do {
+      int c = *s1++, d = *s2++;
+      if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
+      if (d >= 'A' && d <= 'Z') d += 'a' - 'A';
+      diff = c - d;
+    } while (diff == 0 && s1[-1] != '\0' && --len > 0);
+  return diff;
+}
+
+bool mg_to_size_t(struct mg_str str, size_t *val);
+bool mg_to_size_t(struct mg_str str, size_t *val) {
+  size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0;
+  while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
+  if (i < str.len && str.buf[i] == '-') return false;
+  while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
+    size_t digit = (size_t) (str.buf[i] - '0');
+    if (result > max2) return false;  // Overflow
+    result *= 10;
+    if (result > max - digit) return false;  // Overflow
+    result += digit;
+    i++, ndigits++;
+  }
+  while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++;
+  if (ndigits == 0) return false;  // #2322: Content-Length = 1 * DIGIT
+  if (i != str.len) return false;  // Ditto
+  *val = (size_t) result;
+  return true;
+}
+
+// Chunk deletion marker is the MSB in the "processed" counter
+#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
+
+// Multipart POST example:
+// --xyz
+// Content-Disposition: form-data; name="val"
+//
+// abcdef
+// --xyz
+// Content-Disposition: form-data; name="foo"; filename="a.txt"
+// Content-Type: text/plain
+//
+// hello world
+//
+// --xyz--
+size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
+                              struct mg_http_part *part) {
+  struct mg_str cd = mg_str_n("Content-Disposition", 19);
+  const char *s = body.buf;
+  size_t b = ofs, h1, h2, b1, b2, max = body.len;
+
+  // Init part params
+  if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
+
+  // Skip boundary
+  while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
+  if (b <= ofs || b + 2 >= max) return 0;
+  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
+
+  // Skip headers
+  h1 = h2 = b + 2;
+  for (;;) {
+    while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
+    if (h2 == h1) break;
+    if (h2 + 2 >= max) return 0;
+    // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
+    if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
+        mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) {
+      struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
+      part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
+      part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
+    }
+    h1 = h2 = h2 + 2;
+  }
+  b1 = b2 = h2 + 2;
+  while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
+                                           memcmp(&s[b2 + 2], s, b - ofs) == 0))
+    b2++;
+
+  if (b2 + 2 >= max) return 0;
+  if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
+  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
+  return b2 + 2;
+}
+
+void mg_http_bauth(struct mg_connection *c, const char *user,
+                   const char *pass) {
+  struct mg_str u = mg_str(user), p = mg_str(pass);
+  size_t need = c->send.len + 36 + (u.len + p.len) * 2;
+  if (c->send.size < need) mg_iobuf_resize(&c->send, need);
+  if (c->send.size >= need) {
+    size_t i, n = 0;
+    char *buf = (char *) &c->send.buf[c->send.len];
+    memcpy(buf, "Authorization: Basic ", 21);  // DON'T use mg_send!
+    for (i = 0; i < u.len; i++) {
+      n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n);
+    }
+    if (p.len > 0) {
+      n = mg_base64_update(':', buf + 21, n);
+      for (i = 0; i < p.len; i++) {
+        n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n);
+      }
+    }
+    n = mg_base64_final(buf + 21, n);
+    c->send.len += 21 + (size_t) n + 2;
+    memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
+  } else {
+    MG_ERROR(("%lu oom %d->%d ", c->id, (int) c->send.size, (int) need));
+  }
+}
+
+struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
+  struct mg_str entry, k, v, result = mg_str_n(NULL, 0);
+  while (mg_span(buf, &entry, &buf, '&')) {
+    if (mg_span(entry, &k, &v, '=') && name.len == k.len &&
+        mg_ncasecmp(name.buf, k.buf, k.len) == 0) {
+      result = v;
+      break;
+    }
+  }
+  return result;
+}
+
+int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
+                    size_t dst_len) {
+  int len;
+  if (dst != NULL && dst_len > 0) {
+    dst[0] = '\0';  // If destination buffer is valid, always nul-terminate it
+  }
+  if (dst == NULL || dst_len == 0) {
+    len = -2;  // Bad destination
+  } else if (buf->buf == NULL || name == NULL || buf->len == 0) {
+    len = -1;  // Bad source
+  } else {
+    struct mg_str v = mg_http_var(*buf, mg_str(name));
+    if (v.buf == NULL) {
+      len = -4;  // Name does not exist
+    } else {
+      len = mg_url_decode(v.buf, v.len, dst, dst_len, 1);
+      if (len < 0) len = -3;  // Failed to decode
+    }
+  }
+  return len;
+}
+
+static bool isx(int c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+         (c >= 'A' && c <= 'F');
+}
+
+int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
+                  int is_form_url_encoded) {
+  size_t i, j;
+  for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
+    if (src[i] == '%') {
+      // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
+      if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
+        mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t));
+        i += 2;
+      } else {
+        return -1;
+      }
+    } else if (is_form_url_encoded && src[i] == '+') {
+      dst[j] = ' ';
+    } else {
+      dst[j] = src[i];
+    }
+  }
+  if (j < dst_len) dst[j] = '\0';  // Null-terminate the destination
+  return i >= src_len && j < dst_len ? (int) j : -1;
+}
+
+static bool isok(uint8_t c) {
+  return c == '\n' || c == '\r' || c >= ' ';
+}
+
+int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
+  size_t i;
+  for (i = 0; i < buf_len; i++) {
+    if (!isok(buf[i])) return -1;
+    if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
+        (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
+      return (int) i + 1;
+  }
+  return 0;
+}
+struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
+  size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
+  for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
+    struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
+    if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v;
+  }
+  return NULL;
+}
+
+// Is it a valid utf-8 continuation byte
+static bool vcb(uint8_t c) {
+  return (c & 0xc0) == 0x80;
+}
+
+// Get character length (valid utf-8). Used to parse method, URI, headers
+static size_t clen(const char *s, const char *end) {
+  const unsigned char *u = (unsigned char *) s, c = *u;
+  long n = (long) (end - s);
+  if (c > ' ' && c < '~') return 1;  // Usual ascii printed char
+  if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2;  // 2-byte UTF8
+  if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3;
+  if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3]))
+    return 4;
+  return 0;
+}
+
+// Skip until the newline. Return advanced `s`, or NULL on error
+static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
+  v->buf = (char *) s;
+  while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;  // To newline
+  if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL;    // Stray \r
+  if (s < end && s[0] == '\r') s++;                               // Skip \r
+  if (s >= end || *s++ != '\n') return NULL;                      // Skip \n
+  return s;
+}
+
+static bool mg_http_parse_headers(const char *s, const char *end,
+                                  struct mg_http_header *h, size_t max_hdrs) {
+  size_t i, n;
+  for (i = 0; i < max_hdrs; i++) {
+    struct mg_str k = {NULL, 0}, v = {NULL, 0};
+    if (s >= end) return false;
+    if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
+    k.buf = (char *) s;
+    while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n;
+    if (k.len == 0) return false;                     // Empty name
+    if (s >= end || clen(s, end) == 0) return false;  // Invalid UTF-8
+    if (*s++ != ':') return false;  // Invalid, not followed by :
+    // if (clen(s, end) == 0) return false;        // Invalid UTF-8
+    while (s < end && s[0] == ' ') s++;  // Skip spaces
+    if ((s = skiptorn(s, end, &v)) == NULL) return false;
+    while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--;  // Trim spaces
+    // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf));
+    h[i].name = k, h[i].value = v;  // Success. Assign values
+  }
+  return true;
+}
+
+int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
+  int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
+  const char *end = s == NULL ? NULL : s + req_len, *qs;  // Cannot add to NULL
+  const struct mg_str *cl;
+  size_t n;
+
+  memset(hm, 0, sizeof(*hm));
+  if (req_len <= 0) return req_len;
+
+  hm->message.buf = hm->head.buf = (char *) s;
+  hm->body.buf = (char *) end;
+  hm->head.len = (size_t) req_len;
+  hm->message.len = hm->body.len = (size_t) -1;  // Set body length to infinite
+
+  // Parse request line
+  hm->method.buf = (char *) s;
+  while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n;
+  while (s < end && s[0] == ' ') s++;  // Skip spaces
+  hm->uri.buf = (char *) s;
+  while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n;
+  while (s < end && s[0] == ' ') s++;  // Skip spaces
+  if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
+
+  // If URI contains '?' character, setup query string
+  if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) {
+    hm->query.buf = (char *) qs + 1;
+    hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1));
+    hm->uri.len = (size_t) (qs - hm->uri.buf);
+  }
+
+  // Sanity check. Allow protocol/reason to be empty
+  // Do this check after hm->method.len and hm->uri.len are finalised
+  if (hm->method.len == 0 || hm->uri.len == 0) return -1;
+
+  if (!mg_http_parse_headers(s, end, hm->headers,
+                             sizeof(hm->headers) / sizeof(hm->headers[0])))
+    return -1;  // error when parsing
+  if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
+    if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
+    hm->message.len = (size_t) req_len + hm->body.len;
+  }
+
+  // mg_http_parse() is used to parse both HTTP requests and HTTP
+  // responses. If HTTP response does not have Content-Length set, then
+  // body is read until socket is closed, i.e. body.len is infinite (~0).
+  //
+  // For HTTP requests though, according to
+  // http://tools.ietf.org/html/rfc7231#section-8.1.3,
+  // only POST and PUT methods have defined body semantics.
+  // Therefore, if Content-Length is not specified and methods are
+  // not one of PUT or POST, set body length to 0.
+  //
+  // So, if it is HTTP request, and Content-Length is not set,
+  // and method is not (PUT or POST) then reset body length to zero.
+  is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0;
+  if (hm->body.len == (size_t) ~0 && !is_response &&
+      mg_strcasecmp(hm->method, mg_str("PUT")) != 0 &&
+      mg_strcasecmp(hm->method, mg_str("POST")) != 0) {
+    hm->body.len = 0;
+    hm->message.len = (size_t) req_len;
+  }
+
+  // The 204 (No content) responses also have 0 body length
+  if (hm->body.len == (size_t) ~0 && is_response &&
+      mg_strcasecmp(hm->uri, mg_str("204")) == 0) {
+    hm->body.len = 0;
+    hm->message.len = (size_t) req_len;
+  }
+  if (hm->message.len < (size_t) req_len) return -1;  // Overflow protection
+
+  return req_len;
+}
+
+static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
+                                  va_list *ap) {
+  size_t len = c->send.len;
+  mg_send(c, "        \r\n", 10);
+  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
+  if (c->send.len >= len + 10) {
+    mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
+    c->send.buf[len + 8] = '\r';
+    if (c->send.len == len + 10) c->is_resp = 0;  // Last chunk, reset marker
+  }
+  mg_send(c, "\r\n", 2);
+}
+
+void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  mg_http_vprintf_chunk(c, fmt, &ap);
+  va_end(ap);
+}
+
+void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
+  mg_printf(c, "%lx\r\n", (unsigned long) len);
+  mg_send(c, buf, len);
+  mg_send(c, "\r\n", 2);
+  if (len == 0) c->is_resp = 0;
+}
+
+// clang-format off
+static const char *mg_http_status_code_str(int status_code) {
+  switch (status_code) {
+    case 100: return "Continue";
+    case 101: return "Switching Protocols";
+    case 102: return "Processing";
+    case 200: return "OK";
+    case 201: return "Created";
+    case 202: return "Accepted";
+    case 203: return "Non-authoritative Information";
+    case 204: return "No Content";
+    case 205: return "Reset Content";
+    case 206: return "Partial Content";
+    case 207: return "Multi-Status";
+    case 208: return "Already Reported";
+    case 226: return "IM Used";
+    case 300: return "Multiple Choices";
+    case 301: return "Moved Permanently";
+    case 302: return "Found";
+    case 303: return "See Other";
+    case 304: return "Not Modified";
+    case 305: return "Use Proxy";
+    case 307: return "Temporary Redirect";
+    case 308: return "Permanent Redirect";
+    case 400: return "Bad Request";
+    case 401: return "Unauthorized";
+    case 402: return "Payment Required";
+    case 403: return "Forbidden";
+    case 404: return "Not Found";
+    case 405: return "Method Not Allowed";
+    case 406: return "Not Acceptable";
+    case 407: return "Proxy Authentication Required";
+    case 408: return "Request Timeout";
+    case 409: return "Conflict";
+    case 410: return "Gone";
+    case 411: return "Length Required";
+    case 412: return "Precondition Failed";
+    case 413: return "Payload Too Large";
+    case 414: return "Request-URI Too Long";
+    case 415: return "Unsupported Media Type";
+    case 416: return "Requested Range Not Satisfiable";
+    case 417: return "Expectation Failed";
+    case 418: return "I'm a teapot";
+    case 421: return "Misdirected Request";
+    case 422: return "Unprocessable Entity";
+    case 423: return "Locked";
+    case 424: return "Failed Dependency";
+    case 426: return "Upgrade Required";
+    case 428: return "Precondition Required";
+    case 429: return "Too Many Requests";
+    case 431: return "Request Header Fields Too Large";
+    case 444: return "Connection Closed Without Response";
+    case 451: return "Unavailable For Legal Reasons";
+    case 499: return "Client Closed Request";
+    case 500: return "Internal Server Error";
+    case 501: return "Not Implemented";
+    case 502: return "Bad Gateway";
+    case 503: return "Service Unavailable";
+    case 504: return "Gateway Timeout";
+    case 505: return "HTTP Version Not Supported";
+    case 506: return "Variant Also Negotiates";
+    case 507: return "Insufficient Storage";
+    case 508: return "Loop Detected";
+    case 510: return "Not Extended";
+    case 511: return "Network Authentication Required";
+    case 599: return "Network Connect Timeout Error";
+    default: return "";
+  }
+}
+// clang-format on
+
+void mg_http_reply(struct mg_connection *c, int code, const char *headers,
+                   const char *fmt, ...) {
+  va_list ap;
+  size_t len;
+  mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length:            \r\n\r\n", code,
+            mg_http_status_code_str(code), headers == NULL ? "" : headers);
+  len = c->send.len;
+  va_start(ap, fmt);
+  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
+  va_end(ap);
+  if (c->send.len > 16) {
+    size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu",
+                           (unsigned long) (c->send.len - len));
+    c->send.buf[len - 15 + n] = ' ';  // Change ending 0 to space
+  }
+  c->is_resp = 0;
+}
+
+static void http_cb(struct mg_connection *, int, void *);
+static void restore_http_cb(struct mg_connection *c) {
+  mg_fs_close((struct mg_fd *) c->pfn_data);
+  c->pfn_data = NULL;
+  c->pfn = http_cb;
+  c->is_resp = 0;
+}
+
+char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
+char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
+  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
+  return buf;
+}
+
+static void static_cb(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
+    struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
+    // Read to send IO buffer directly, avoid extra on-stack buffer
+    size_t n, max = MG_IO_SIZE, space;
+    size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
+                                     sizeof(size_t) * sizeof(size_t)];
+    if (c->send.size < max) mg_iobuf_resize(&c->send, max);
+    if (c->send.len >= c->send.size) return;  // Rate limit
+    if ((space = c->send.size - c->send.len) > *cl) space = *cl;
+    n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
+    c->send.len += n;
+    *cl -= n;
+    if (n == 0) restore_http_cb(c);
+  } else if (ev == MG_EV_CLOSE) {
+    restore_http_cb(c);
+  }
+  (void) ev_data;
+}
+
+// Known mime types. Keep it outside guess_content_type() function, since
+// some environments don't like it defined there.
+// clang-format off
+#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 }
+static struct mg_str s_known_types[] = {
+    MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
+    MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
+    MG_C_STR("gif"), MG_C_STR("image/gif"),
+    MG_C_STR("png"), MG_C_STR("image/png"),
+    MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
+    MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
+    MG_C_STR("woff"), MG_C_STR("font/woff"),
+    MG_C_STR("ttf"), MG_C_STR("font/ttf"),
+    MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
+    MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
+    MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
+    MG_C_STR("csv"), MG_C_STR("text/csv"),
+    MG_C_STR("doc"), MG_C_STR("application/msword"),
+    MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
+    MG_C_STR("gz"), MG_C_STR("application/gzip"),
+    MG_C_STR("ico"), MG_C_STR("image/x-icon"),
+    MG_C_STR("json"), MG_C_STR("application/json"),
+    MG_C_STR("mov"), MG_C_STR("video/quicktime"),
+    MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
+    MG_C_STR("mp4"), MG_C_STR("video/mp4"),
+    MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
+    MG_C_STR("pdf"), MG_C_STR("application/pdf"),
+    MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
+    MG_C_STR("wav"), MG_C_STR("audio/wav"),
+    MG_C_STR("webp"), MG_C_STR("image/webp"),
+    MG_C_STR("zip"), MG_C_STR("application/zip"),
+    MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
+    {0, 0},
+};
+// clang-format on
+
+static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
+  struct mg_str entry, k, v, s = mg_str(extra);
+  size_t i = 0;
+
+  // Shrink path to its extension only
+  while (i < path.len && path.buf[path.len - i - 1] != '.') i++;
+  path.buf += path.len - i;
+  path.len = i;
+
+  // Process user-provided mime type overrides, if any
+  while (mg_span(s, &entry, &s, ',')) {
+    if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0) return v;
+  }
+
+  // Process built-in mime types
+  for (i = 0; s_known_types[i].buf != NULL; i += 2) {
+    if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1];
+  }
+
+  return mg_str("text/plain; charset=utf-8");
+}
+
+static int getrange(struct mg_str *s, size_t *a, size_t *b) {
+  size_t i, numparsed = 0;
+  for (i = 0; i + 6 < s->len; i++) {
+    struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6);
+    if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue;
+    if (mg_span(v, &k, &v, '-')) {
+      if (mg_to_size_t(k, a)) numparsed++;
+      if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
+    } else {
+      if (mg_to_size_t(v, a)) numparsed++;
+    }
+    break;
+  }
+  return (int) numparsed;
+}
+
+void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
+                        const char *path,
+                        const struct mg_http_serve_opts *opts) {
+  char etag[64], tmp[MG_PATH_MAX];
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
+  struct mg_fd *fd = NULL;
+  size_t size = 0;
+  time_t mtime = 0;
+  struct mg_str *inm = NULL;
+  struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
+  bool gzip = false;
+
+  if (path != NULL) {
+    // If a browser sends us "Accept-Encoding: gzip", try to open .gz first
+    struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
+    if (ae != NULL) {
+      char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf);
+      if (ae_ != NULL && strstr(ae_, "gzip") != NULL) {
+        mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
+        fd = mg_fs_open(fs, tmp, MG_FS_READ);
+        if (fd != NULL) gzip = true, path = tmp;
+      }
+      free(ae_);
+    }
+    // No luck opening .gz? Open what we've told to open
+    if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
+  }
+
+  // Failed to open, and page404 is configured? Open it, then
+  if (fd == NULL && opts->page404 != NULL) {
+    fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
+    path = opts->page404;
+    mime = guess_content_type(mg_str(path), opts->mime_types);
+  }
+
+  if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
+    mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
+    mg_fs_close(fd);
+    // NOTE: mg_http_etag() call should go first!
+  } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
+             (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
+             mg_strcasecmp(*inm, mg_str(etag)) == 0) {
+    mg_fs_close(fd);
+    mg_http_reply(c, 304, opts->extra_headers, "");
+  } else {
+    int n, status = 200;
+    char range[100];
+    size_t r1 = 0, r2 = 0, cl = size;
+
+    // Handle Range header
+    struct mg_str *rh = mg_http_get_header(hm, "Range");
+    range[0] = '\0';
+    if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
+      // If range is specified like "400-", set second limit to content len
+      if (n == 1) r2 = cl - 1;
+      if (r1 > r2 || r2 >= cl) {
+        status = 416;
+        cl = 0;
+        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
+                    (int64_t) size);
+      } else {
+        status = 206;
+        cl = r2 - r1 + 1;
+        mg_snprintf(range, sizeof(range),
+                    "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
+                    (uint64_t) (r1 + cl - 1), (uint64_t) size);
+        fs->sk(fd->fd, r1);
+      }
+    }
+    mg_printf(c,
+              "HTTP/1.1 %d %s\r\n"
+              "Content-Type: %.*s\r\n"
+              "Etag: %s\r\n"
+              "Content-Length: %llu\r\n"
+              "%s%s%s\r\n",
+              status, mg_http_status_code_str(status), (int) mime.len, mime.buf,
+              etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
+              range, opts->extra_headers ? opts->extra_headers : "");
+    if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) {
+      c->is_draining = 1;
+      c->is_resp = 0;
+      mg_fs_close(fd);
+    } else {
+      // Track to-be-sent content length at the end of c->data, aligned
+      size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
+                                        sizeof(size_t) * sizeof(size_t)];
+      c->pfn = static_cb;
+      c->pfn_data = fd;
+      *clp = cl;
+    }
+  }
+}
+
+struct printdirentrydata {
+  struct mg_connection *c;
+  struct mg_http_message *hm;
+  const struct mg_http_serve_opts *opts;
+  const char *dir;
+};
+
+#if MG_ENABLE_DIRLIST
+static void printdirentry(const char *name, void *userdata) {
+  struct printdirentrydata *d = (struct printdirentrydata *) userdata;
+  struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
+  size_t size = 0;
+  time_t t = 0;
+  char path[MG_PATH_MAX], sz[40], mod[40];
+  int flags, n = 0;
+
+  // MG_DEBUG(("[%s] [%s]", d->dir, name));
+  if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
+      sizeof(path)) {
+    MG_ERROR(("%s truncated", name));
+  } else if ((flags = fs->st(path, &size, &t)) == 0) {
+    MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
+  } else {
+    const char *slash = flags & MG_FS_DIR ? "/" : "";
+    if (flags & MG_FS_DIR) {
+      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
+    } else {
+      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
+    }
+#if defined(MG_HTTP_DIRLIST_TIME_FMT)
+    {
+      char time_str[40];
+      struct tm *time_info = localtime(&t);
+      strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
+      mg_snprintf(mod, sizeof(mod), "%s", time_str);
+    }
+#else
+    mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
+#endif
+    n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
+    mg_printf(d->c,
+              "  <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
+              "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
+              n, path, slash, name, slash, (unsigned long) t, mod,
+              flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
+  }
+}
+
+static void listdir(struct mg_connection *c, struct mg_http_message *hm,
+                    const struct mg_http_serve_opts *opts, char *dir) {
+  const char *sort_js_code =
+      "<script>function srt(tb, sc, so, d) {"
+      "var tr = Array.prototype.slice.call(tb.rows, 0),"
+      "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
+      "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
+      "t1 = a.cells[2].getAttribute('name'), "
+      "t2 = b.cells[2].getAttribute('name'); "
+      "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
+      "n1 ? parseInt(n2) - parseInt(n1) : "
+      "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
+  const char *sort_js_code2 =
+      "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
+      "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
+      "};"
+      "window.onload = function() {"
+      "var tb = document.getElementById('tb');"
+      "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
+      "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
+      "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
+      "sc = c; ev.preventDefault();}};"
+      "srt(tb, sc, so, true);"
+      "}"
+      "</script>";
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
+  struct printdirentrydata d = {c, hm, opts, dir};
+  char tmp[10], buf[MG_PATH_MAX];
+  size_t off, n;
+  int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0);
+  struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
+
+  mg_printf(c,
+            "HTTP/1.1 200 OK\r\n"
+            "Content-Type: text/html; charset=utf-8\r\n"
+            "%s"
+            "Content-Length:         \r\n\r\n",
+            opts->extra_headers == NULL ? "" : opts->extra_headers);
+  off = c->send.len;  // Start of body
+  mg_printf(c,
+            "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
+            "<style>th,td {text-align: left; padding-right: 1em; "
+            "font-family: monospace; }</style></head>"
+            "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
+            "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
+            "<a href=\"#\" rel=\"1\">Modified</a></th>"
+            "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
+            "<tr><td colspan=\"3\"><hr></td></tr>"
+            "</thead>"
+            "<tbody id=\"tb\">\n",
+            (int) uri.len, uri.buf, sort_js_code, sort_js_code2, (int) uri.len,
+            uri.buf);
+  mg_printf(c, "%s",
+            "  <tr><td><a href=\"..\">..</a></td>"
+            "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
+
+  fs->ls(dir, printdirentry, &d);
+  mg_printf(c,
+            "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
+            "</table><address>Mongoose v.%s</address></body></html>\n",
+            MG_VERSION);
+  n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
+  if (n > sizeof(tmp)) n = 0;
+  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
+  c->is_resp = 0;                          // Mark response end
+}
+#endif
+
+// Resolve requested file into `path` and return its fs->st() result
+static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
+                        struct mg_fs *fs, struct mg_str url, struct mg_str dir,
+                        char *path, size_t path_size) {
+  int flags, tmp;
+  // Append URI to the root_dir, and sanitize it
+  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf);
+  if (n + 2 >= path_size) {
+    mg_http_reply(c, 400, "", "Exceeded path size");
+    return -1;
+  }
+  path[path_size - 1] = '\0';
+  // Terminate root dir with slash
+  if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
+  if (url.len < hm->uri.len) {
+    mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n,
+                  path_size - n, 0);
+  }
+  path[path_size - 1] = '\0';  // Double-check
+  if (!mg_path_is_sane(mg_str_n(path, path_size))) {
+    mg_http_reply(c, 400, "", "Invalid path");
+    return -1;
+  }
+  n = strlen(path);
+  while (n > 1 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
+  flags = mg_strcmp(hm->uri, mg_str("/")) == 0 ? MG_FS_DIR
+                                               : fs->st(path, NULL, NULL);
+  MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path,
+              flags));
+  if (flags == 0) {
+    // Do nothing - let's caller decide
+  } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
+             hm->uri.buf[hm->uri.len - 1] != '/') {
+    mg_printf(c,
+              "HTTP/1.1 301 Moved\r\n"
+              "Location: %.*s/\r\n"
+              "Content-Length: 0\r\n"
               "\r\n",
-              (int) hm->uri.len, hm->uri.ptr);
+              (int) hm->uri.len, hm->uri.buf);
     c->is_resp = 0;
     flags = -1;
   } else if (flags & MG_FS_DIR) {
@@ -2057,3318 +3036,11754 @@  static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
       path[n + 1 + strlen(MG_HTTP_INDEX)] =
           '\0';  // Remove appended .gz in index file name
     } else {
-      path[n] = '\0';  // Remove appended index file name
+      path[n] = '\0';  // Remove appended index file name
+    }
+  }
+  return flags;
+}
+
+static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
+                       const struct mg_http_serve_opts *opts, char *path,
+                       size_t path_size) {
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
+  struct mg_str k, v, part, s = mg_str(opts->root_dir), u = {NULL, 0}, p = u;
+  while (mg_span(s, &part, &s, ',')) {
+    if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0);
+    if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
+    if (hm->uri.len < k.len) continue;
+    if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue;
+    u = k, p = v;
+  }
+  return uri_to_path2(c, hm, fs, u, p, path, path_size);
+}
+
+void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
+                       const struct mg_http_serve_opts *opts) {
+  char path[MG_PATH_MAX];
+  const char *sp = opts->ssi_pattern;
+  int flags = uri_to_path(c, hm, opts, path, sizeof(path));
+  if (flags < 0) {
+    // Do nothing: the response has already been sent by uri_to_path()
+  } else if (flags & MG_FS_DIR) {
+#if MG_ENABLE_DIRLIST
+    listdir(c, hm, opts, path);
+#else
+    mg_http_reply(c, 403, "", "Forbidden\n");
+#endif
+  } else if (flags && sp != NULL && mg_match(mg_str(path), mg_str(sp), NULL)) {
+    mg_http_serve_ssi(c, opts->root_dir, path);
+  } else {
+    mg_http_serve_file(c, hm, path, opts);
+  }
+}
+
+static bool mg_is_url_safe(int c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
+         (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
+}
+
+size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
+  size_t i, n = 0;
+  for (i = 0; i < sl; i++) {
+    int c = *(unsigned char *) &s[i];
+    if (n + 4 >= len) return 0;
+    if (mg_is_url_safe(c)) {
+      buf[n++] = s[i];
+    } else {
+      mg_snprintf(&buf[n], 4, "%%%M", mg_print_hex, 1, &s[i]);
+      n += 3;
+    }
+  }
+  if (len > 0 && n < len - 1) buf[n] = '\0';  // Null-terminate the destination
+  if (len > 0) buf[len - 1] = '\0';           // Always.
+  return n;
+}
+
+void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
+                   char *pass, size_t passlen) {
+  struct mg_str *v = mg_http_get_header(hm, "Authorization");
+  user[0] = pass[0] = '\0';
+  if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) {
+    char buf[256];
+    size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf));
+    const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0);
+    if (p != NULL) {
+      mg_snprintf(user, userlen, "%.*s", p - buf, buf);
+      mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1);
+    }
+  } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) {
+    mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7);
+  } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
+    struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12));
+    if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf);
+  } else {
+    mg_http_get_var(&hm->query, "access_token", pass, passlen);
+  }
+}
+
+static struct mg_str stripquotes(struct mg_str s) {
+  return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"'
+             ? mg_str_n(s.buf + 1, s.len - 2)
+             : s;
+}
+
+struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
+  size_t i;
+  for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
+    if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) {
+      const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len];
+      int q = p < x && *p == '"' ? 1 : 0;
+      while (p < x &&
+             (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
+        p++;
+      // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len,
+      // v.buf, (int) (p - b), b));
+      return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
+    }
+  }
+  return mg_str_n(NULL, 0);
+}
+
+long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
+                    struct mg_fs *fs, const char *dir, size_t max_size) {
+  char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
+  long res = 0, offset;
+  mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
+  mg_http_get_var(&hm->query, "file", file, sizeof(file));
+  offset = strtol(buf, NULL, 0);
+  mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
+  if (hm->body.len == 0) {
+    mg_http_reply(c, 200, "", "%ld", res);  // Nothing to write
+  } else if (file[0] == '\0') {
+    mg_http_reply(c, 400, "", "file required");
+    res = -1;
+  } else if (mg_path_is_sane(mg_str(file)) == false) {
+    mg_http_reply(c, 400, "", "%s: invalid file", file);
+    res = -2;
+  } else if (offset < 0) {
+    mg_http_reply(c, 400, "", "offset required");
+    res = -3;
+  } else if ((size_t) offset + hm->body.len > max_size) {
+    mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
+                  (unsigned long) max_size);
+    res = -4;
+  } else {
+    struct mg_fd *fd;
+    size_t current_size = 0;
+    MG_DEBUG(("%s -> %lu bytes @ %ld", path, hm->body.len, offset));
+    if (offset == 0) fs->rm(path);  // If offset if 0, truncate file
+    fs->st(path, &current_size, NULL);
+    if (offset > 0 && current_size != (size_t) offset) {
+      mg_http_reply(c, 400, "", "%s: offset mismatch", path);
+      res = -5;
+    } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
+      mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
+      res = -6;
+    } else {
+      res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len);
+      mg_fs_close(fd);
+      mg_http_reply(c, 200, "", "%ld", res);
+    }
+  }
+  return res;
+}
+
+int mg_http_status(const struct mg_http_message *hm) {
+  return atoi(hm->uri.buf);
+}
+
+static bool is_hex_digit(int c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+         (c >= 'A' && c <= 'F');
+}
+
+static int skip_chunk(const char *buf, int len, int *pl, int *dl) {
+  int i = 0, n = 0;
+  if (len < 3) return 0;
+  while (i < len && is_hex_digit(buf[i])) i++;
+  if (i == 0) return -1;                     // Error, no length specified
+  if (i > (int) sizeof(int) * 2) return -1;  // Chunk length is too big
+  if (len < i + 1 || buf[i] != '\r' || buf[i + 1] != '\n') return -1;  // Error
+  if (mg_str_to_num(mg_str_n(buf, (size_t) i), 16, &n, sizeof(int)) == false)
+    return -1;                    // Decode chunk length, overflow
+  if (n < 0) return -1;           // Error. TODO(): some checks now redundant
+  if (n > len - i - 4) return 0;  // Chunk not yet fully buffered
+  if (buf[i + n + 2] != '\r' || buf[i + n + 3] != '\n') return -1;  // Error
+  *pl = i + 2, *dl = n;
+  return i + 2 + n + 2;
+}
+
+static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
+    struct mg_http_message hm;
+    size_t ofs = 0;  // Parsing offset
+    while (c->is_resp == 0 && ofs < c->recv.len) {
+      const char *buf = (char *) c->recv.buf + ofs;
+      int n = mg_http_parse(buf, c->recv.len - ofs, &hm);
+      struct mg_str *te;  // Transfer - encoding header
+      bool is_chunked = false;
+      if (n < 0) {
+        // We don't use mg_error() here, to avoid closing pipelined requests
+        // prematurely, see #2592
+        MG_ERROR(("HTTP parse, %lu bytes", c->recv.len));
+        c->is_draining = 1;
+        mg_hexdump(buf, c->recv.len - ofs > 16 ? 16 : c->recv.len - ofs);
+        c->recv.len = 0;
+        return;
+      }
+      if (n == 0) break;                 // Request is not buffered yet
+      mg_call(c, MG_EV_HTTP_HDRS, &hm);  // Got all HTTP headers
+      if (ev == MG_EV_CLOSE) {           // If client did not set Content-Length
+        hm.message.len = c->recv.len - ofs;  // and closes now, deliver MSG
+        hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf);
+      }
+      if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
+        if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
+          is_chunked = true;
+        } else {
+          mg_error(c, "Invalid Transfer-Encoding");  // See #2460
+          return;
+        }
+      } else if (mg_http_get_header(&hm, "Content-length") == NULL) {
+        // #2593: HTTP packets must contain either Transfer-Encoding or
+        // Content-length
+        bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0;
+        bool require_content_len = false;
+        if (!is_response && (mg_strcasecmp(hm.method, mg_str("POST")) == 0 ||
+                             mg_strcasecmp(hm.method, mg_str("PUT")) == 0)) {
+          // POST and PUT should include an entity body. Therefore, they should
+          // contain a Content-length header. Other requests can also contain a
+          // body, but their content has no defined semantics (RFC 7231)
+          require_content_len = true;
+        } else if (is_response) {
+          // HTTP spec 7.2 Entity body: All other responses must include a body
+          // or Content-Length header field defined with a value of 0.
+          int status = mg_http_status(&hm);
+          require_content_len = status >= 200 && status != 204 && status != 304;
+        }
+        if (require_content_len) {
+          mg_http_reply(c, 411, "", "");
+          MG_ERROR(("%s", "Content length missing from request"));
+        }
+      }
+
+      if (is_chunked) {
+        // For chunked data, strip off prefixes and suffixes from chunks
+        // and relocate them right after the headers, then report a message
+        char *s = (char *) c->recv.buf + ofs + n;
+        int o = 0, pl, dl, cl, len = (int) (c->recv.len - ofs - (size_t) n);
+
+        // Find zero-length chunk (the end of the body)
+        while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0 && dl) o += cl;
+        if (cl == 0) break;  // No zero-len chunk, buffer more data
+        if (cl < 0) {
+          mg_error(c, "Invalid chunk");
+          break;
+        }
+
+        // Zero chunk found. Second pass: strip + relocate
+        o = 0, hm.body.len = 0, hm.message.len = (size_t) n;
+        while ((cl = skip_chunk(s + o, len - o, &pl, &dl)) > 0) {
+          memmove(s + hm.body.len, s + o + pl, (size_t) dl);
+          o += cl, hm.body.len += (size_t) dl, hm.message.len += (size_t) dl;
+          if (dl == 0) break;
+        }
+        ofs += (size_t) (n + o);
+      } else {  // Normal, non-chunked data
+        size_t len = c->recv.len - ofs - (size_t) n;
+        if (hm.body.len > len) break;  // Buffer more data
+        ofs += (size_t) n + hm.body.len;
+      }
+
+      if (c->is_accepted) c->is_resp = 1;  // Start generating response
+      mg_call(c, MG_EV_HTTP_MSG, &hm);     // User handler can clear is_resp
+    }
+    if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs);  // Delete processed data
+  }
+  (void) ev_data;
+}
+
+static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_HTTP_MSG) {
+    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
+    if (mg_match(hm->uri, mg_str("/quit"), NULL)) {
+      mg_http_reply(c, 200, "", "ok\n");
+      c->is_draining = 1;
+      c->data[0] = 'X';
+    } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) {
+      int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
+      mg_log_set(level);
+      mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
+    } else {
+      mg_http_reply(c, 200, "", "hi\n");
+    }
+  } else if (ev == MG_EV_CLOSE) {
+    if (c->data[0] == 'X') *(bool *) c->fn_data = true;
+  }
+}
+
+void mg_hello(const char *url) {
+  struct mg_mgr mgr;
+  bool done = false;
+  mg_mgr_init(&mgr);
+  if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
+  while (done == false) mg_mgr_poll(&mgr, 100);
+  mg_mgr_free(&mgr);
+}
+
+struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url,
+                                      mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
+  if (c != NULL) c->pfn = http_cb;
+  return c;
+}
+
+struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
+                                     mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
+  if (c != NULL) c->pfn = http_cb;
+  return c;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/iobuf.c"
+#endif
+
+
+
+
+
+static size_t roundup(size_t size, size_t align) {
+  return align == 0 ? size : (size + align - 1) / align * align;
+}
+
+int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
+  int ok = 1;
+  new_size = roundup(new_size, io->align);
+  if (new_size == 0) {
+    mg_bzero(io->buf, io->size);
+    free(io->buf);
+    io->buf = NULL;
+    io->len = io->size = 0;
+  } else if (new_size != io->size) {
+    // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
+    // porting to some obscure platforms like FreeRTOS
+    void *p = calloc(1, new_size);
+    if (p != NULL) {
+      size_t len = new_size < io->len ? new_size : io->len;
+      if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
+      mg_bzero(io->buf, io->size);
+      free(io->buf);
+      io->buf = (unsigned char *) p;
+      io->size = new_size;
+    } else {
+      ok = 0;
+      MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
+    }
+  }
+  return ok;
+}
+
+int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
+  io->buf = NULL;
+  io->align = align;
+  io->size = io->len = 0;
+  return mg_iobuf_resize(io, size);
+}
+
+size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
+                    size_t len) {
+  size_t new_size = roundup(io->len + len, io->align);
+  mg_iobuf_resize(io, new_size);      // Attempt to resize
+  if (new_size != io->size) len = 0;  // Resize failure, append nothing
+  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
+  if (buf != NULL) memmove(io->buf + ofs, buf, len);
+  if (ofs > io->len) io->len += ofs - io->len;
+  io->len += len;
+  return len;
+}
+
+size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
+  if (ofs > io->len) ofs = io->len;
+  if (ofs + len > io->len) len = io->len - ofs;
+  if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
+  if (io->buf) mg_bzero(io->buf + io->len - len, len);
+  io->len -= len;
+  return len;
+}
+
+void mg_iobuf_free(struct mg_iobuf *io) {
+  mg_iobuf_resize(io, 0);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/json.c"
+#endif
+
+
+
+
+static const char *escapeseq(int esc) {
+  return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
+}
+
+static char json_esc(int c, int esc) {
+  const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
+  for (p = esc1; *p != '\0'; p++) {
+    if (*p == c) return esc2[p - esc1];
+  }
+  return 0;
+}
+
+static int mg_pass_string(const char *s, int len) {
+  int i;
+  for (i = 0; i < len; i++) {
+    if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
+      i++;
+    } else if (s[i] == '\0') {
+      return MG_JSON_INVALID;
+    } else if (s[i] == '"') {
+      return i;
+    }
+  }
+  return MG_JSON_INVALID;
+}
+
+static double mg_atod(const char *p, int len, int *numlen) {
+  double d = 0.0;
+  int i = 0, sign = 1;
+
+  // Sign
+  if (i < len && *p == '-') {
+    sign = -1, i++;
+  } else if (i < len && *p == '+') {
+    i++;
+  }
+
+  // Decimal
+  for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
+    d *= 10.0;
+    d += p[i] - '0';
+  }
+  d *= sign;
+
+  // Fractional
+  if (i < len && p[i] == '.') {
+    double frac = 0.0, base = 0.1;
+    i++;
+    for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
+      frac += base * (p[i] - '0');
+      base /= 10.0;
+    }
+    d += frac * sign;
+  }
+
+  // Exponential
+  if (i < len && (p[i] == 'e' || p[i] == 'E')) {
+    int j, exp = 0, minus = 0;
+    i++;
+    if (i < len && p[i] == '-') minus = 1, i++;
+    if (i < len && p[i] == '+') i++;
+    while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
+      exp = exp * 10 + (p[i++] - '0');
+    if (minus) exp = -exp;
+    for (j = 0; j < exp; j++) d *= 10.0;
+    for (j = 0; j < -exp; j++) d /= 10.0;
+  }
+
+  if (numlen != NULL) *numlen = i;
+  return d;
+}
+
+// Iterate over object or array elements
+size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key,
+                    struct mg_str *val) {
+  if (ofs >= obj.len) {
+    ofs = 0;  // Out of boundaries, stop scanning
+  } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) {
+    ofs = 0;  // Not an array or object, stop
+  } else {
+    struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs);
+    if (ofs == 0) ofs++, sub.buf++, sub.len--;
+    if (*obj.buf == '[') {  // Iterate over an array
+      int n = 0, o = mg_json_get(sub, "$", &n);
+      if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
+        ofs = 0;  // Error parsing key, stop scanning
+      } else {
+        if (key) *key = mg_str_n(NULL, 0);
+        if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
+        ofs = (size_t) (&sub.buf[o + n] - obj.buf);
+      }
+    } else {  // Iterate over an object
+      int n = 0, o = mg_json_get(sub, "$", &n);
+      if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
+        ofs = 0;  // Error parsing key, stop scanning
+      } else {
+        if (key) *key = mg_str_n(sub.buf + o, (size_t) n);
+        sub.buf += o + n, sub.len -= (size_t) (o + n);
+        while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++;
+        if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++;
+        n = 0, o = mg_json_get(sub, "$", &n);
+        if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) {
+          ofs = 0;  // Error parsing value, stop scanning
+        } else {
+          if (val) *val = mg_str_n(sub.buf + o, (size_t) n);
+          ofs = (size_t) (&sub.buf[o + n] - obj.buf);
+        }
+      }
+    }
+    // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf));
+    while (ofs && ofs < obj.len &&
+           (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' ||
+            obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) {
+      ofs++;
+    }
+    if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++;
+    if (ofs > obj.len) ofs = 0;
+  }
+  return ofs;
+}
+
+int mg_json_get(struct mg_str json, const char *path, int *toklen) {
+  const char *s = json.buf;
+  int len = (int) json.len;
+  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
+  unsigned char nesting[MG_JSON_MAX_DEPTH];
+  int i = 0;             // Current offset in `s`
+  int j = 0;             // Offset in `s` we're looking for (return value)
+  int depth = 0;         // Current depth (nesting level)
+  int ed = 0;            // Expected depth
+  int pos = 1;           // Current position in `path`
+  int ci = -1, ei = -1;  // Current and expected index in array
+
+  if (toklen) *toklen = 0;
+  if (path[0] != '$') return MG_JSON_INVALID;
+
+#define MG_CHECKRET(x)                                  \
+  do {                                                  \
+    if (depth == ed && path[pos] == '\0' && ci == ei) { \
+      if (toklen) *toklen = i - j + 1;                  \
+      return j;                                         \
+    }                                                   \
+  } while (0)
+
+// In the ascii table, the distance between `[` and `]` is 2.
+// Ditto for `{` and `}`. Hence +2 in the code below.
+#define MG_EOO(x)                                            \
+  do {                                                       \
+    if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND;   \
+    if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
+    depth--;                                                 \
+    MG_CHECKRET(x);                                          \
+  } while (0)
+
+  for (i = 0; i < len; i++) {
+    unsigned char c = ((unsigned char *) s)[i];
+    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
+    switch (expecting) {
+      case S_VALUE:
+        // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
+        if (depth == ed) j = i;
+        if (c == '{') {
+          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
+          if (depth == ed && path[pos] == '.' && ci == ei) {
+            // If we start the object, reset array indices
+            ed++, pos++, ci = ei = -1;
+          }
+          nesting[depth++] = c;
+          expecting = S_KEY;
+          break;
+        } else if (c == '[') {
+          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
+          if (depth == ed && path[pos] == '[' && ei == ci) {
+            ed++, pos++, ci = 0;
+            for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
+              ei *= 10;
+              ei += path[pos] - '0';
+            }
+            if (path[pos] != 0) pos++;
+          }
+          nesting[depth++] = c;
+          break;
+        } else if (c == ']' && depth > 0) {  // Empty array
+          MG_EOO(']');
+        } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
+          i += 3;
+        } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
+          i += 3;
+        } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
+          i += 4;
+        } else if (c == '-' || ((c >= '0' && c <= '9'))) {
+          int numlen = 0;
+          mg_atod(&s[i], len - i, &numlen);
+          i += numlen - 1;
+        } else if (c == '"') {
+          int n = mg_pass_string(&s[i + 1], len - i - 1);
+          if (n < 0) return n;
+          i += n + 1;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        MG_CHECKRET('V');
+        if (depth == ed && ei >= 0) ci++;
+        expecting = S_COMMA_OR_EOO;
+        break;
+
+      case S_KEY:
+        if (c == '"') {
+          int n = mg_pass_string(&s[i + 1], len - i - 1);
+          if (n < 0) return n;
+          if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
+          if (depth < ed) return MG_JSON_NOT_FOUND;
+          if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
+          // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n,
+          //        &s[i + 1], n, depth, ed, ci, ei);
+          //  NOTE(cpq): in the check sequence below is important.
+          //  strncmp() must go first: it fails fast if the remaining length
+          //  of the path is smaller than `n`.
+          if (depth == ed && path[pos - 1] == '.' &&
+              strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
+              (path[pos + n] == '\0' || path[pos + n] == '.' ||
+               path[pos + n] == '[')) {
+            pos += n;
+          }
+          i += n + 1;
+          expecting = S_COLON;
+        } else if (c == '}') {  // Empty object
+          MG_EOO('}');
+          expecting = S_COMMA_OR_EOO;
+          if (depth == ed && ei >= 0) ci++;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+
+      case S_COLON:
+        if (c == ':') {
+          expecting = S_VALUE;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+
+      case S_COMMA_OR_EOO:
+        if (depth <= 0) {
+          return MG_JSON_INVALID;
+        } else if (c == ',') {
+          expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
+        } else if (c == ']' || c == '}') {
+          if (depth == ed && c == '}' && path[pos - 1] == '.')
+            return MG_JSON_NOT_FOUND;
+          if (depth == ed && c == ']' && path[pos - 1] == ',')
+            return MG_JSON_NOT_FOUND;
+          MG_EOO('O');
+          if (depth == ed && ei >= 0) ci++;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+    }
+  }
+  return MG_JSON_NOT_FOUND;
+}
+
+struct mg_str mg_json_get_tok(struct mg_str json, const char *path) {
+  int len = 0, ofs = mg_json_get(json, path, &len);
+  return mg_str_n(ofs < 0 ? NULL : json.buf + ofs,
+                  (size_t) (len < 0 ? 0 : len));
+}
+
+bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
+  int n, toklen, found = 0;
+  if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
+      (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) {
+    if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL);
+    found = 1;
+  }
+  return found;
+}
+
+bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
+  int found = 0, off = mg_json_get(json, path, NULL);
+  if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) {
+    if (v != NULL) *v = json.buf[off] == 't';
+    found = 1;
+  }
+  return found;
+}
+
+bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
+  size_t i, j;
+  for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
+    if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') {
+      //  \uXXXX escape. We process simple one-byte chars \u00xx within ASCII
+      //  range. More complex chars would require dragging in a UTF8 library,
+      //  which is too much for us
+      if (mg_str_to_num(mg_str_n(s.buf + i + 2, 4), 16, &to[j],
+                        sizeof(uint8_t)) == false)
+        return false;
+      i += 5;
+    } else if (s.buf[i] == '\\' && i + 1 < s.len) {
+      char c = json_esc(s.buf[i + 1], 0);
+      if (c == 0) return false;
+      to[j] = c;
+      i++;
+    } else {
+      to[j] = s.buf[i];
+    }
+  }
+  if (j >= n) return false;
+  if (n > 0) to[j] = '\0';
+  return true;
+}
+
+char *mg_json_get_str(struct mg_str json, const char *path) {
+  char *result = NULL;
+  int len = 0, off = mg_json_get(json, path, &len);
+  if (off >= 0 && len > 1 && json.buf[off] == '"') {
+    if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
+        !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)),
+                          result, (size_t) len)) {
+      free(result);
+      result = NULL;
+    }
+  }
+  return result;
+}
+
+char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
+  char *result = NULL;
+  int len = 0, off = mg_json_get(json, path, &len);
+  if (off >= 0 && json.buf[off] == '"' && len > 1 &&
+      (result = (char *) calloc(1, (size_t) len)) != NULL) {
+    size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result,
+                                (size_t) len);
+    if (slen != NULL) *slen = (int) k;
+  }
+  return result;
+}
+
+char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
+  char *result = NULL;
+  int len = 0, off = mg_json_get(json, path, &len);
+  if (off >= 0 && json.buf[off] == '"' && len > 1 &&
+      (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
+    int i;
+    for (i = 0; i < len - 2; i += 2) {
+      mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1],
+                    sizeof(uint8_t));
+    }
+    result[len / 2 - 1] = '\0';
+    if (slen != NULL) *slen = len / 2 - 1;
+  }
+  return result;
+}
+
+long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
+  double dv;
+  long result = dflt;
+  if (mg_json_get_num(json, path, &dv)) result = (long) dv;
+  return result;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/log.c"
+#endif
+
+
+
+
+
+int mg_log_level = MG_LL_INFO;
+static mg_pfn_t s_log_func = mg_pfn_stdout;
+static void *s_log_func_param = NULL;
+
+void mg_log_set_fn(mg_pfn_t fn, void *param) {
+  s_log_func = fn;
+  s_log_func_param = param;
+}
+
+static void logc(unsigned char c) {
+  s_log_func((char) c, s_log_func_param);
+}
+
+static void logs(const char *buf, size_t len) {
+  size_t i;
+  for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
+}
+
+#if MG_ENABLE_CUSTOM_LOG
+// Let user define their own mg_log_prefix() and mg_log()
+#else
+void mg_log_prefix(int level, const char *file, int line, const char *fname) {
+  const char *p = strrchr(file, '/');
+  char buf[41];
+  size_t n;
+  if (p == NULL) p = strrchr(file, '\\');
+  n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,
+                  p == NULL ? file : p + 1, line, fname);
+  if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
+  while (n < sizeof(buf)) buf[n++] = ' ';
+  logs(buf, n - 1);
+}
+
+void mg_log(const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
+  va_end(ap);
+  logs("\r\n", 2);
+}
+#endif
+
+static unsigned char nibble(unsigned c) {
+  return (unsigned char) (c < 10 ? c + '0' : c + 'W');
+}
+
+#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
+void mg_hexdump(const void *buf, size_t len) {
+  const unsigned char *p = (const unsigned char *) buf;
+  unsigned char ascii[16], alen = 0;
+  size_t i;
+  for (i = 0; i < len; i++) {
+    if ((i % 16) == 0) {
+      // Print buffered ascii chars
+      if (i > 0) logs("  ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0;
+      // Print hex address, then \t
+      logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
+          logc(nibble((i >> 4) & 15)), logc('0'), logs("   ", 3);
+    }
+    logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15));  // Two nibbles, e.g. c5
+    logc(' ');                                         // Space after hex number
+    ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.';        // Add to the ascii buf
+  }
+  while (alen < 16) logs("   ", 3), ascii[alen++] = ' ';
+  logs("  ", 2), logs((char *) ascii, 16), logc('\n');
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/md5.c"
+#endif
+
+
+
+//  This code implements the MD5 message-digest algorithm.
+//  The algorithm is due to Ron Rivest.  This code was
+//  written by Colin Plumb in 1993, no copyright is claimed.
+//  This code is in the public domain; do with it what you wish.
+//
+//  Equivalent code is available from RSA Data Security, Inc.
+//  This code has been tested against that, and is equivalent,
+//  except that you don't need to include two pages of legalese
+//  with every copy.
+//
+//  To compute the message digest of a chunk of bytes, declare an
+//  MD5Context structure, pass it to MD5Init, call MD5Update as
+//  needed on buffers full of bytes, and then call MD5Final, which
+//  will fill a supplied 16-byte array with the digest.
+
+#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
+
+static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
+  if (MG_BIG_ENDIAN) {
+    do {
+      uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+                   ((unsigned) buf[1] << 8 | buf[0]);
+      *(uint32_t *) buf = t;
+      buf += 4;
+    } while (--longs);
+  } else {
+    (void) buf, (void) longs;  // Little endian. Do nothing
+  }
+}
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void mg_md5_init(mg_md5_ctx *ctx) {
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
+
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+}
+
+static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
+  uint32_t a, b, c, d;
+
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
+
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) {
+  uint32_t t;
+
+  t = ctx->bits[0];
+  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
+  ctx->bits[1] += (uint32_t) len >> 29;
+
+  t = (t >> 3) & 0x3f;
+
+  if (t) {
+    unsigned char *p = (unsigned char *) ctx->in + t;
+
+    t = 64 - t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    mg_byte_reverse(ctx->in, 16);
+    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += t;
+    len -= t;
+  }
+
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    mg_byte_reverse(ctx->in, 16);
+    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += 64;
+    len -= 64;
+  }
+
+  memcpy(ctx->in, buf, len);
+}
+
+void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
+  unsigned count;
+  unsigned char *p;
+  uint32_t *a;
+
+  count = (ctx->bits[0] >> 3) & 0x3F;
+
+  p = ctx->in + count;
+  *p++ = 0x80;
+  count = 64 - 1 - count;
+  if (count < 8) {
+    memset(p, 0, count);
+    mg_byte_reverse(ctx->in, 16);
+    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+    memset(ctx->in, 0, 56);
+  } else {
+    memset(p, 0, count - 8);
+  }
+  mg_byte_reverse(ctx->in, 14);
+
+  a = (uint32_t *) ctx->in;
+  a[14] = ctx->bits[0];
+  a[15] = ctx->bits[1];
+
+  mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+  mg_byte_reverse((unsigned char *) ctx->buf, 4);
+  memcpy(digest, ctx->buf, 16);
+  memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/mqtt.c"
+#endif
+
+
+
+
+
+
+
+
+#define MQTT_CLEAN_SESSION 0x02
+#define MQTT_HAS_WILL 0x04
+#define MQTT_WILL_RETAIN 0x20
+#define MQTT_HAS_PASSWORD 0x40
+#define MQTT_HAS_USER_NAME 0x80
+
+struct mg_mqtt_pmap {
+  uint8_t id;
+  uint8_t type;
+};
+
+static const struct mg_mqtt_pmap s_prop_map[] = {
+    {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
+    {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
+    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
+    {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
+    {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
+    {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
+    {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
+    {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
+    {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
+    {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
+    {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
+    {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
+    {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
+    {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE},
+    {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
+
+void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags,
+                         uint32_t len) {
+  uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
+  buf[0] = (uint8_t) ((cmd << 4) | flags);
+  do {
+    *vlen = len % 0x80;
+    len /= 0x80;
+    if (len > 0) *vlen |= 0x80;
+    vlen++;
+  } while (len > 0 && vlen < &buf[sizeof(buf)]);
+  mg_send(c, buf, (size_t) (vlen - buf));
+}
+
+static void mg_send_u16(struct mg_connection *c, uint16_t value) {
+  mg_send(c, &value, sizeof(value));
+}
+
+static void mg_send_u32(struct mg_connection *c, uint32_t value) {
+  mg_send(c, &value, sizeof(value));
+}
+
+static uint8_t varint_size(size_t length) {
+  uint8_t bytes_needed = 0;
+  do {
+    bytes_needed++;
+    length /= 0x80;
+  } while (length > 0);
+  return bytes_needed;
+}
+
+static size_t encode_varint(uint8_t *buf, size_t value) {
+  size_t len = 0;
+
+  do {
+    uint8_t b = (uint8_t) (value % 128);
+    value /= 128;
+    if (value > 0) b |= 0x80;
+    buf[len++] = b;
+  } while (value > 0);
+
+  return len;
+}
+
+static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) {
+  size_t multiplier = 1, offset;
+  *value = 0;
+
+  for (offset = 0; offset < 4 && offset < len; offset++) {
+    uint8_t encoded_byte = buf[offset];
+    *value += (encoded_byte & 0x7f) * multiplier;
+    multiplier *= 128;
+
+    if ((encoded_byte & 0x80) == 0) return offset + 1;
+  }
+
+  return 0;
+}
+
+static int mqtt_prop_type_by_id(uint8_t prop_id) {
+  size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]);
+  for (i = 0; i < num_properties; ++i) {
+    if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
+  }
+  return -1;  // Property ID not found
+}
+
+// Returns the size of the properties section, without the
+// size of the content's length
+static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) {
+  size_t i, size = 0;
+  for (i = 0; i < count; i++) {
+    size++;  // identifier
+    switch (mqtt_prop_type_by_id(props[i].id)) {
+      case MQTT_PROP_TYPE_STRING_PAIR:
+        size += (uint32_t) (props[i].val.len + props[i].key.len +
+                            2 * sizeof(uint16_t));
+        break;
+      case MQTT_PROP_TYPE_STRING:
+        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
+        break;
+      case MQTT_PROP_TYPE_BINARY_DATA:
+        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
+        break;
+      case MQTT_PROP_TYPE_VARIABLE_INT:
+        size += varint_size((uint32_t) props[i].iv);
+        break;
+      case MQTT_PROP_TYPE_INT:
+        size += (uint32_t) sizeof(uint32_t);
+        break;
+      case MQTT_PROP_TYPE_SHORT:
+        size += (uint32_t) sizeof(uint16_t);
+        break;
+      case MQTT_PROP_TYPE_BYTE:
+        size += (uint32_t) sizeof(uint8_t);
+        break;
+      default:
+        return size;  // cannot parse further down
+    }
+  }
+
+  return size;
+}
+
+// returns the entire size of the properties section, including the
+// size of the variable length of the content
+static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) {
+  size_t size = get_properties_length(props, count);
+  size += varint_size(size);
+  return size;
+}
+
+static void mg_send_mqtt_properties(struct mg_connection *c,
+                                    struct mg_mqtt_prop *props, size_t nprops) {
+  size_t total_size = get_properties_length(props, nprops);
+  uint8_t buf_v[4] = {0, 0, 0, 0};
+  uint8_t buf[4] = {0, 0, 0, 0};
+  size_t i, len = encode_varint(buf, total_size);
+
+  mg_send(c, buf, (size_t) len);
+  for (i = 0; i < nprops; i++) {
+    mg_send(c, &props[i].id, sizeof(props[i].id));
+    switch (mqtt_prop_type_by_id(props[i].id)) {
+      case MQTT_PROP_TYPE_STRING_PAIR:
+        mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
+        mg_send(c, props[i].key.buf, props[i].key.len);
+        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
+        mg_send(c, props[i].val.buf, props[i].val.len);
+        break;
+      case MQTT_PROP_TYPE_BYTE:
+        mg_send(c, &props[i].iv, sizeof(uint8_t));
+        break;
+      case MQTT_PROP_TYPE_SHORT:
+        mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
+        break;
+      case MQTT_PROP_TYPE_INT:
+        mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
+        break;
+      case MQTT_PROP_TYPE_STRING:
+        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
+        mg_send(c, props[i].val.buf, props[i].val.len);
+        break;
+      case MQTT_PROP_TYPE_BINARY_DATA:
+        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
+        mg_send(c, props[i].val.buf, props[i].val.len);
+        break;
+      case MQTT_PROP_TYPE_VARIABLE_INT:
+        len = encode_varint(buf_v, props[i].iv);
+        mg_send(c, buf_v, (size_t) len);
+        break;
+    }
+  }
+}
+
+size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop,
+                         size_t ofs) {
+  uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs;
+  uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len;
+  size_t new_pos = ofs, len;
+  prop->id = i[0];
+
+  if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size)
+    return 0;
+  i++, new_pos++;
+
+  switch (mqtt_prop_type_by_id(prop->id)) {
+    case MQTT_PROP_TYPE_STRING_PAIR:
+      prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
+      prop->key.buf = (char *) i + 2;
+      i += 2 + prop->key.len;
+      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
+      prop->val.buf = (char *) i + 2;
+      new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
+      break;
+    case MQTT_PROP_TYPE_BYTE:
+      prop->iv = (uint8_t) i[0];
+      new_pos++;
+      break;
+    case MQTT_PROP_TYPE_SHORT:
+      prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
+      new_pos += sizeof(uint16_t);
+      break;
+    case MQTT_PROP_TYPE_INT:
+      prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
+                 ((uint32_t) i[2] << 8) | i[3];
+      new_pos += sizeof(uint32_t);
+      break;
+    case MQTT_PROP_TYPE_STRING:
+      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
+      prop->val.buf = (char *) i + 2;
+      new_pos += 2 + prop->val.len;
+      break;
+    case MQTT_PROP_TYPE_BINARY_DATA:
+      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
+      prop->val.buf = (char *) i + 2;
+      new_pos += 2 + prop->val.len;
+      break;
+    case MQTT_PROP_TYPE_VARIABLE_INT:
+      len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
+      new_pos = (!len) ? 0 : new_pos + len;
+      break;
+    default:
+      new_pos = 0;
+  }
+
+  return new_pos;
+}
+
+void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
+  char client_id[21];
+  struct mg_str cid = opts->client_id;
+  size_t total_len = 7 + 1 + 2 + 2;
+  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
+
+  if (cid.len == 0) {
+    mg_random_str(client_id, sizeof(client_id) - 1);
+    client_id[sizeof(client_id) - 1] = '\0';
+    cid = mg_str(client_id);
+  }
+
+  if (hdr[6] == 0) hdr[6] = 4;  // If version is not set, use 4 (3.1.1)
+  c->is_mqtt5 = hdr[6] == 5;    // Set version 5 flag
+  hdr[7] = (uint8_t) ((opts->qos & 3) << 3);  // Connection flags
+  if (opts->user.len > 0) {
+    total_len += 2 + (uint32_t) opts->user.len;
+    hdr[7] |= MQTT_HAS_USER_NAME;
+  }
+  if (opts->pass.len > 0) {
+    total_len += 2 + (uint32_t) opts->pass.len;
+    hdr[7] |= MQTT_HAS_PASSWORD;
+  }
+  if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t
+    total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len;
+    hdr[7] |= MQTT_HAS_WILL;
+  }
+  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
+  if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
+  total_len += (uint32_t) cid.len;
+  if (c->is_mqtt5) {
+    total_len += get_props_size(opts->props, opts->num_props);
+    if (hdr[7] & MQTT_HAS_WILL)
+      total_len += get_props_size(opts->will_props, opts->num_will_props);
+  }
+
+  mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
+  mg_send(c, hdr, sizeof(hdr));
+  // keepalive == 0 means "do not disconnect us!"
+  mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
+
+  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
+
+  mg_send_u16(c, mg_htons((uint16_t) cid.len));
+  mg_send(c, cid.buf, cid.len);
+
+  if (hdr[7] & MQTT_HAS_WILL) {
+    if (c->is_mqtt5)
+      mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props);
+
+    mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
+    mg_send(c, opts->topic.buf, opts->topic.len);
+    mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
+    mg_send(c, opts->message.buf, opts->message.len);
+  }
+  if (opts->user.len > 0) {
+    mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
+    mg_send(c, opts->user.buf, opts->user.len);
+  }
+  if (opts->pass.len > 0) {
+    mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
+    mg_send(c, opts->pass.buf, opts->pass.len);
+  }
+}
+
+uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
+  uint16_t id = opts->retransmit_id;
+  uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0));
+  size_t len = 2 + opts->topic.len + opts->message.len;
+  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
+            (char *) opts->topic.buf, (int) opts->message.len,
+            (char *) opts->message.buf));
+  if (opts->qos > 0) len += 2;
+  if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props);
+
+  if (opts->qos > 0 && id != 0) flags |= 1 << 3;
+  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
+  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
+  mg_send(c, opts->topic.buf, opts->topic.len);
+  if (opts->qos > 0) {    // need to send 'id' field
+    if (id == 0) {  // generate new one if not resending
+      if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
+      id = c->mgr->mqtt_id;
+    }
+    mg_send_u16(c, mg_htons(id));
+  }
+
+  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
+
+  if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len);
+  return id;
+}
+
+void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
+  uint8_t qos_ = opts->qos & 3;
+  size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0;
+  size_t len = 2 + opts->topic.len + 2 + 1 + plen;
+
+  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len);
+  if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
+  mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
+  if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props);
+
+  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
+  mg_send(c, opts->topic.buf, opts->topic.len);
+  mg_send(c, &qos_, sizeof(qos_));
+}
+
+int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
+                  struct mg_mqtt_message *m) {
+  uint8_t lc = 0, *p, *end;
+  uint32_t n = 0, len_len = 0;
+
+  memset(m, 0, sizeof(*m));
+  m->dgram.buf = (char *) buf;
+  if (len < 2) return MQTT_INCOMPLETE;
+  m->cmd = (uint8_t) (buf[0] >> 4);
+  m->qos = (buf[0] >> 1) & 3;
+
+  n = len_len = 0;
+  p = (uint8_t *) buf + 1;
+  while ((size_t) (p - buf) < len) {
+    lc = *((uint8_t *) p++);
+    n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
+    len_len++;
+    if (!(lc & 0x80)) break;
+    if (len_len >= 4) return MQTT_MALFORMED;
+  }
+  end = p + n;
+  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
+  m->dgram.len = (size_t) (end - buf);
+
+  switch (m->cmd) {
+    case MQTT_CMD_CONNACK:
+      if (end - p < 2) return MQTT_MALFORMED;
+      m->ack = p[1];
+      break;
+    case MQTT_CMD_PUBACK:
+    case MQTT_CMD_PUBREC:
+    case MQTT_CMD_PUBREL:
+    case MQTT_CMD_PUBCOMP:
+    case MQTT_CMD_SUBSCRIBE:
+    case MQTT_CMD_SUBACK:
+    case MQTT_CMD_UNSUBSCRIBE:
+    case MQTT_CMD_UNSUBACK:
+      if (p + 2 > end) return MQTT_MALFORMED;
+      m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
+      p += 2;
+      break;
+    case MQTT_CMD_PUBLISH: {
+      if (p + 2 > end) return MQTT_MALFORMED;
+      m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
+      m->topic.buf = (char *) p + 2;
+      p += 2 + m->topic.len;
+      if (p > end) return MQTT_MALFORMED;
+      if (m->qos > 0) {
+        if (p + 2 > end) return MQTT_MALFORMED;
+        m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
+        p += 2;
+      }
+      if (p > end) return MQTT_MALFORMED;
+      if (version == 5 && p + 2 < end) {
+        len_len =
+            (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
+        if (!len_len) return MQTT_MALFORMED;
+        m->props_start = (size_t) (p + len_len - buf);
+        p += len_len + m->props_size;
+      }
+      if (p > end) return MQTT_MALFORMED;
+      m->data.buf = (char *) p;
+      m->data.len = (size_t) (end - p);
+      break;
+    }
+    default:
+      break;
+  }
+  return MQTT_OK;
+}
+
+static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_READ) {
+    for (;;) {
+      uint8_t version = c->is_mqtt5 ? 5 : 4;
+      struct mg_mqtt_message mm;
+      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
+      if (rc == MQTT_MALFORMED) {
+        MG_ERROR(("%lu MQTT malformed message", c->id));
+        c->is_closing = 1;
+        break;
+      } else if (rc == MQTT_OK) {
+        MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
+                    (int) mm.dgram.len, (int) mm.data.len, mm.data.buf));
+        switch (mm.cmd) {
+          case MQTT_CMD_CONNACK:
+            mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
+            if (mm.ack == 0) {
+              MG_DEBUG(("%lu Connected", c->id));
+            } else {
+              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
+              c->is_closing = 1;
+            }
+            break;
+          case MQTT_CMD_PUBLISH: {
+            /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
+                      mm.topic.buf, (int) mm.data.len, mm.data.buf));*/
+            if (mm.qos > 0) {
+              uint16_t id = mg_ntohs(mm.id);
+              uint32_t remaining_len = sizeof(id);
+              if (c->is_mqtt5) remaining_len += 2;  // 3.4.2
+
+              mg_mqtt_send_header(
+                  c,
+                  (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
+                  0, remaining_len);
+              mg_send(c, &id, sizeof(id));
+
+              if (c->is_mqtt5) {
+                uint16_t zero = 0;
+                mg_send(c, &zero, sizeof(zero));
+              }
+            }
+            mg_call(c, MG_EV_MQTT_MSG, &mm);  // let the app handle qos stuff
+            break;
+          }
+          case MQTT_CMD_PUBREC: {  // MQTT5: 3.5.2-1 TODO(): variable header rc
+            uint16_t id = mg_ntohs(mm.id);
+            uint32_t remaining_len = sizeof(id);  // MQTT5 3.6.2-1
+            mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
+            mg_send(c, &id, sizeof(id));  // MQTT5 3.6.1-1, flags = 2
+            break;
+          }
+          case MQTT_CMD_PUBREL: {  // MQTT5: 3.6.2-1 TODO(): variable header rc
+            uint16_t id = mg_ntohs(mm.id);
+            uint32_t remaining_len = sizeof(id);  // MQTT5 3.7.2-1
+            mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
+            mg_send(c, &id, sizeof(id));
+            break;
+          }
+        }
+        mg_call(c, MG_EV_MQTT_CMD, &mm);
+        mg_iobuf_del(&c->recv, 0, mm.dgram.len);
+      } else {
+        break;
+      }
+    }
+  }
+  (void) ev_data;
+}
+
+void mg_mqtt_ping(struct mg_connection *nc) {
+  mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
+}
+
+void mg_mqtt_pong(struct mg_connection *nc) {
+  mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
+}
+
+void mg_mqtt_disconnect(struct mg_connection *c,
+                        const struct mg_mqtt_opts *opts) {
+  size_t len = 0;
+  if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props);
+  mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
+
+  if (c->is_mqtt5) {
+    uint8_t zero = 0;
+    mg_send(c, &zero, sizeof(zero));  // reason code
+    mg_send_mqtt_properties(c, opts->props, opts->num_props);
+  }
+}
+
+struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
+                                      const struct mg_mqtt_opts *opts,
+                                      mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
+  if (c != NULL) {
+    struct mg_mqtt_opts empty;
+    memset(&empty, 0, sizeof(empty));
+    mg_mqtt_login(c, opts == NULL ? &empty : opts);
+    c->pfn = mqtt_cb;
+  }
+  return c;
+}
+
+struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
+                                     mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
+  if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
+  return c;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/net.c"
+#endif
+
+
+
+
+
+
+
+
+
+size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) {
+  size_t old = c->send.len;
+  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
+  return c->send.len - old;
+}
+
+size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
+  size_t len = 0;
+  va_list ap;
+  va_start(ap, fmt);
+  len = mg_vprintf(c, fmt, &ap);
+  va_end(ap);
+  return len;
+}
+
+static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
+  uint32_t localhost = mg_htonl(0x7f000001);
+  if (mg_strcasecmp(str, mg_str("localhost")) != 0) return false;
+  memcpy(addr->ip, &localhost, sizeof(uint32_t));
+  addr->is_ip6 = false;
+  return true;
+}
+
+static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
+  if (str.len > 0) return false;
+  memset(addr->ip, 0, sizeof(addr->ip));
+  addr->is_ip6 = false;
+  return true;
+}
+
+static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
+  uint8_t data[4] = {0, 0, 0, 0};
+  size_t i, num_dots = 0;
+  for (i = 0; i < str.len; i++) {
+    if (str.buf[i] >= '0' && str.buf[i] <= '9') {
+      int octet = data[num_dots] * 10 + (str.buf[i] - '0');
+      if (octet > 255) return false;
+      data[num_dots] = (uint8_t) octet;
+    } else if (str.buf[i] == '.') {
+      if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false;
+      num_dots++;
+    } else {
+      return false;
+    }
+  }
+  if (num_dots != 3 || str.buf[i - 1] == '.') return false;
+  memcpy(&addr->ip, data, sizeof(data));
+  addr->is_ip6 = false;
+  return true;
+}
+
+static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
+  int i;
+  uint32_t ipv4;
+  if (str.len < 14) return false;
+  if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false;
+  for (i = 2; i < 6; i++) {
+    if (str.buf[i] != 'f' && str.buf[i] != 'F') return false;
+  }
+  // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7);
+  if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false;
+  memcpy(&ipv4, addr->ip, sizeof(ipv4));
+  memset(addr->ip, 0, sizeof(addr->ip));
+  addr->ip[10] = addr->ip[11] = 255;
+  memcpy(&addr->ip[12], &ipv4, 4);
+  addr->is_ip6 = true;
+  return true;
+}
+
+static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
+  size_t i, j = 0, n = 0, dc = 42;
+  addr->scope_id = 0;
+  if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2;
+  if (mg_v4mapped(str, addr)) return true;
+  for (i = 0; i < str.len; i++) {
+    if ((str.buf[i] >= '0' && str.buf[i] <= '9') ||
+        (str.buf[i] >= 'a' && str.buf[i] <= 'f') ||
+        (str.buf[i] >= 'A' && str.buf[i] <= 'F')) {
+      unsigned long val;  // TODO(): This loops on chars, refactor
+      if (i > j + 3) return false;
+      // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j]));
+      mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val));
+      addr->ip[n] = (uint8_t) ((val >> 8) & 255);
+      addr->ip[n + 1] = (uint8_t) (val & 255);
+    } else if (str.buf[i] == ':') {
+      j = i + 1;
+      if (i > 0 && str.buf[i - 1] == ':') {
+        dc = n;  // Double colon
+        if (i > 1 && str.buf[i - 2] == ':') return false;
+      } else if (i > 0) {
+        n += 2;
+      }
+      if (n > 14) return false;
+      addr->ip[n] = addr->ip[n + 1] = 0;  // For trailing ::
+    } else if (str.buf[i] == '%') {       // Scope ID, last in string
+      return mg_str_to_num(mg_str_n(&str.buf[i + 1], str.len - i - 1), 10,
+                           &addr->scope_id, sizeof(uint8_t));
+    } else {
+      return false;
+    }
+  }
+  if (n < 14 && dc == 42) return false;
+  if (n < 14) {
+    memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
+    memset(&addr->ip[dc], 0, 14 - n);
+  }
+
+  addr->is_ip6 = true;
+  return true;
+}
+
+bool mg_aton(struct mg_str str, struct mg_addr *addr) {
+  // MG_INFO(("[%.*s]", (int) str.len, str.buf));
+  return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
+         mg_aton6(str, addr);
+}
+
+struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
+  struct mg_connection *c =
+      (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
+  if (c != NULL) {
+    c->mgr = mgr;
+    c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE;
+    c->id = ++mgr->nextid;
+    MG_PROF_INIT(c);
+  }
+  return c;
+}
+
+void mg_close_conn(struct mg_connection *c) {
+  mg_resolve_cancel(c);  // Close any pending DNS query
+  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
+  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
+  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
+  // Order of operations is important. `MG_EV_CLOSE` event must be fired
+  // before we deallocate received data, see #1331
+  mg_call(c, MG_EV_CLOSE, NULL);
+  MG_DEBUG(("%lu %ld closed", c->id, c->fd));
+  MG_PROF_DUMP(c);
+  MG_PROF_FREE(c);
+
+  mg_tls_free(c);
+  mg_iobuf_free(&c->recv);
+  mg_iobuf_free(&c->send);
+  mg_iobuf_free(&c->rtls);
+  mg_bzero((unsigned char *) c, sizeof(*c));
+  free(c);
+}
+
+struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
+                                 mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = NULL;
+  if (url == NULL || url[0] == '\0') {
+    MG_ERROR(("null url"));
+  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("OOM"));
+  } else {
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->is_udp = (strncmp(url, "udp:", 4) == 0);
+    c->fd = (void *) (size_t) MG_INVALID_SOCKET;
+    c->fn = fn;
+    c->is_client = true;
+    c->fn_data = fn_data;
+    MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
+    mg_call(c, MG_EV_OPEN, (void *) url);
+    mg_resolve(c, url);
+  }
+  return c;
+}
+
+struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
+                                mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = NULL;
+  if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("OOM %s", url));
+  } else if (!mg_open_listener(c, url)) {
+    MG_ERROR(("Failed: %s, errno %d", url, errno));
+    MG_PROF_FREE(c);
+    free(c);
+    c = NULL;
+  } else {
+    c->is_listening = 1;
+    c->is_udp = strncmp(url, "udp:", 4) == 0;
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->fn = fn;
+    c->fn_data = fn_data;
+    mg_call(c, MG_EV_OPEN, NULL);
+    if (mg_url_is_ssl(url)) c->is_tls = 1;  // Accepted connection must
+    MG_DEBUG(("%lu %ld %s", c->id, c->fd, url));
+  }
+  return c;
+}
+
+struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
+                                mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_alloc_conn(mgr);
+  if (c != NULL) {
+    c->fd = (void *) (size_t) fd;
+    c->fn = fn;
+    c->fn_data = fn_data;
+    MG_EPOLL_ADD(c);
+    mg_call(c, MG_EV_OPEN, NULL);
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+  }
+  return c;
+}
+
+struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
+                              unsigned flags, void (*fn)(void *), void *arg) {
+  struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
+  if (t != NULL) {
+    mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
+    t->id = mgr->timerid++;
+  }
+  return t;
+}
+
+long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
+  if (c->rtls.len == 0) return MG_IO_WAIT;
+  if (len > c->rtls.len) len = c->rtls.len;
+  memcpy(buf, c->rtls.buf, len);
+  mg_iobuf_del(&c->rtls, 0, len);
+  return (long) len;
+}
+
+void mg_mgr_free(struct mg_mgr *mgr) {
+  struct mg_connection *c;
+  struct mg_timer *tmp, *t = mgr->timers;
+  while (t != NULL) tmp = t->next, free(t), t = tmp;
+  mgr->timers = NULL;  // Important. Next call to poll won't touch timers
+  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
+  mg_mgr_poll(mgr, 0);
+#if MG_ENABLE_FREERTOS_TCP
+  FreeRTOS_DeleteSocketSet(mgr->ss);
+#endif
+  MG_DEBUG(("All connections closed"));
+#if MG_ENABLE_EPOLL
+  if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
+#endif
+  mg_tls_ctx_free(mgr);
+}
+
+void mg_mgr_init(struct mg_mgr *mgr) {
+  memset(mgr, 0, sizeof(*mgr));
+#if MG_ENABLE_EPOLL
+  if ((mgr->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
+    MG_ERROR(("epoll_create1 errno %d", errno));
+#else
+  mgr->epoll_fd = -1;
+#endif
+#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+  // clang-format off
+  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
+  // clang-format on
+#elif MG_ENABLE_FREERTOS_TCP
+  mgr->ss = FreeRTOS_CreateSocketSet();
+#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
+  // Ignore SIGPIPE signal, so if client cancels the request, it
+  // won't kill the whole process.
+  signal(SIGPIPE, SIG_IGN);
+#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT)
+  MG_TCPIP_DRIVER_INIT(mgr);
+#endif
+  mgr->pipe = MG_INVALID_SOCKET;
+  mgr->dnstimeout = 3000;
+  mgr->dns4.url = "udp://8.8.8.8:53";
+  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
+  mg_tls_ctx_init(mgr);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/net_builtin.c"
+#endif
+
+
+#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
+#define MG_EPHEMERAL_PORT_BASE 32768
+#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
+
+#ifndef MIP_TCP_KEEPALIVE_MS
+#define MIP_TCP_KEEPALIVE_MS 45000  // TCP keep-alive period, ms
+#endif
+
+#define MIP_TCP_ACK_MS 150    // Timeout for ACKing
+#define MIP_TCP_ARP_MS 100    // Timeout for ARP response
+#define MIP_TCP_SYN_MS 15000  // Timeout for connection establishment
+#define MIP_TCP_FIN_MS 1000   // Timeout for closing connection
+#define MIP_TCP_WIN 6000      // TCP window size
+
+struct connstate {
+  uint32_t seq, ack;           // TCP seq/ack counters
+  uint64_t timer;              // TCP keep-alive / ACK timer
+  uint32_t acked;              // Last ACK-ed number
+  size_t unacked;              // Not acked bytes
+  uint8_t mac[6];              // Peer MAC address
+  uint8_t ttype;               // Timer type. 0: ack, 1: keep-alive
+#define MIP_TTYPE_KEEPALIVE 0  // Connection is idle for long, send keepalive
+#define MIP_TTYPE_ACK 1        // Peer sent us data, we have to ack it soon
+#define MIP_TTYPE_ARP 2        // ARP resolve sent, waiting for response
+#define MIP_TTYPE_SYN 3        // SYN sent, waiting for response
+#define MIP_TTYPE_FIN 4  // FIN sent, waiting until terminating the connection
+  uint8_t tmiss;         // Number of keep-alive misses
+  struct mg_iobuf raw;   // For TLS only. Incoming raw data
+};
+
+#pragma pack(push, 1)
+
+struct lcp {
+  uint8_t addr, ctrl, proto[2], code, id, len[2];
+};
+
+struct eth {
+  uint8_t dst[6];  // Destination MAC address
+  uint8_t src[6];  // Source MAC address
+  uint16_t type;   // Ethernet type
+};
+
+struct ip {
+  uint8_t ver;    // Version
+  uint8_t tos;    // Unused
+  uint16_t len;   // Length
+  uint16_t id;    // Unused
+  uint16_t frag;  // Fragmentation
+#define IP_FRAG_OFFSET_MSK 0xFF1F
+#define IP_MORE_FRAGS_MSK 0x20
+  uint8_t ttl;    // Time to live
+  uint8_t proto;  // Upper level protocol
+  uint16_t csum;  // Checksum
+  uint32_t src;   // Source IP
+  uint32_t dst;   // Destination IP
+};
+
+struct ip6 {
+  uint8_t ver;      // Version
+  uint8_t opts[3];  // Options
+  uint16_t len;     // Length
+  uint8_t proto;    // Upper level protocol
+  uint8_t ttl;      // Time to live
+  uint8_t src[16];  // Source IP
+  uint8_t dst[16];  // Destination IP
+};
+
+struct icmp {
+  uint8_t type;
+  uint8_t code;
+  uint16_t csum;
+};
+
+struct arp {
+  uint16_t fmt;    // Format of hardware address
+  uint16_t pro;    // Format of protocol address
+  uint8_t hlen;    // Length of hardware address
+  uint8_t plen;    // Length of protocol address
+  uint16_t op;     // Operation
+  uint8_t sha[6];  // Sender hardware address
+  uint32_t spa;    // Sender protocol address
+  uint8_t tha[6];  // Target hardware address
+  uint32_t tpa;    // Target protocol address
+};
+
+struct tcp {
+  uint16_t sport;  // Source port
+  uint16_t dport;  // Destination port
+  uint32_t seq;    // Sequence number
+  uint32_t ack;    // Acknowledgement number
+  uint8_t off;     // Data offset
+  uint8_t flags;   // TCP flags
+#define TH_FIN 0x01
+#define TH_SYN 0x02
+#define TH_RST 0x04
+#define TH_PUSH 0x08
+#define TH_ACK 0x10
+#define TH_URG 0x20
+#define TH_ECE 0x40
+#define TH_CWR 0x80
+  uint16_t win;   // Window
+  uint16_t csum;  // Checksum
+  uint16_t urp;   // Urgent pointer
+};
+
+struct udp {
+  uint16_t sport;  // Source port
+  uint16_t dport;  // Destination port
+  uint16_t len;    // UDP length
+  uint16_t csum;   // UDP checksum
+};
+
+struct dhcp {
+  uint8_t op, htype, hlen, hops;
+  uint32_t xid;
+  uint16_t secs, flags;
+  uint32_t ciaddr, yiaddr, siaddr, giaddr;
+  uint8_t hwaddr[208];
+  uint32_t magic;
+  uint8_t options[32];
+};
+
+#pragma pack(pop)
+
+struct pkt {
+  struct mg_str raw;  // Raw packet data
+  struct mg_str pay;  // Payload data
+  struct eth *eth;
+  struct llc *llc;
+  struct arp *arp;
+  struct ip *ip;
+  struct ip6 *ip6;
+  struct icmp *icmp;
+  struct tcp *tcp;
+  struct udp *udp;
+  struct dhcp *dhcp;
+};
+
+static void send_syn(struct mg_connection *c);
+
+static void mkpay(struct pkt *pkt, void *p) {
+  pkt->pay =
+      mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p));
+}
+
+static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
+  size_t i;
+  const uint8_t *p = (const uint8_t *) buf;
+  for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
+  return sum;
+}
+
+static uint16_t csumfin(uint32_t sum) {
+  while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
+  return mg_htons(~sum & 0xffff);
+}
+
+static uint16_t ipcsum(const void *buf, size_t len) {
+  uint32_t sum = csumup(0, buf, len);
+  return csumfin(sum);
+}
+
+static void settmout(struct mg_connection *c, uint8_t type) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  struct connstate *s = (struct connstate *) (c + 1);
+  unsigned n = type == MIP_TTYPE_ACK   ? MIP_TCP_ACK_MS
+               : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
+               : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
+               : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
+                                       : MIP_TCP_KEEPALIVE_MS;
+  s->timer = ifp->now + n;
+  s->ttype = type;
+  MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
+}
+
+static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
+  size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
+  if (n == len) ifp->nsent++;
+  return n;
+}
+
+static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct arp *arp = (struct arp *) (eth + 1);
+  memset(eth->dst, 255, sizeof(eth->dst));
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));
+  eth->type = mg_htons(0x806);
+  memset(arp, 0, sizeof(*arp));
+  arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
+  arp->plen = 4;
+  arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
+  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
+  ether_output(ifp, PDIFF(eth, arp + 1));
+}
+
+static void onstatechange(struct mg_tcpip_if *ifp) {
+  if (ifp->state == MG_TCPIP_STATE_READY) {
+    MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
+    MG_INFO(("       GW: %M", mg_print_ip4, &ifp->gw));
+    MG_INFO(("      MAC: %M", mg_print_mac, &ifp->mac));
+    arp_ask(ifp, ifp->gw);
+  } else if (ifp->state == MG_TCPIP_STATE_UP) {
+    MG_ERROR(("Link up"));
+    srand((unsigned int) mg_millis());
+  } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
+    MG_ERROR(("Link down"));
+  }
+}
+
+static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                        uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
+                        size_t plen) {
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct ip *ip = (struct ip *) (eth + 1);
+  memcpy(eth->dst, mac_dst, sizeof(eth->dst));
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));  // Use our MAC
+  eth->type = mg_htons(0x800);
+  memset(ip, 0, sizeof(*ip));
+  ip->ver = 0x45;   // Version 4, header length 5 words
+  ip->frag = 0x40;  // Don't fragment
+  ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
+  ip->ttl = 64;
+  ip->proto = proto;
+  ip->src = ip_src;
+  ip->dst = ip_dst;
+  ip->csum = ipcsum(ip, sizeof(*ip));
+  return ip;
+}
+
+static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
+                   uint16_t sport, uint32_t ip_dst, uint16_t dport,
+                   const void *buf, size_t len) {
+  struct ip *ip =
+      tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
+  struct udp *udp = (struct udp *) (ip + 1);
+  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
+  udp->sport = sport;
+  udp->dport = dport;
+  udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
+  udp->csum = 0;
+  uint32_t cs = csumup(0, udp, sizeof(*udp));
+  cs = csumup(cs, buf, len);
+  cs = csumup(cs, &ip->src, sizeof(ip->src));
+  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
+  udp->csum = csumfin(cs);
+  memmove(udp + 1, buf, len);
+  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
+  ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len);
+}
+
+static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
+                    uint32_t ip_dst, uint8_t *opts, size_t optslen,
+                    bool ciaddr) {
+  // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
+  struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
+  dhcp.magic = mg_htonl(0x63825363);
+  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
+  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
+  memcpy(&dhcp.options, opts, optslen);
+  if (ciaddr) dhcp.ciaddr = ip_src;
+  tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
+         sizeof(dhcp));
+}
+
+static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
+
+// RFC-2131 #4.3.6, #4.4.1
+static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req,
+                                uint32_t ip_srv) {
+  uint8_t opts[] = {
+      53, 1, 3,                 // Type: DHCP request
+      55, 2, 1,   3,            // GW and mask
+      12, 3, 'm', 'i', 'p',     // Host name: "mip"
+      54, 4, 0,   0,   0,   0,  // DHCP server ID
+      50, 4, 0,   0,   0,   0,  // Requested IP
+      255                       // End of options
+  };
+  memcpy(opts + 14, &ip_srv, sizeof(ip_srv));
+  memcpy(opts + 20, &ip_req, sizeof(ip_req));
+  tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
+  MG_DEBUG(("DHCP req sent"));
+}
+
+// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
+static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                               uint32_t ip_src, uint32_t ip_dst) {
+  uint8_t opts[] = {
+      53, 1, 3,  // Type: DHCP request
+      255        // End of options
+  };
+  tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
+  MG_DEBUG(("DHCP req sent"));
+}
+
+static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
+  uint8_t opts[] = {
+      53, 1, 1,     // Type: DHCP discover
+      55, 2, 1, 3,  // Parameters: ip, mask
+      255           // End of options
+  };
+  tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
+  MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac));
+}
+
+static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
+                                     bool lsn) {
+  struct mg_connection *c = NULL;
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    if (c->is_arplooking && pkt->arp &&
+        memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
+      break;
+    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
+    if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
+        lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
+      break;
+  }
+  return c;
+}
+
+static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
+    // ARP request. Make a response, then send
+    // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
+    //          &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
+    struct eth *eth = (struct eth *) ifp->tx.buf;
+    struct arp *arp = (struct arp *) (eth + 1);
+    memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
+    memcpy(eth->src, ifp->mac, sizeof(eth->src));
+    eth->type = mg_htons(0x806);
+    *arp = *pkt->arp;
+    arp->op = mg_htons(2);
+    memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
+    memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
+    arp->tpa = pkt->arp->spa;
+    arp->spa = ifp->ip;
+    MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac,
+              &ifp->mac));
+    ether_output(ifp, PDIFF(eth, arp + 1));
+  } else if (pkt->arp->op == mg_htons(2)) {
+    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
+    if (pkt->arp->spa == ifp->gw) {
+      // Got response for the GW ARP request. Set ifp->gwmac
+      memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
+    } else {
+      struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+      if (c != NULL && c->is_arplooking) {
+        struct connstate *s = (struct connstate *) (c + 1);
+        memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
+        MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip,
+                  mg_print_mac, s->mac));
+        c->is_arplooking = 0;
+        send_syn(c);
+        settmout(c, MIP_TTYPE_SYN);
+      }
+    }
+  }
+}
+
+static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("ICMP %d", (int) len));
+  if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
+    size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
+    size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
+    if (plen > space) plen = space;
+    struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
+                          sizeof(struct icmp) + plen);
+    struct icmp *icmp = (struct icmp *) (ip + 1);
+    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
+    memcpy(icmp + 1, pkt->pay.buf, plen);  // Copy RX payload to TX
+    icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
+    ether_output(ifp, hlen + plen);
+  }
+}
+
+static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
+  uint8_t msgtype = 0, state = ifp->state;
+  // perform size check first, then access fields
+  uint8_t *p = pkt->dhcp->options,
+          *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
+  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
+  if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
+  while (p + 1 < end && p[0] != 255) {  // Parse options RFC-1533 #9
+    if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) {  // Mask
+      memcpy(&mask, p + 2, sizeof(mask));
+    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) {  // GW
+      memcpy(&gw, p + 2, sizeof(gw));
+      ip = pkt->dhcp->yiaddr;
+    } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) {  // Lease
+      memcpy(&lease, p + 2, sizeof(lease));
+      lease = mg_ntohl(lease);
+    } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) {  // Msg Type
+      msgtype = p[2];
+    }
+    p += p[1] + 2;
+  }
+  // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
+  if (msgtype == 6 && ifp->ip == ip) {  // DHCPNACK, release IP
+    ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
+  } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw &&
+             lease) {  // DHCPOFFER
+    // select IP, (4.4.1) (fallback to IP source addr on foul play)
+    tx_dhcp_request_sel(ifp, ip,
+                        pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->ip->src);
+    ifp->state = MG_TCPIP_STATE_REQ;  // REQUESTING state
+  } else if (msgtype == 5) {          // DHCPACK
+    if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) {  // got an IP
+      ifp->lease_expire = ifp->now + lease * 1000;
+      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
+      // assume DHCP server = router until ARP resolves
+      memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
+      ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
+      ifp->state = MG_TCPIP_STATE_READY;  // BOUND state
+      uint64_t rand;
+      mg_random(&rand, sizeof(rand));
+      srand((unsigned int) (rand + mg_millis()));
+    } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) {  // renew
+      ifp->lease_expire = ifp->now + lease * 1000;
+      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
+    }  // TODO(): accept provided T1/T2 and store server IP for renewal (4.4)
+  }
+  if (ifp->state != state) onstatechange(ifp);
+}
+
+// Simple DHCP server that assigns a next IP address: ifp->ip + 1
+static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  uint8_t op = 0, *p = pkt->dhcp->options,
+          *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
+  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
+  // struct dhcp *req = pkt->dhcp;
+  struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
+  res.yiaddr = ifp->ip;
+  ((uint8_t *) (&res.yiaddr))[3]++;                // Offer our IP + 1
+  while (p + 1 < end && p[0] != 255) {             // Parse options
+    if (p[0] == 53 && p[1] == 1 && p + 2 < end) {  // Message type
+      op = p[2];
+    }
+    p += p[1] + 2;
+  }
+  if (op == 1 || op == 3) {         // DHCP Discover or DHCP Request
+    uint8_t msg = op == 1 ? 2 : 5;  // Message type: DHCP OFFER or DHCP ACK
+    uint8_t opts[] = {
+        53, 1, msg,                 // Message type
+        1,  4, 0,   0,   0,   0,    // Subnet mask
+        54, 4, 0,   0,   0,   0,    // Server ID
+        12, 3, 'm', 'i', 'p',       // Host name: "mip"
+        51, 4, 255, 255, 255, 255,  // Lease time
+        255                         // End of options
+    };
+    memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
+    memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
+    memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
+    memcpy(&res.options, opts, sizeof(opts));
+    res.magic = pkt->dhcp->magic;
+    res.xid = pkt->dhcp->xid;
+    if (ifp->enable_get_gateway) {
+      ifp->gw = res.yiaddr;
+      memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
+    }
+    tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
+           op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
+  }
+}
+
+static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
+  if (c == NULL) {
+    // No UDP listener on this port. Should send ICMP, but keep silent.
+  } else {
+    c->rem.port = pkt->udp->sport;
+    memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+    struct connstate *s = (struct connstate *) (c + 1);
+    memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
+    if (c->recv.len >= MG_MAX_RECV_SIZE) {
+      mg_error(c, "max_recv_buf_size reached");
+    } else if (c->recv.size - c->recv.len < pkt->pay.len &&
+               !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
+      mg_error(c, "oom");
+    } else {
+      memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
+      c->recv.len += pkt->pay.len;
+      mg_call(c, MG_EV_READ, &pkt->pay.len);
+    }
+  }
+}
+
+static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
+                     uint8_t flags, uint16_t sport, uint16_t dport,
+                     uint32_t seq, uint32_t ack, const void *buf, size_t len) {
+#if 0
+  uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0};  // MSS = 1460, SACK permitted
+  if (flags & TH_SYN) {
+    // Handshake? Set MSS
+    buf = opts;
+    len = sizeof(opts);
+  }
+#endif
+  struct ip *ip =
+      tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
+  struct tcp *tcp = (struct tcp *) (ip + 1);
+  memset(tcp, 0, sizeof(*tcp));
+  if (buf != NULL && len) memmove(tcp + 1, buf, len);
+  tcp->sport = sport;
+  tcp->dport = dport;
+  tcp->seq = seq;
+  tcp->ack = ack;
+  tcp->flags = flags;
+  tcp->win = mg_htons(MIP_TCP_WIN);
+  tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
+  // if (flags & TH_SYN) tcp->off = 0x70;  // Handshake? header size 28 bytes
+
+  uint32_t cs = 0;
+  uint16_t n = (uint16_t) (sizeof(*tcp) + len);
+  uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
+  cs = csumup(cs, tcp, n);
+  cs = csumup(cs, &ip->src, sizeof(ip->src));
+  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  cs = csumup(cs, pseudo, sizeof(pseudo));
+  tcp->csum = csumfin(cs);
+  MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
+              mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
+              mg_ntohs(tcp->dport), tcp->flags, len));
+  // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
+  return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
+}
+
+static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt,
+                         uint8_t flags, uint32_t seq, const void *buf,
+                         size_t len) {
+  uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
+  return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport,
+                pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta),
+                buf, len);
+}
+
+static struct mg_connection *accept_conn(struct mg_connection *lsn,
+                                         struct pkt *pkt) {
+  struct mg_connection *c = mg_alloc_conn(lsn->mgr);
+  if (c == NULL) {
+    MG_ERROR(("OOM"));
+    return NULL;
+  }
+  struct connstate *s = (struct connstate *) (c + 1);
+  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
+  memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
+  settmout(c, MIP_TTYPE_KEEPALIVE);
+  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+  c->rem.port = pkt->tcp->sport;
+  MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
+  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
+  c->is_accepted = 1;
+  c->is_hexdumping = lsn->is_hexdumping;
+  c->pfn = lsn->pfn;
+  c->loc = lsn->loc;
+  c->pfn_data = lsn->pfn_data;
+  c->fn = lsn->fn;
+  c->fn_data = lsn->fn_data;
+  mg_call(c, MG_EV_OPEN, NULL);
+  mg_call(c, MG_EV_ACCEPT, NULL);
+  return c;
+}
+
+static size_t trim_len(struct mg_connection *c, size_t len) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8;
+  size_t max_headers_len =
+      eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
+  size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len;
+
+  // If the frame exceeds the available buffer, trim the length
+  if (len + max_headers_len > ifp->tx.len) {
+    len = ifp->tx.len - max_headers_len;
+  }
+  // Ensure the MTU isn't lower than the minimum allowed value
+  if (ifp->mtu < min_mtu) {
+    MG_ERROR(("MTU is lower than minimum, capping to %lu", min_mtu));
+    ifp->mtu = (uint16_t) min_mtu;
+  }
+  // If the total packet size exceeds the MTU, trim the length
+  if (len + max_headers_len - eth_h_len > ifp->mtu) {
+    len = ifp->mtu - max_headers_len + eth_h_len;
+    if (c->is_udp) {
+      MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
+    }
+  }
+
+  return len;
+}
+
+long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  struct connstate *s = (struct connstate *) (c + 1);
+  uint32_t dst_ip = *(uint32_t *) c->rem.ip;
+  len = trim_len(c, len);
+  if (c->is_udp) {
+    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len);
+  } else {
+    size_t sent =
+        tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
+               mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
+    if (sent == 0) {
+      return MG_IO_WAIT;
+    } else if (sent == (size_t) -1) {
+      return MG_IO_ERR;
+    } else {
+      s->seq += (uint32_t) len;
+      if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
+    }
+  }
+  return (long) len;
+}
+
+static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) {
+  long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
+  if (n == MG_IO_ERR) {
+    mg_error(c, "TLS recv error");
+  } else if (n > 0) {
+    // Decrypted successfully - trigger MG_EV_READ
+    io->len += (size_t) n;
+    mg_call(c, MG_EV_READ, &n);
+  }
+}
+
+static void read_conn(struct mg_connection *c, struct pkt *pkt) {
+  struct connstate *s = (struct connstate *) (c + 1);
+  struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
+  uint32_t seq = mg_ntohl(pkt->tcp->seq);
+  uint32_t rem_ip;
+  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+  if (pkt->tcp->flags & TH_FIN) {
+    // If we initiated the closure, we reply with ACK upon receiving FIN
+    // If we didn't initiate it, we reply with FIN as part of the normal TCP
+    // closure process
+    uint8_t flags = TH_ACK;
+    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
+    if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
+      if (s->seq == mg_htonl(pkt->tcp->ack)) {  // Simultaneous closure ?
+        s->seq++;                               // Yes. Increment our SEQ
+      } else {                                  // Otherwise,
+        s->seq = mg_htonl(pkt->tcp->ack);       // Set to peer's ACK
+      }
+    } else {
+      flags |= TH_FIN;
+      c->is_draining = 1;
+      settmout(c, MIP_TTYPE_FIN);
+    }
+    tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
+           c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+  } else if (pkt->pay.len == 0) {
+    // TODO(cpq): handle this peer's ACK
+  } else if (seq != s->ack) {
+    uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
+    if (s->ack == ack) {
+      MG_VERBOSE(("ignoring duplicate pkt"));
+    } else {
+      MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
+      tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
+             c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
+             0);
+    }
+  } else if (io->size - io->len < pkt->pay.len &&
+             !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
+    mg_error(c, "oom");
+  } else {
+    // Copy TCP payload into the IO buffer. If the connection is plain text,
+    // we copy to c->recv. If the connection is TLS, this data is encrypted,
+    // therefore we copy that encrypted data to the c->rtls iobuffer instead,
+    // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
+    // call back mg_io_recv() which grabs raw data from c->rtls
+    memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
+    io->len += pkt->pay.len;
+
+    MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
+    // Advance ACK counter
+    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
+    s->unacked += pkt->pay.len;
+    // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
+    if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
+      // Send ACK immediately
+      MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
+      tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
+             c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL,
+             0);
+      s->unacked = 0;
+      s->acked = s->ack;
+      if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
+    } else {
+      // if not already running, setup a timer to send an ACK later
+      if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
+    }
+
+    if (c->is_tls && c->is_tls_hs) {
+      mg_tls_handshake(c);
+    } else if (c->is_tls) {
+      // TLS connection. Make room for decrypted data in c->recv
+      io = &c->recv;
+      if (io->size - io->len < pkt->pay.len &&
+          !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
+        mg_error(c, "oom");
+      } else {
+        // Decrypt data directly into c->recv
+        handle_tls_recv(c, io);
+      }
+    } else {
+      // Plain text connection, data is already in c->recv, trigger
+      // MG_EV_READ
+      mg_call(c, MG_EV_READ, &pkt->pay.len);
+    }
+  }
+}
+
+static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+  struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
+#if 0
+  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
+#endif
+  if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) {
+    s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
+    tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
+    c->is_connecting = 0;  // Client connected
+    settmout(c, MIP_TTYPE_KEEPALIVE);
+    mg_call(c, MG_EV_CONNECT, NULL);  // Let user know
+  } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) {
+    // mg_hexdump(pkt->raw.buf, pkt->raw.len);
+    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else if (c != NULL && pkt->tcp->flags & TH_RST) {
+    mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
+  } else if (c != NULL) {
+#if 0
+    MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
+              mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
+              mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
+    mg_hexdump(pkt->pay.buf, pkt->pay.len);
+#endif
+    s->tmiss = 0;                         // Reset missed keep-alive counter
+    if (s->ttype == MIP_TTYPE_KEEPALIVE)  // Advance keep-alive timer
+      settmout(c,
+               MIP_TTYPE_KEEPALIVE);  // unless a former ACK timeout is pending
+    read_conn(c, pkt);  // Override timer with ACK timeout if needed
+  } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
+    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else if (pkt->tcp->flags & TH_RST) {
+    if (c->is_accepted) mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
+    // ignore RST if not connected
+  } else if (pkt->tcp->flags & TH_SYN) {
+    // Use peer's source port as ISN, in order to recognise the handshake
+    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
+    tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
+  } else if (pkt->tcp->flags & TH_FIN) {
+    tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
+    accept_conn(c, pkt);
+  } else if (!c->is_accepted) {  // no peer
+    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else {
+    // MG_VERBOSE(("dropped silently.."));
+  }
+}
+
+static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & IP_FRAG_OFFSET_MSK) {
+    if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1);
+    if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
+    struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+    if (c) mg_error(c, "Received fragmented packet");
+  } else if (pkt->ip->proto == 1) {
+    pkt->icmp = (struct icmp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
+    mkpay(pkt, pkt->icmp + 1);
+    rx_icmp(ifp, pkt);
+  } else if (pkt->ip->proto == 17) {
+    pkt->udp = (struct udp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    mkpay(pkt, pkt->udp + 1);
+    MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
+                mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
+                mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
+    if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
+      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
+      mkpay(pkt, pkt->dhcp + 1);
+      rx_dhcp_client(ifp, pkt);
+    } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) {
+      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
+      mkpay(pkt, pkt->dhcp + 1);
+      rx_dhcp_server(ifp, pkt);
+    } else {
+      rx_udp(ifp, pkt);
+    }
+  } else if (pkt->ip->proto == 6) {
+    pkt->tcp = (struct tcp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->tcp)) return;
+    mkpay(pkt, pkt->tcp + 1);
+    uint16_t iplen = mg_ntohs(pkt->ip->len);
+    uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
+    if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
+    MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
+                mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
+                mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
+    rx_tcp(ifp, pkt);
+  }
+}
+
+static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("IP %d", (int) len));
+  if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
+    pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
+    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
+    mkpay(pkt, pkt->icmp + 1);
+    rx_icmp(ifp, pkt);
+  } else if (pkt->ip6->proto == 17) {
+    pkt->udp = (struct udp *) (pkt->ip6 + 1);
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    // MG_DEBUG(("  UDP %u %u -> %u", len, mg_htons(udp->sport),
+    // mg_htons(udp->dport)));
+    mkpay(pkt, pkt->udp + 1);
+  }
+}
+
+static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
+  struct pkt pkt;
+  memset(&pkt, 0, sizeof(pkt));
+  pkt.raw.buf = (char *) buf;
+  pkt.raw.len = len;
+  pkt.eth = (struct eth *) buf;
+  // mg_hexdump(buf, len > 16 ? 16: len);
+  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
+  if (ifp->enable_mac_check &&
+      memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
+      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
+    return;
+  if (ifp->enable_crc32_check && len > 4) {
+    len -= 4;  // TODO(scaprile): check on bigendian
+    uint32_t crc = mg_crc32(0, (const char *) buf, len);
+    if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return;
+  }
+  if (pkt.eth->type == mg_htons(0x806)) {
+    pkt.arp = (struct arp *) (pkt.eth + 1);
+    if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;  // Truncated
+    rx_arp(ifp, &pkt);
+  } else if (pkt.eth->type == mg_htons(0x86dd)) {
+    pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
+    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;  // Truncated
+    if ((pkt.ip6->ver >> 4) != 0x6) return;                         // Not IP
+    mkpay(&pkt, pkt.ip6 + 1);
+    rx_ip6(ifp, &pkt);
+  } else if (pkt.eth->type == mg_htons(0x800)) {
+    pkt.ip = (struct ip *) (pkt.eth + 1);
+    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
+    // Truncate frame to what IP header tells us
+    if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
+      pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
+    }
+    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
+    if ((pkt.ip->ver >> 4) != 4) return;                           // Not IP
+    mkpay(&pkt, pkt.ip + 1);
+    rx_ip(ifp, &pkt);
+  } else {
+    MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
+    if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len >= 32 ? 32 : len);
+  }
+}
+
+static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
+  struct mg_connection *c;
+  bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now);
+  ifp->now = now;
+
+#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
+  if (expired_1000ms) {
+    const char *names[] = {"down", "up", "req", "ready"};
+    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
+             names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
+             ifp->ndrop, ifp->nerr));
+  }
+#endif
+  // Handle physical interface up/down status
+  if (expired_1000ms && ifp->driver->up) {
+    bool up = ifp->driver->up(ifp);
+    bool current = ifp->state != MG_TCPIP_STATE_DOWN;
+    if (up != current) {
+      ifp->state = up == false               ? MG_TCPIP_STATE_DOWN
+                   : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
+                                             : MG_TCPIP_STATE_READY;
+      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
+      onstatechange(ifp);
+    }
+    if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
+  }
+  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
+
+  // DHCP RFC-2131 (4.4)
+  if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
+    tx_dhcp_discover(ifp);  // INIT (4.4.1)
+  } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
+             ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
+    if (ifp->now >= ifp->lease_expire) {
+      ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
+      onstatechange(ifp);
+    } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
+               ((ifp->now / 1000) % 60) == 0) {
+      // hack: 30 min before deadline, try to rebind (4.3.6) every min
+      tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
+    }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
+  }
+
+  // Read data from the network
+  if (ifp->driver->rx != NULL) {  // Polling driver. We must call it
+    size_t len =
+        ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
+    if (len > 0) {
+      ifp->nrecv++;
+      mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
+    }
+  } else {  // Interrupt-based driver. Fills recv queue itself
+    char *buf;
+    size_t len = mg_queue_next(&ifp->recv_queue, &buf);
+    if (len > 0) {
+      mg_tcpip_rx(ifp, buf, len);
+      mg_queue_del(&ifp->recv_queue, len);
+    }
+  }
+
+  // Process timeouts
+  for (c = ifp->mgr->conns; c != NULL; c = c->next) {
+    if (c->is_udp || c->is_listening || c->is_resolving) continue;
+    struct connstate *s = (struct connstate *) (c + 1);
+    uint32_t rem_ip;
+    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+    if (now > s->timer) {
+      if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
+        MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
+        tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
+               mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+        s->acked = s->ack;
+      } else if (s->ttype == MIP_TTYPE_ARP) {
+        mg_error(c, "ARP timeout");
+      } else if (s->ttype == MIP_TTYPE_SYN) {
+        mg_error(c, "Connection timeout");
+      } else if (s->ttype == MIP_TTYPE_FIN) {
+        c->is_closing = 1;
+        continue;
+      } else {
+        if (s->tmiss++ > 2) {
+          mg_error(c, "keepalive");
+        } else {
+          MG_VERBOSE(("%lu keepalive", c->id));
+          tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
+                 mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
+        }
+      }
+
+      settmout(c, MIP_TTYPE_KEEPALIVE);
+    }
+  }
+}
+
+// This function executes in interrupt context, thus it should copy data
+// somewhere fast. Note that newlib's malloc is not thread safe, thus use
+// our lock-free queue with preallocated buffer to copy data and return asap
+void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) {
+  char *p;
+  if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
+    memcpy(p, buf, len);
+    mg_queue_add(&ifp->recv_queue, len);
+    ifp->nrecv++;
+  } else {
+    ifp->ndrop++;
+  }
+}
+
+void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
+  // If MAC address is not set, make a random one
+  if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
+      ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
+    ifp->mac[0] = 0x02;  // Locally administered, unicast
+    mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
+    MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac));
+  }
+
+  if (ifp->driver->init && !ifp->driver->init(ifp)) {
+    MG_ERROR(("driver init failed"));
+  } else {
+    size_t framesize = 1540;
+    ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize;
+    if (ifp->recv_queue.size == 0)
+      ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
+    ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
+    ifp->timer_1000ms = mg_millis();
+    mgr->priv = ifp;
+    ifp->mgr = mgr;
+    ifp->mtu = MG_TCPIP_MTU_DEFAULT;
+    mgr->extraconnsize = sizeof(struct connstate);
+    if (ifp->ip == 0) ifp->enable_dhcp_client = true;
+    memset(ifp->gwmac, 255, sizeof(ifp->gwmac));  // Set to broadcast
+    mg_random(&ifp->eport, sizeof(ifp->eport));   // Random from 0 to 65535
+    ifp->eport |= MG_EPHEMERAL_PORT_BASE;         // Random from
+                                           // MG_EPHEMERAL_PORT_BASE to 65535
+    if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
+  }
+}
+
+void mg_tcpip_free(struct mg_tcpip_if *ifp) {
+  free(ifp->recv_queue.buf);
+  free(ifp->tx.buf);
+}
+
+static void send_syn(struct mg_connection *c) {
+  struct connstate *s = (struct connstate *) (c + 1);
+  uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  uint32_t rem_ip;
+  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+  tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL,
+         0);
+}
+
+void mg_connect_resolved(struct mg_connection *c) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  uint32_t rem_ip;
+  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+  c->is_resolving = 0;
+  if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
+  memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
+  c->loc.port = mg_htons(ifp->eport++);
+  MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
+            &c->rem));
+  mg_call(c, MG_EV_RESOLVE, NULL);
+  if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
+    struct connstate *s = (struct connstate *) (c + 1);
+    memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
+  } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) {
+    // If we're in the same LAN, fire an ARP lookup.
+    MG_DEBUG(("%lu ARP lookup...", c->id));
+    arp_ask(ifp, rem_ip);
+    settmout(c, MIP_TTYPE_ARP);
+    c->is_arplooking = 1;
+    c->is_connecting = 1;
+  } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
+    struct connstate *s = (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
+    uint8_t mcastp[3] = {0x01, 0x00, 0x5E};              // multicast group
+    memcpy(s->mac, mcastp, 3);
+    memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3);  // 23 LSb
+    s->mac[3] &= 0x7F;
+  } else {
+    struct connstate *s = (struct connstate *) (c + 1);
+    memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
+    if (c->is_udp) {
+      mg_call(c, MG_EV_CONNECT, NULL);
+    } else {
+      send_syn(c);
+      settmout(c, MIP_TTYPE_SYN);
+      c->is_connecting = 1;
+    }
+  }
+}
+
+bool mg_open_listener(struct mg_connection *c, const char *url) {
+  c->loc.port = mg_htons(mg_url_port(url));
+  return true;
+}
+
+static void write_conn(struct mg_connection *c) {
+  long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
+                       : mg_io_send(c, c->send.buf, c->send.len);
+  if (len == MG_IO_ERR) {
+    mg_error(c, "tx err");
+  } else if (len > 0) {
+    mg_iobuf_del(&c->send, 0, (size_t) len);
+    mg_call(c, MG_EV_WRITE, &len);
+  }
+}
+
+static void init_closure(struct mg_connection *c) {
+  struct connstate *s = (struct connstate *) (c + 1);
+  if (c->is_udp == false && c->is_listening == false &&
+      c->is_connecting == false) {  // For TCP conns,
+    struct mg_tcpip_if *ifp =
+        (struct mg_tcpip_if *) c->mgr->priv;  // send TCP FIN
+    uint32_t rem_ip;
+    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+    tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
+           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    settmout(c, MIP_TTYPE_FIN);
+  }
+}
+
+static void close_conn(struct mg_connection *c) {
+  struct connstate *s = (struct connstate *) (c + 1);
+  mg_iobuf_free(&s->raw);  // For TLS connections, release raw data
+  mg_close_conn(c);
+}
+
+static bool can_write(struct mg_connection *c) {
+  return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
+         c->is_tls_hs == 0 && c->is_arplooking == 0;
+}
+
+void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv;
+  struct mg_connection *c, *tmp;
+  uint64_t now = mg_millis();
+  mg_timer_poll(&mgr->timers, now);
+  if (ifp == NULL || ifp->driver == NULL) return;
+  mg_tcpip_poll(ifp, now);
+  for (c = mgr->conns; c != NULL; c = tmp) {
+    tmp = c->next;
+    struct connstate *s = (struct connstate *) (c + 1);
+    mg_call(c, MG_EV_POLL, &now);
+    MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
+                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
+                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
+    if (c->is_tls && mg_tls_pending(c) > 0)
+      handle_tls_recv(c, (struct mg_iobuf *) &c->rtls);
+    if (can_write(c)) write_conn(c);
+    if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN)
+      init_closure(c);
+    if (c->is_closing) close_conn(c);
+  }
+  (void) ms;
+}
+
+bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
+  bool res = false;
+  uint32_t rem_ip;
+  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
+  if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
+    mg_error(c, "net down");
+  } else if (c->is_udp) {
+    struct connstate *s = (struct connstate *) (c + 1);
+    len = trim_len(c, len);  // Trimming length if necessary
+    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
+    res = true;
+  } else {
+    res = mg_iobuf_add(&c->send, c->send.len, buf, len);
+  }
+  return res;
+}
+#endif  // MG_ENABLE_TCPIP
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/ota_dummy.c"
+#endif
+
+
+
+#if MG_OTA == MG_OTA_NONE
+bool mg_ota_begin(size_t new_firmware_size) {
+  (void) new_firmware_size;
+  return true;
+}
+bool mg_ota_write(const void *buf, size_t len) {
+  (void) buf, (void) len;
+  return true;
+}
+bool mg_ota_end(void) {
+  return true;
+}
+bool mg_ota_commit(void) {
+  return true;
+}
+bool mg_ota_rollback(void) {
+  return true;
+}
+int mg_ota_status(int fw) {
+  (void) fw;
+  return 0;
+}
+uint32_t mg_ota_crc32(int fw) {
+  (void) fw;
+  return 0;
+}
+uint32_t mg_ota_timestamp(int fw) {
+  (void) fw;
+  return 0;
+}
+size_t mg_ota_size(int fw) {
+  (void) fw;
+  return 0;
+}
+MG_IRAM void mg_ota_boot(void) {
+}
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/ota_esp32.c"
+#endif
+
+
+#if MG_ARCH == MG_ARCH_ESP32 && MG_OTA == MG_OTA_ESP32
+
+static const esp_partition_t *s_ota_update_partition;
+static esp_ota_handle_t s_ota_update_handle;
+static bool s_ota_success;
+
+// Those empty macros do nothing, but mark places in the code which could
+// potentially trigger a watchdog reboot due to the log flash erase operation
+#define disable_wdt()
+#define enable_wdt()
+
+bool mg_ota_begin(size_t new_firmware_size) {
+  if (s_ota_update_partition != NULL) {
+    MG_ERROR(("Update in progress. Call mg_ota_end() ?"));
+    return false;
+  } else {
+    s_ota_success = false;
+    disable_wdt();
+    s_ota_update_partition = esp_ota_get_next_update_partition(NULL);
+    esp_err_t err = esp_ota_begin(s_ota_update_partition, new_firmware_size,
+                                  &s_ota_update_handle);
+    enable_wdt();
+    MG_DEBUG(("esp_ota_begin(): %d", err));
+    s_ota_success = (err == ESP_OK);
+  }
+  return s_ota_success;
+}
+
+bool mg_ota_write(const void *buf, size_t len) {
+  disable_wdt();
+  esp_err_t err = esp_ota_write(s_ota_update_handle, buf, len);
+  enable_wdt();
+  MG_INFO(("esp_ota_write(): %d", err));
+  s_ota_success = err == ESP_OK;
+  return s_ota_success;
+}
+
+bool mg_ota_end(void) {
+  esp_err_t err = esp_ota_end(s_ota_update_handle);
+  MG_DEBUG(("esp_ota_end(%p): %d", s_ota_update_handle, err));
+  if (s_ota_success && err == ESP_OK) {
+    err = esp_ota_set_boot_partition(s_ota_update_partition);
+    s_ota_success = (err == ESP_OK);
+  }
+  MG_DEBUG(("Finished ESP32 OTA, success: %d", s_ota_success));
+  s_ota_update_partition = NULL;
+  return s_ota_success;
+}
+
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/ota_flash.c"
+#endif
+
+
+
+
+
+// This OTA implementation uses the internal flash API outlined in device.h
+// It splits flash into 2 equal partitions, and stores OTA status in the
+// last sector of the partition.
+
+#if MG_OTA == MG_OTA_FLASH
+
+#define MG_OTADATA_KEY 0xb07afed0
+
+static char *s_addr;      // Current address to write to
+static size_t s_size;     // Firmware size to flash. In-progress indicator
+static uint32_t s_crc32;  // Firmware checksum
+
+struct mg_otadata {
+  uint32_t crc32, size, timestamp, status;
+};
+
+bool mg_ota_begin(size_t new_firmware_size) {
+  bool ok = false;
+  if (s_size) {
+    MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
+  } else {
+    size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
+    s_crc32 = 0;
+    s_addr = (char *) mg_flash_start() + half;
+    MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
+    if (new_firmware_size < max) {
+      ok = true;
+      s_size = new_firmware_size;
+      MG_INFO(("Starting OTA, firmware size %lu", s_size));
+    } else {
+      MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max));
+    }
+  }
+  return ok;
+}
+
+bool mg_ota_write(const void *buf, size_t len) {
+  bool ok = false;
+  if (s_size == 0) {
+    MG_ERROR(("OTA is not started, call mg_ota_begin()"));
+  } else {
+    size_t align = mg_flash_write_align();
+    size_t len_aligned_down = MG_ROUND_DOWN(len, align);
+    if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down);
+    if (len_aligned_down < len) {
+      size_t left = len - len_aligned_down;
+      char tmp[align];
+      memset(tmp, 0xff, sizeof(tmp));
+      memcpy(tmp, (char *) buf + len_aligned_down, left);
+      ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp));
+    }
+    s_crc32 = mg_crc32(s_crc32, (char *) buf, len);  // Update CRC
+    MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok));
+    s_addr += len;
+  }
+  return ok;
+}
+
+MG_IRAM static uint32_t mg_fwkey(int fw) {
+  uint32_t key = MG_OTADATA_KEY + fw;
+  int bank = mg_flash_bank();
+  if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
+  if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
+  return key;
+}
+
+bool mg_ota_end(void) {
+  char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
+  bool ok = false;
+  if (s_size) {
+    size_t size = s_addr - base;
+    uint32_t crc32 = mg_crc32(0, base, s_size);
+    if (size == s_size && crc32 == s_crc32) {
+      uint32_t now = (uint32_t) (mg_now() / 1000);
+      struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
+      uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
+      ok = mg_flash_save(NULL, key, &od, sizeof(od));
+    }
+    MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size,
+              size, ok ? "ok" : "fail"));
+    s_size = 0;
+    if (ok) ok = mg_flash_swap_bank();
+  }
+  MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
+  return ok;
+}
+
+MG_IRAM static struct mg_otadata mg_otadata(int fw) {
+  uint32_t key = mg_fwkey(fw);
+  struct mg_otadata od = {};
+  MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev"));
+  mg_flash_load(NULL, key, &od, sizeof(od));
+  // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key));
+  // mg_hexdump(&od, sizeof(od));
+  return od;
+}
+
+int mg_ota_status(int fw) {
+  struct mg_otadata od = mg_otadata(fw);
+  return od.status;
+}
+uint32_t mg_ota_crc32(int fw) {
+  struct mg_otadata od = mg_otadata(fw);
+  return od.crc32;
+}
+uint32_t mg_ota_timestamp(int fw) {
+  struct mg_otadata od = mg_otadata(fw);
+  return od.timestamp;
+}
+size_t mg_ota_size(int fw) {
+  struct mg_otadata od = mg_otadata(fw);
+  return od.size;
+}
+
+MG_IRAM bool mg_ota_commit(void) {
+  bool ok = true;
+  struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
+  if (od.status != MG_OTA_COMMITTED) {
+    od.status = MG_OTA_COMMITTED;
+    MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
+    ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od));
+  }
+  return ok;
+}
+
+bool mg_ota_rollback(void) {
+  MG_DEBUG(("Rolling firmware back"));
+  if (mg_flash_bank() == 0) {
+    // No dual bank support. Mark previous firmware as FIRST_BOOT
+    struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
+    prev.status = MG_OTA_FIRST_BOOT;
+    return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev,
+                         sizeof(prev));
+  } else {
+    return mg_flash_swap_bank();
+  }
+}
+
+MG_IRAM void mg_ota_boot(void) {
+  MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
+  struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
+  struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
+
+  if (curr.status == MG_OTA_FIRST_BOOT) {
+    if (prev.status == MG_OTA_UNAVAILABLE) {
+      MG_INFO(("Setting previous firmware state to committed"));
+      prev.status = MG_OTA_COMMITTED;
+      mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev));
+    }
+    curr.status = MG_OTA_UNCOMMITTED;
+    MG_INFO(("First boot, setting status to UNCOMMITTED"));
+    mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr));
+  } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
+    // Swap paritions. Pray power does not disappear
+    size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
+    char *partition1 = mg_flash_start();
+    char *partition2 = mg_flash_start() + fs / 2;
+    size_t ofs, max = fs / 2 - ss;  // Set swap size to the whole partition
+
+    if (curr.status != MG_OTA_UNAVAILABLE &&
+        prev.status != MG_OTA_UNAVAILABLE) {
+      // We know exact sizes of both firmwares.
+      // Shrink swap size to the MAX(firmware1, firmware2)
+      size_t sz = curr.size > prev.size ? curr.size : prev.size;
+      if (sz > 0 && sz < max) max = sz;
+    }
+
+    // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
+    prev.status = MG_OTA_UNCOMMITTED;
+    mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
+                  sizeof(prev));
+    mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
+                  sizeof(curr));
+
+    MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
+    MG_INFO(("Do NOT power off..."));
+    mg_log_level = MG_LL_NONE;
+
+    // We use the last sector of partition2 for OTA data/config storage
+    // Therefore we can use last sector of partition1 for swapping
+    char *tmpsector = partition1 + fs / 2 - ss;  // Last sector of partition1
+    (void) tmpsector;
+    for (ofs = 0; ofs < max; ofs += ss) {
+      // mg_flash_erase(tmpsector);
+      mg_flash_write(tmpsector, partition1 + ofs, ss);
+      // mg_flash_erase(partition1 + ofs);
+      mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
+      // mg_flash_erase(partition2 + ofs);
+      mg_flash_write(partition2 + ofs, tmpsector, ss);
+    }
+    mg_device_reset();
+  }
+}
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/printf.c"
+#endif
+
+
+
+
+size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) {
+  size_t len = mg_snprintf(NULL, 0, fmt, ap);
+  char *buf;
+  if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
+    len = 0;  // Nah. Not enough space
+  } else {
+    len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
+    mg_queue_add(q, len);
+  }
+  return len;
+}
+
+size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
+  va_list ap;
+  size_t len;
+  va_start(ap, fmt);
+  len = mg_queue_vprintf(q, fmt, &ap);
+  va_end(ap);
+  return len;
+}
+
+static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
+  struct mg_iobuf *io = (struct mg_iobuf *) param;
+  if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
+  if (io->len + 2 <= io->size) {
+    io->buf[io->len++] = (uint8_t) ch;
+    io->buf[io->len] = 0;
+  } else if (io->len < io->size) {
+    io->buf[io->len++] = 0;  // Guarantee to 0-terminate
+  }
+}
+
+static void mg_putchar_iobuf_static(char ch, void *param) {
+  mg_pfn_iobuf_private(ch, param, false);
+}
+
+void mg_pfn_iobuf(char ch, void *param) {
+  mg_pfn_iobuf_private(ch, param, true);
+}
+
+size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
+  struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
+  size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
+  if (n < len) buf[n] = '\0';
+  return n;
+}
+
+size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
+  va_list ap;
+  size_t n;
+  va_start(ap, fmt);
+  n = mg_vsnprintf(buf, len, fmt, &ap);
+  va_end(ap);
+  return n;
+}
+
+char *mg_vmprintf(const char *fmt, va_list *ap) {
+  struct mg_iobuf io = {0, 0, 0, 256};
+  mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
+  return (char *) io.buf;
+}
+
+char *mg_mprintf(const char *fmt, ...) {
+  char *s;
+  va_list ap;
+  va_start(ap, fmt);
+  s = mg_vmprintf(fmt, &ap);
+  va_end(ap);
+  return s;
+}
+
+void mg_pfn_stdout(char c, void *param) {
+  putchar(c);
+  (void) param;
+}
+
+static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
+  return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+}
+
+static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t *p) {
+  return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", mg_ntohs(p[0]),
+                    mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
+                    mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
+                    mg_ntohs(p[7]));
+}
+
+size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) {
+  uint8_t *p = va_arg(*ap, uint8_t *);
+  return print_ip4(out, arg, p);
+}
+
+size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) {
+  uint16_t *p = va_arg(*ap, uint16_t *);
+  return print_ip6(out, arg, p);
+}
+
+size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) {
+  struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
+  if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip);
+  return print_ip4(out, arg, (uint8_t *) &addr->ip);
+}
+
+size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap) {
+  struct mg_addr *a = va_arg(*ap, struct mg_addr *);
+  return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, mg_ntohs(a->port));
+}
+
+size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) {
+  uint8_t *p = va_arg(*ap, uint8_t *);
+  return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2],
+                    p[3], p[4], p[5]);
+}
+
+static char mg_esc(int c, bool esc) {
+  const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
+  for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
+    if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
+  }
+  return 0;
+}
+
+static char mg_escape(int c) {
+  return mg_esc(c, true);
+}
+
+static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
+                   size_t len) {
+  size_t i = 0, extra = 0;
+  for (i = 0; i < len && buf[i] != '\0'; i++) {
+    char c = mg_escape(buf[i]);
+    if (c) {
+      out('\\', ptr), out(c, ptr), extra++;
+    } else {
+      out(buf[i], ptr);
+    }
+  }
+  return i + extra;
+}
+
+static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf,
+                   size_t len) {
+  size_t i, j, n = 0;
+  const char *t =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  for (i = 0; i < len; i += 3) {
+    uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
+            c3 = i + 2 < len ? buf[i + 2] : 0;
+    char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
+    if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
+    if (i + 2 < len) tmp[3] = t[c3 & 63];
+    for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg);
+    n += j;
+  }
+  return n;
+}
+
+size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) {
+  size_t bl = (size_t) va_arg(*ap, int);
+  uint8_t *p = va_arg(*ap, uint8_t *);
+  const char *hex = "0123456789abcdef";
+  size_t j;
+  for (j = 0; j < bl; j++) {
+    out(hex[(p[j] >> 4) & 0x0F], arg);
+    out(hex[p[j] & 0x0F], arg);
+  }
+  return 2 * bl;
+}
+size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap) {
+  size_t len = (size_t) va_arg(*ap, int);
+  uint8_t *buf = va_arg(*ap, uint8_t *);
+  return bcpy(out, arg, buf, len);
+}
+
+size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) {
+  size_t len = (size_t) va_arg(*ap, int);
+  char *p = va_arg(*ap, char *);
+  if (len == 0) len = p == NULL ? 0 : strlen(p);
+  return qcpy(out, arg, p, len);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/queue.c"
+#endif
+
+
+
+#if (defined(__GNUC__) && (__GNUC__ > 4) ||                                \
+     (defined(__GNUC_MINOR__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \
+    defined(__clang__)
+#define MG_MEMORY_BARRIER() __sync_synchronize()
+#elif defined(_MSC_VER) && _MSC_VER >= 1700
+#define MG_MEMORY_BARRIER() MemoryBarrier()
+#elif !defined(MG_MEMORY_BARRIER)
+#define MG_MEMORY_BARRIER()
+#endif
+
+// Every message in a queue is prepended by a 32-bit message length (ML).
+// If ML is 0, then it is the end, and reader must wrap to the beginning.
+//
+//  Queue when q->tail <= q->head:
+//  |----- free -----| ML | message1 | ML | message2 |  ----- free ------|
+//  ^                ^                               ^                   ^
+// buf              tail                            head                len
+//
+//  Queue when q->tail > q->head:
+//  | ML | message2 |----- free ------| ML | message1 | 0 |---- free ----|
+//  ^               ^                 ^                                  ^
+// buf             head              tail                               len
+
+void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
+  q->size = size;
+  q->buf = buf;
+  q->head = q->tail = 0;
+}
+
+static size_t mg_queue_read_len(struct mg_queue *q) {
+  uint32_t n = 0;
+  MG_MEMORY_BARRIER();
+  memcpy(&n, q->buf + q->tail, sizeof(n));
+  assert(q->tail + n + sizeof(n) <= q->size);
+  return n;
+}
+
+static void mg_queue_write_len(struct mg_queue *q, size_t len) {
+  uint32_t n = (uint32_t) len;
+  memcpy(q->buf + q->head, &n, sizeof(n));
+  MG_MEMORY_BARRIER();
+}
+
+size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
+  size_t space = 0, hs = sizeof(uint32_t) * 2;  // *2 is for the 0 marker
+  if (q->head >= q->tail && q->head + len + hs <= q->size) {
+    space = q->size - q->head - hs;  // There is enough space
+  } else if (q->head >= q->tail && q->tail > hs) {
+    mg_queue_write_len(q, 0);  // Not enough space ahead
+    q->head = 0;               // Wrap head to the beginning
+  }
+  if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
+  if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
+  return space;
+}
+
+size_t mg_queue_next(struct mg_queue *q, char **buf) {
+  size_t len = 0;
+  if (q->tail != q->head) {
+    len = mg_queue_read_len(q);
+    if (len == 0) {  // Zero (head wrapped) ?
+      q->tail = 0;   // Reset tail to the start
+      if (q->head > q->tail) len = mg_queue_read_len(q);  // Read again
+    }
+  }
+  if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
+  assert(q->tail + len <= q->size);
+  return len;
+}
+
+void mg_queue_add(struct mg_queue *q, size_t len) {
+  assert(len > 0);
+  mg_queue_write_len(q, len);
+  assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
+  q->head += len + sizeof(uint32_t);
+}
+
+void mg_queue_del(struct mg_queue *q, size_t len) {
+  q->tail += len + sizeof(uint32_t);
+  assert(q->tail + sizeof(uint32_t) <= q->size);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/rpc.c"
+#endif
+
+
+
+void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
+                void (*fn)(struct mg_rpc_req *), void *fn_data) {
+  struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
+  if (rpc != NULL) {
+    rpc->method.buf = mg_mprintf("%.*s", method.len, method.buf);
+    rpc->method.len = method.len;
+    rpc->fn = fn;
+    rpc->fn_data = fn_data;
+    rpc->next = *head, *head = rpc;
+  }
+}
+
+void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
+  struct mg_rpc *r;
+  while ((r = *head) != NULL) {
+    if (r->fn == fn || fn == NULL) {
+      *head = r->next;
+      free((void *) r->method.buf);
+      free(r);
+    } else {
+      head = &(*head)->next;
+    }
+  }
+}
+
+static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
+  struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
+  while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
+  if (h != NULL) {
+    r->rpc = h;
+    h->fn(r);
+  } else {
+    mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.buf);
+  }
+}
+
+void mg_rpc_process(struct mg_rpc_req *r) {
+  int len, off = mg_json_get(r->frame, "$.method", &len);
+  if (off > 0 && r->frame.buf[off] == '"') {
+    struct mg_str method = mg_str_n(&r->frame.buf[off + 1], (size_t) len - 2);
+    mg_rpc_call(r, method);
+  } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
+             (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
+    mg_rpc_call(r, mg_str(""));  // JSON response! call "" method handler
+  } else {
+    mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
+               r->frame.buf);  // Invalid
+  }
+}
+
+void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
+  int len, off = mg_json_get(r->frame, "$.id", &len);
+  if (off > 0) {
+    mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len,
+               &r->frame.buf[off], mg_print_esc, 0, "result");
+    mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
+    mg_xprintf(r->pfn, r->pfn_data, "}");
+  }
+}
+
+void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  mg_rpc_vok(r, fmt, &ap);
+  va_end(ap);
+}
+
+void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) {
+  int len, off = mg_json_get(r->frame, "$.id", &len);
+  mg_xprintf(r->pfn, r->pfn_data, "{");
+  if (off > 0) {
+    mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len,
+               &r->frame.buf[off]);
+  }
+  mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error",
+             mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
+  mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
+  mg_xprintf(r->pfn, r->pfn_data, "}}");
+}
+
+void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  mg_rpc_verr(r, code, fmt, &ap);
+  va_end(ap);
+}
+
+static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
+  struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
+  size_t len = 0;
+  for (h = *head; h != NULL; h = h->next) {
+    if (h->method.len == 0) continue;  // Ignore response handler
+    len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
+                      mg_print_esc, (int) h->method.len, h->method.buf);
+  }
+  return len;
+}
+
+void mg_rpc_list(struct mg_rpc_req *r) {
+  mg_rpc_ok(r, "[%M]", print_methods, r->head);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/sha1.c"
+#endif
+/* Copyright(c) By Steve Reid <steve@edmweb.com> */
+/* 100% Public Domain */
+
+
+
+union char64long16 {
+  unsigned char c[64];
+  uint32_t l[16];
+};
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+static uint32_t blk0(union char64long16 *block, int i) {
+  if (MG_BIG_ENDIAN) {
+  } else {
+    block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
+                  (rol(block->l[i], 8) & 0x00FF00FF);
+  }
+  return block->l[i];
+}
+
+/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
+#undef blk
+#undef R0
+#undef R1
+#undef R2
+#undef R3
+#undef R4
+
+#define blk(i)                                                               \
+  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
+                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
+                          1))
+#define R0(v, w, x, y, z, i)                                          \
+  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
+  w = rol(w, 30);
+#define R1(v, w, x, y, z, i)                                  \
+  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
+  w = rol(w, 30);
+#define R2(v, w, x, y, z, i)                          \
+  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
+  w = rol(w, 30);
+#define R3(v, w, x, y, z, i)                                        \
+  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
+  w = rol(w, 30);
+#define R4(v, w, x, y, z, i)                          \
+  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
+  w = rol(w, 30);
+
+static void mg_sha1_transform(uint32_t state[5],
+                              const unsigned char *buffer) {
+  uint32_t a, b, c, d, e;
+  union char64long16 block[1];
+
+  memcpy(block, buffer, 64);
+  a = state[0];
+  b = state[1];
+  c = state[2];
+  d = state[3];
+  e = state[4];
+  R0(a, b, c, d, e, 0);
+  R0(e, a, b, c, d, 1);
+  R0(d, e, a, b, c, 2);
+  R0(c, d, e, a, b, 3);
+  R0(b, c, d, e, a, 4);
+  R0(a, b, c, d, e, 5);
+  R0(e, a, b, c, d, 6);
+  R0(d, e, a, b, c, 7);
+  R0(c, d, e, a, b, 8);
+  R0(b, c, d, e, a, 9);
+  R0(a, b, c, d, e, 10);
+  R0(e, a, b, c, d, 11);
+  R0(d, e, a, b, c, 12);
+  R0(c, d, e, a, b, 13);
+  R0(b, c, d, e, a, 14);
+  R0(a, b, c, d, e, 15);
+  R1(e, a, b, c, d, 16);
+  R1(d, e, a, b, c, 17);
+  R1(c, d, e, a, b, 18);
+  R1(b, c, d, e, a, 19);
+  R2(a, b, c, d, e, 20);
+  R2(e, a, b, c, d, 21);
+  R2(d, e, a, b, c, 22);
+  R2(c, d, e, a, b, 23);
+  R2(b, c, d, e, a, 24);
+  R2(a, b, c, d, e, 25);
+  R2(e, a, b, c, d, 26);
+  R2(d, e, a, b, c, 27);
+  R2(c, d, e, a, b, 28);
+  R2(b, c, d, e, a, 29);
+  R2(a, b, c, d, e, 30);
+  R2(e, a, b, c, d, 31);
+  R2(d, e, a, b, c, 32);
+  R2(c, d, e, a, b, 33);
+  R2(b, c, d, e, a, 34);
+  R2(a, b, c, d, e, 35);
+  R2(e, a, b, c, d, 36);
+  R2(d, e, a, b, c, 37);
+  R2(c, d, e, a, b, 38);
+  R2(b, c, d, e, a, 39);
+  R3(a, b, c, d, e, 40);
+  R3(e, a, b, c, d, 41);
+  R3(d, e, a, b, c, 42);
+  R3(c, d, e, a, b, 43);
+  R3(b, c, d, e, a, 44);
+  R3(a, b, c, d, e, 45);
+  R3(e, a, b, c, d, 46);
+  R3(d, e, a, b, c, 47);
+  R3(c, d, e, a, b, 48);
+  R3(b, c, d, e, a, 49);
+  R3(a, b, c, d, e, 50);
+  R3(e, a, b, c, d, 51);
+  R3(d, e, a, b, c, 52);
+  R3(c, d, e, a, b, 53);
+  R3(b, c, d, e, a, 54);
+  R3(a, b, c, d, e, 55);
+  R3(e, a, b, c, d, 56);
+  R3(d, e, a, b, c, 57);
+  R3(c, d, e, a, b, 58);
+  R3(b, c, d, e, a, 59);
+  R4(a, b, c, d, e, 60);
+  R4(e, a, b, c, d, 61);
+  R4(d, e, a, b, c, 62);
+  R4(c, d, e, a, b, 63);
+  R4(b, c, d, e, a, 64);
+  R4(a, b, c, d, e, 65);
+  R4(e, a, b, c, d, 66);
+  R4(d, e, a, b, c, 67);
+  R4(c, d, e, a, b, 68);
+  R4(b, c, d, e, a, 69);
+  R4(a, b, c, d, e, 70);
+  R4(e, a, b, c, d, 71);
+  R4(d, e, a, b, c, 72);
+  R4(c, d, e, a, b, 73);
+  R4(b, c, d, e, a, 74);
+  R4(a, b, c, d, e, 75);
+  R4(e, a, b, c, d, 76);
+  R4(d, e, a, b, c, 77);
+  R4(c, d, e, a, b, 78);
+  R4(b, c, d, e, a, 79);
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+  state[4] += e;
+  /* Erase working structures. The order of operations is important,
+   * used to ensure that compiler doesn't optimize those out. */
+  memset(block, 0, sizeof(block));
+  a = b = c = d = e = 0;
+  (void) a;
+  (void) b;
+  (void) c;
+  (void) d;
+  (void) e;
+}
+
+void mg_sha1_init(mg_sha1_ctx *context) {
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xEFCDAB89;
+  context->state[2] = 0x98BADCFE;
+  context->state[3] = 0x10325476;
+  context->state[4] = 0xC3D2E1F0;
+  context->count[0] = context->count[1] = 0;
+}
+
+void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
+                    size_t len) {
+  size_t i, j;
+
+  j = context->count[0];
+  if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
+  context->count[1] += (uint32_t) (len >> 29);
+  j = (j >> 3) & 63;
+  if ((j + len) > 63) {
+    memcpy(&context->buffer[j], data, (i = 64 - j));
+    mg_sha1_transform(context->state, context->buffer);
+    for (; i + 63 < len; i += 64) {
+      mg_sha1_transform(context->state, &data[i]);
+    }
+    j = 0;
+  } else
+    i = 0;
+  memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
+  unsigned i;
+  unsigned char finalcount[8], c;
+
+  for (i = 0; i < 8; i++) {
+    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
+                                      ((3 - (i & 3)) * 8)) &
+                                     255);
+  }
+  c = 0200;
+  mg_sha1_update(context, &c, 1);
+  while ((context->count[0] & 504) != 448) {
+    c = 0000;
+    mg_sha1_update(context, &c, 1);
+  }
+  mg_sha1_update(context, finalcount, 8);
+  for (i = 0; i < 20; i++) {
+    digest[i] =
+        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+  }
+  memset(context, '\0', sizeof(*context));
+  memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/sha256.c"
+#endif
+// https://github.com/B-Con/crypto-algorithms
+// Author:     Brad Conte (brad AT bradconte.com)
+// Disclaimer: This code is presented "as is" without any guarantees.
+// Details:    Defines the API for the corresponding SHA1 implementation.
+// Copyright:  public domain
+
+
+
+#define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
+#define ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
+#define maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define ep0(x) (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22))
+#define ep1(x) (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25))
+#define sig0(x) (ror(x, 7) ^ ror(x, 18) ^ ((x) >> 3))
+#define sig1(x) (ror(x, 17) ^ ror(x, 19) ^ ((x) >> 10))
+
+static const uint32_t mg_sha256_k[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
+
+void mg_sha256_init(mg_sha256_ctx *ctx) {
+  ctx->len = 0;
+  ctx->bits = 0;
+  ctx->state[0] = 0x6a09e667;
+  ctx->state[1] = 0xbb67ae85;
+  ctx->state[2] = 0x3c6ef372;
+  ctx->state[3] = 0xa54ff53a;
+  ctx->state[4] = 0x510e527f;
+  ctx->state[5] = 0x9b05688c;
+  ctx->state[6] = 0x1f83d9ab;
+  ctx->state[7] = 0x5be0cd19;
+}
+
+static void mg_sha256_chunk(mg_sha256_ctx *ctx) {
+  int i, j;
+  uint32_t a, b, c, d, e, f, g, h;
+  uint32_t m[64];
+  for (i = 0, j = 0; i < 16; ++i, j += 4)
+    m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) |
+                       ((uint32_t) ctx->buffer[j + 1] << 16) |
+                       ((uint32_t) ctx->buffer[j + 2] << 8) |
+                       ((uint32_t) ctx->buffer[j + 3]));
+  for (; i < 64; ++i)
+    m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16];
+
+  a = ctx->state[0];
+  b = ctx->state[1];
+  c = ctx->state[2];
+  d = ctx->state[3];
+  e = ctx->state[4];
+  f = ctx->state[5];
+  g = ctx->state[6];
+  h = ctx->state[7];
+
+  for (i = 0; i < 64; ++i) {
+    uint32_t t1 = h + ep1(e) + ch(e, f, g) + mg_sha256_k[i] + m[i];
+    uint32_t t2 = ep0(a) + maj(a, b, c);
+    h = g;
+    g = f;
+    f = e;
+    e = d + t1;
+    d = c;
+    c = b;
+    b = a;
+    a = t1 + t2;
+  }
+
+  ctx->state[0] += a;
+  ctx->state[1] += b;
+  ctx->state[2] += c;
+  ctx->state[3] += d;
+  ctx->state[4] += e;
+  ctx->state[5] += f;
+  ctx->state[6] += g;
+  ctx->state[7] += h;
+}
+
+void mg_sha256_update(mg_sha256_ctx *ctx, const unsigned char *data,
+                      size_t len) {
+  size_t i;
+  for (i = 0; i < len; i++) {
+    ctx->buffer[ctx->len] = data[i];
+    if ((++ctx->len) == 64) {
+      mg_sha256_chunk(ctx);
+      ctx->bits += 512;
+      ctx->len = 0;
+    }
+  }
+}
+
+// TODO: make final reusable (remove side effects)
+void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *ctx) {
+  uint32_t i = ctx->len;
+  if (i < 56) {
+    ctx->buffer[i++] = 0x80;
+    while (i < 56) {
+      ctx->buffer[i++] = 0x00;
+    }
+  } else {
+    ctx->buffer[i++] = 0x80;
+    while (i < 64) {
+      ctx->buffer[i++] = 0x00;
+    }
+    mg_sha256_chunk(ctx);
+    memset(ctx->buffer, 0, 56);
+  }
+
+  ctx->bits += ctx->len * 8;
+  ctx->buffer[63] = (uint8_t) ((ctx->bits) & 0xff);
+  ctx->buffer[62] = (uint8_t) ((ctx->bits >> 8) & 0xff);
+  ctx->buffer[61] = (uint8_t) ((ctx->bits >> 16) & 0xff);
+  ctx->buffer[60] = (uint8_t) ((ctx->bits >> 24) & 0xff);
+  ctx->buffer[59] = (uint8_t) ((ctx->bits >> 32) & 0xff);
+  ctx->buffer[58] = (uint8_t) ((ctx->bits >> 40) & 0xff);
+  ctx->buffer[57] = (uint8_t) ((ctx->bits >> 48) & 0xff);
+  ctx->buffer[56] = (uint8_t) ((ctx->bits >> 56) & 0xff);
+  mg_sha256_chunk(ctx);
+
+  for (i = 0; i < 4; ++i) {
+    digest[i] = (uint8_t) ((ctx->state[0] >> (24 - i * 8)) & 0xff);
+    digest[i + 4] = (uint8_t) ((ctx->state[1] >> (24 - i * 8)) & 0xff);
+    digest[i + 8] = (uint8_t) ((ctx->state[2] >> (24 - i * 8)) & 0xff);
+    digest[i + 12] = (uint8_t) ((ctx->state[3] >> (24 - i * 8)) & 0xff);
+    digest[i + 16] = (uint8_t) ((ctx->state[4] >> (24 - i * 8)) & 0xff);
+    digest[i + 20] = (uint8_t) ((ctx->state[5] >> (24 - i * 8)) & 0xff);
+    digest[i + 24] = (uint8_t) ((ctx->state[6] >> (24 - i * 8)) & 0xff);
+    digest[i + 28] = (uint8_t) ((ctx->state[7] >> (24 - i * 8)) & 0xff);
+  }
+}
+
+void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data,
+                    size_t datasz) {
+  mg_sha256_ctx ctx;
+  uint8_t k[64] = {0};
+  uint8_t o_pad[64], i_pad[64];
+  unsigned int i;
+  memset(i_pad, 0x36, sizeof(i_pad));
+  memset(o_pad, 0x5c, sizeof(o_pad));
+  if (keysz < 64) {
+    if (keysz > 0) memmove(k, key, keysz);
+  } else {
+    mg_sha256_init(&ctx);
+    mg_sha256_update(&ctx, key, keysz);
+    mg_sha256_final(k, &ctx);
+  }
+  for (i = 0; i < sizeof(k); i++) {
+    i_pad[i] ^= k[i];
+    o_pad[i] ^= k[i];
+  }
+  mg_sha256_init(&ctx);
+  mg_sha256_update(&ctx, i_pad, sizeof(i_pad));
+  mg_sha256_update(&ctx, data, datasz);
+  mg_sha256_final(dst, &ctx);
+  mg_sha256_init(&ctx);
+  mg_sha256_update(&ctx, o_pad, sizeof(o_pad));
+  mg_sha256_update(&ctx, dst, 32);
+  mg_sha256_final(dst, &ctx);
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/sntp.c"
+#endif
+
+
+
+
+
+
+#define SNTP_TIME_OFFSET 2208988800U  // (1970 - 1900) in seconds
+#define SNTP_MAX_FRAC 4294967295.0    // 2 ** 32 - 1
+
+static int64_t gettimestamp(const uint32_t *data) {
+  uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
+  if (sec) sec -= SNTP_TIME_OFFSET;
+  return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0);
+}
+
+int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
+  int64_t res = -1;
+  int mode = len > 0 ? buf[0] & 7 : 0;
+  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
+  if (len < 48) {
+    MG_ERROR(("%s", "corrupt packet"));
+  } else if (mode != 4 && mode != 5) {
+    MG_ERROR(("%s", "not a server reply"));
+  } else if (buf[1] == 0) {
+    MG_ERROR(("%s", "server sent a kiss of death"));
+  } else if (version == 4 || version == 3) {
+    // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
+    int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
+    int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
+    int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
+    int64_t t3 = (int64_t) mg_millis();
+    int64_t delta = (t3 - t0) - (t2 - t1);
+    MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
+    res = t2 + delta / 2;
+  } else {
+    MG_ERROR(("unexpected version: %d", version));
+  }
+  return res;
+}
+
+static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_READ) {
+    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
+    if (milliseconds > 0) {
+      MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds));
+      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
+      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
+                  (unsigned) (milliseconds % 1000)));
+    }
+    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
+  } else if (ev == MG_EV_CONNECT) {
+    mg_sntp_request(c);
+  } else if (ev == MG_EV_CLOSE) {
+  }
+  (void) ev_data;
+}
+
+void mg_sntp_request(struct mg_connection *c) {
+  if (c->is_resolving) {
+    MG_ERROR(("%lu wait until resolved", c->id));
+  } else {
+    int64_t now = (int64_t) mg_millis();  // Use int64_t, for vc98
+    uint8_t buf[48] = {0};
+    uint32_t *t = (uint32_t *) &buf[40];
+    double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
+    buf[0] = (0 << 6) | (4 << 3) | 3;
+    t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
+    t[1] = mg_htonl((uint32_t) frac);
+    mg_send(c, buf, sizeof(buf));
+  }
+}
+
+struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
+                                      mg_event_handler_t fn, void *fnd) {
+  struct mg_connection *c = NULL;
+  if (url == NULL) url = "udp://time.google.com:123";
+  if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
+  return c;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/sock.c"
+#endif
+
+
+
+
+
+
+
+
+
+
+
+#if MG_ENABLE_SOCKET
+
+#ifndef closesocket
+#define closesocket(x) close(x)
+#endif
+
+#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
+#define S2PTR(s_) ((void *) (size_t) (s_))
+
+#ifndef MSG_NONBLOCKING
+#define MSG_NONBLOCKING 0
+#endif
+
+#ifndef AF_INET6
+#define AF_INET6 10
+#endif
+
+#ifndef MG_SOCK_ERR
+#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
+#endif
+
+#ifndef MG_SOCK_INTR
+#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == EINTR)
+#endif
+
+#ifndef MG_SOCK_PENDING
+#define MG_SOCK_PENDING(errcode) \
+  (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK))
+#endif
+
+#ifndef MG_SOCK_RESET
+#define MG_SOCK_RESET(errcode) \
+  (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
+#endif
+
+union usa {
+  struct sockaddr sa;
+  struct sockaddr_in sin;
+#if MG_ENABLE_IPV6
+  struct sockaddr_in6 sin6;
+#endif
+};
+
+static socklen_t tousa(struct mg_addr *a, union usa *usa) {
+  socklen_t len = sizeof(usa->sin);
+  memset(usa, 0, sizeof(*usa));
+  usa->sin.sin_family = AF_INET;
+  usa->sin.sin_port = a->port;
+  memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (a->is_ip6) {
+    usa->sin.sin_family = AF_INET6;
+    usa->sin6.sin6_port = a->port;
+    usa->sin6.sin6_scope_id = a->scope_id;
+    memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
+    len = sizeof(usa->sin6);
+  }
+#endif
+  return len;
+}
+
+static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
+  a->is_ip6 = is_ip6;
+  a->port = usa->sin.sin_port;
+  memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (is_ip6) {
+    memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
+    a->port = usa->sin6.sin6_port;
+    a->scope_id = (uint8_t) usa->sin6.sin6_scope_id;
+  }
+#endif
+}
+
+static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
+  union usa usa;
+  socklen_t n = sizeof(usa);
+  if (getsockname(fd, &usa.sa, &n) == 0) {
+    tomgaddr(&usa, addr, n != sizeof(usa.sin));
+  }
+}
+
+static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
+  if (n == MG_IO_WAIT) {
+    // Do nothing
+  } else if (n <= 0) {
+    c->is_closing = 1;  // Termination. Don't call mg_error(): #1529
+  } else if (n > 0) {
+    if (c->is_hexdumping) {
+      MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc,
+               r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
+      mg_hexdump(buf, (size_t) n);
+    }
+    if (r) {
+      c->recv.len += (size_t) n;
+      mg_call(c, MG_EV_READ, &n);
+    } else {
+      mg_iobuf_del(&c->send, 0, (size_t) n);
+      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
+      if (c->send.len == 0) {
+        MG_EPOLL_MOD(c, 0);
+      }
+      mg_call(c, MG_EV_WRITE, &n);
     }
   }
-  return flags;
 }
 
-static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
-                       const struct mg_http_serve_opts *opts, char *path,
-                       size_t path_size) {
-  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0};
-  while (mg_commalist(&s, &k, &v)) {
-    if (v.len == 0) v = k, k = mg_str("/");
-    if (hm->uri.len < k.len) continue;
-    if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
-    u = k, p = v;
+long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
+  long n;
+  if (c->is_udp) {
+    union usa usa;
+    socklen_t slen = tousa(&c->rem, &usa);
+    n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
+    if (n > 0) setlocaddr(FD(c), &c->loc);
+  } else {
+    n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
   }
-  return uri_to_path2(c, hm, fs, u, p, path, path_size);
+  MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
+  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
+  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
+  if (n <= 0) return MG_IO_ERR;
+  return n;
 }
 
-void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
-                       const struct mg_http_serve_opts *opts) {
-  char path[MG_PATH_MAX];
-  const char *sp = opts->ssi_pattern;
-  int flags = uri_to_path(c, hm, opts, path, sizeof(path));
-  if (flags < 0) {
-    // Do nothing: the response has already been sent by uri_to_path()
-  } else if (flags & MG_FS_DIR) {
-    listdir(c, hm, opts, path);
-  } else if (flags && sp != NULL &&
-             mg_globmatch(sp, strlen(sp), path, strlen(path))) {
-    mg_http_serve_ssi(c, opts->root_dir, path);
+bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
+  if (c->is_udp) {
+    long n = mg_io_send(c, buf, len);
+    MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
+              c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
+    iolog(c, (char *) buf, n, false);
+    return n > 0;
   } else {
-    mg_http_serve_file(c, hm, path, opts);
+    return mg_iobuf_add(&c->send, c->send.len, buf, len);
   }
 }
 
-static bool mg_is_url_safe(int c) {
-  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
-         (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
+static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
+#if defined(MG_CUSTOM_NONBLOCK)
+  MG_CUSTOM_NONBLOCK(fd);
+#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+  unsigned long on = 1;
+  ioctlsocket(fd, FIONBIO, &on);
+#elif MG_ENABLE_RL
+  unsigned long on = 1;
+  ioctlsocket(fd, FIONBIO, &on);
+#elif MG_ENABLE_FREERTOS_TCP
+  const BaseType_t off = 0;
+  if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0;
+  if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0;
+#elif MG_ENABLE_LWIP
+  lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
+#elif MG_ARCH == MG_ARCH_AZURERTOS
+  fcntl(fd, F_SETFL, O_NONBLOCK);
+#elif MG_ARCH == MG_ARCH_TIRTOS
+  int val = 0;
+  setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
+  // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
+  int sz = sizeof(val);
+  getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
+  val /= 2;  // set send low-water mark at half send buffer size
+  setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
+#else
+  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);  // Non-blocking mode
+  fcntl(fd, F_SETFD, FD_CLOEXEC);                          // Set close-on-exec
+#endif
 }
 
-size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
-  size_t i, n = 0;
-  for (i = 0; i < sl; i++) {
-    int c = *(unsigned char *) &s[i];
-    if (n + 4 >= len) return 0;
-    if (mg_is_url_safe(c)) {
-      buf[n++] = s[i];
+bool mg_open_listener(struct mg_connection *c, const char *url) {
+  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
+  bool success = false;
+  c->loc.port = mg_htons(mg_url_port(url));
+  if (!mg_aton(mg_url_host(url), &c->loc)) {
+    MG_ERROR(("invalid listening URL: %s", url));
+  } else {
+    union usa usa;
+    socklen_t slen = tousa(&c->loc, &usa);
+    int rc, on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
+    int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
+    int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
+    (void) on;
+
+    if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
+      MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
+#if defined(SO_EXCLUSIVEADDRUSE)
+    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+                                (char *) &on, sizeof(on))) != 0) {
+      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
+      MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc)));
+#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
+    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
+                                sizeof(on))) != 0) {
+      // 1. SO_REUSEADDR semantics on UNIX and Windows is different.  On
+      // Windows, SO_REUSEADDR allows to bind a socket to a port without error
+      // even if the port is already open by another program. This is not the
+      // behavior SO_REUSEADDR was designed for, and leads to hard-to-track
+      // failure scenarios.
+      //
+      // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining
+      // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but
+      // won't work! (setsockopt will return EINVAL)
+      MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
+#endif
+#if MG_IPV6_V6ONLY
+      // Bind only to the V6 address, not V4 address on this port
+    } else if (c->loc.is_ip6 &&
+               (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
+                                sizeof(on))) != 0) {
+      // See #2089. Allow to bind v4 and v6 sockets on the same port
+      MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
+#endif
+    } else if ((rc = bind(fd, &usa.sa, slen)) != 0) {
+      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
+    } else if ((type == SOCK_STREAM &&
+                (rc = listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE)) != 0)) {
+      // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
+      // In case port was set to 0, get the real port number
+      MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
     } else {
-      buf[n++] = '%';
-      mg_hex(&s[i], 1, &buf[n]);
-      n += 2;
+      setlocaddr(fd, &c->loc);
+      mg_set_non_blocking_mode(fd);
+      c->fd = S2PTR(fd);
+      MG_EPOLL_ADD(c);
+      success = true;
     }
   }
-  if (len > 0 && n < len - 1) buf[n] = '\0';  // Null-terminate the destination
-  if (len > 0) buf[len - 1] = '\0';           // Always.
-  return n;
+  if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
+  return success;
 }
 
-void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
-                   char *pass, size_t passlen) {
-  struct mg_str *v = mg_http_get_header(hm, "Authorization");
-  user[0] = pass[0] = '\0';
-  if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
-    char buf[256];
-    int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
-    const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 0);
-    if (p != NULL) {
-      mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
-      mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
-    }
-  } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) {
-    mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
-  } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
-    struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12));
-    if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
+static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
+  long n = 0;
+  if (c->is_udp) {
+    union usa usa;
+    socklen_t slen = tousa(&c->rem, &usa);
+    n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
+    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
   } else {
-    mg_http_get_var(&hm->query, "access_token", pass, passlen);
+    n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
   }
+  MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n)));
+  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
+  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
+  if (n <= 0) return MG_IO_ERR;
+  return n;
 }
 
-static struct mg_str stripquotes(struct mg_str s) {
-  return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
-             ? mg_str_n(s.ptr + 1, s.len - 2)
-             : s;
+static bool ioalloc(struct mg_connection *c, struct mg_iobuf *io) {
+  bool res = false;
+  if (io->len >= MG_MAX_RECV_SIZE) {
+    mg_error(c, "MG_MAX_RECV_SIZE");
+  } else if (io->size <= io->len &&
+             !mg_iobuf_resize(io, io->size + MG_IO_SIZE)) {
+    mg_error(c, "OOM");
+  } else {
+    res = true;
+  }
+  return res;
 }
 
-struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
-  size_t i;
-  for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
-    if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) {
-      const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
-      int q = p < x && *p == '"' ? 1 : 0;
-      while (p < x &&
-             (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
-        p++;
-      // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len,
-      // v.ptr, (int) (p - b), b));
-      return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
+// NOTE(lsm): do only one iteration of reads, cause some systems
+// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
+static void read_conn(struct mg_connection *c) {
+  if (ioalloc(c, &c->recv)) {
+    char *buf = (char *) &c->recv.buf[c->recv.len];
+    size_t len = c->recv.size - c->recv.len;
+    long n = -1;
+    if (c->is_tls) {
+      if (!ioalloc(c, &c->rtls)) return;
+      n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len],
+                   c->rtls.size - c->rtls.len);
+      if (n == MG_IO_ERR && c->rtls.len == 0) {
+        // Close only if we have fully drained both raw (rtls) and TLS buffers
+        c->is_closing = 1;
+      } else {
+        if (n > 0) c->rtls.len += (size_t) n;
+        if (c->is_tls_hs) mg_tls_handshake(c);
+        n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len);
+      }
+    } else {
+      n = recv_raw(c, buf, len);
     }
+    MG_DEBUG(("%lu %ld %lu:%lu:%lu %ld err %d", c->id, c->fd, c->send.len,
+              c->recv.len, c->rtls.len, n, MG_SOCK_ERR(n)));
+    iolog(c, buf, n, true);
   }
-  return mg_str_n(NULL, 0);
 }
 
-bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) {
-  return mg_match(hm->uri, mg_str(glob), NULL);
+static void write_conn(struct mg_connection *c) {
+  char *buf = (char *) c->send.buf;
+  size_t len = c->send.len;
+  long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len);
+  MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
+            (long) c->send.len, (long) c->send.size, (long) c->recv.len,
+            (long) c->recv.size, n, MG_SOCK_ERR(n)));
+  iolog(c, buf, n, false);
 }
 
-long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
-                    struct mg_fs *fs, const char *path, size_t max_size) {
-  char buf[20] = "0";
-  long res = 0, offset;
-  mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
-  offset = strtol(buf, NULL, 0);
-  if (hm->body.len == 0) {
-    mg_http_reply(c, 200, "", "%ld", res);  // Nothing to write
+static void close_conn(struct mg_connection *c) {
+  if (FD(c) != MG_INVALID_SOCKET) {
+#if MG_ENABLE_EPOLL
+    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
+#endif
+    closesocket(FD(c));
+#if MG_ENABLE_FREERTOS_TCP
+    FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
+#endif
+  }
+  mg_close_conn(c);
+}
+
+static void connect_conn(struct mg_connection *c) {
+  union usa usa;
+  socklen_t n = sizeof(usa);
+  // Use getpeername() to test whether we have connected
+  if (getpeername(FD(c), &usa.sa, &n) == 0) {
+    c->is_connecting = 0;
+    setlocaddr(FD(c), &c->loc);
+    mg_call(c, MG_EV_CONNECT, NULL);
+    MG_EPOLL_MOD(c, 0);
+    if (c->is_tls_hs) mg_tls_handshake(c);
   } else {
-    struct mg_fd *fd;
-    size_t current_size = 0;
-    MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset));
-    if (offset == 0) fs->rm(path);  // If offset if 0, truncate file
-    fs->st(path, &current_size, NULL);
-    if (offset < 0) {
-      mg_http_reply(c, 400, "", "offset required");
-      res = -1;
-    } else if (offset > 0 && current_size != (size_t) offset) {
-      mg_http_reply(c, 400, "", "%s: offset mismatch", path);
-      res = -2;
-    } else if ((size_t) offset + hm->body.len > max_size) {
-      mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
-                    (unsigned long) max_size);
-      res = -3;
-    } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
-      mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
-      res = -4;
+    mg_error(c, "socket error");
+  }
+}
+
+static void setsockopts(struct mg_connection *c) {
+#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
+    MG_ARCH == MG_ARCH_TIRTOS
+  (void) c;
+#else
+  int on = 1;
+#if !defined(SOL_TCP)
+#define SOL_TCP IPPROTO_TCP
+#endif
+  if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0)
+    (void) 0;
+  if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) !=
+      0)
+    (void) 0;
+#endif
+}
+
+void mg_connect_resolved(struct mg_connection *c) {
+  int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
+  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;  // c->rem has resolved IP
+  c->fd = S2PTR(socket(af, type, 0));               // Create outbound socket
+  c->is_resolving = 0;                              // Clear resolving flag
+  if (FD(c) == MG_INVALID_SOCKET) {
+    mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
+  } else if (c->is_udp) {
+    MG_EPOLL_ADD(c);
+#if MG_ARCH == MG_ARCH_TIRTOS
+    union usa usa;  // TI-RTOS NDK requires binding to receive on UDP sockets
+    socklen_t slen = tousa(&c->loc, &usa);
+    if ((rc = bind(c->fd, &usa.sa, slen)) != 0)
+      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
+#endif
+    setlocaddr(FD(c), &c->loc);
+    mg_call(c, MG_EV_RESOLVE, NULL);
+    mg_call(c, MG_EV_CONNECT, NULL);
+  } else {
+    union usa usa;
+    socklen_t slen = tousa(&c->rem, &usa);
+    mg_set_non_blocking_mode(FD(c));
+    setsockopts(c);
+    MG_EPOLL_ADD(c);
+    mg_call(c, MG_EV_RESOLVE, NULL);
+    rc = connect(FD(c), &usa.sa, slen);  // Attempt to connect
+    if (rc == 0) {                       // Success
+      setlocaddr(FD(c), &c->loc);
+      mg_call(c, MG_EV_CONNECT, NULL);  // Send MG_EV_CONNECT to the user
+    } else if (MG_SOCK_PENDING(rc)) {   // Need to wait for TCP handshake
+      MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem));
+      c->is_connecting = 1;
     } else {
-      res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
-      mg_fs_close(fd);
-      mg_http_reply(c, 200, "", "%ld", res);
+      mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
     }
   }
-  return res;
-}
-
-int mg_http_status(const struct mg_http_message *hm) {
-  return atoi(hm->uri.ptr);
-}
-
-// If a server sends data to the client using chunked encoding, Mongoose strips
-// off the chunking prefix (hex length and \r\n) and suffix (\r\n), appends the
-// stripped data to the body, and fires the MG_EV_HTTP_CHUNK event.  When zero
-// chunk is received, we fire MG_EV_HTTP_MSG, and the body already has all
-// chunking prefixes/suffixes stripped.
-//
-// If a server sends data without chunked encoding, we also fire a series of
-// MG_EV_HTTP_CHUNK events for every received piece of data, and then we fire
-// MG_EV_HTTP_MSG event in the end.
-//
-// We track total processed length in the c->pfn_data, which is a void *
-// pointer: we store a size_t value there.
-static bool getchunk(struct mg_str s, size_t *prefixlen, size_t *datalen) {
-  size_t i = 0, n;
-  while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
-  n = mg_unhexn(s.ptr, i);
-  // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
-  if (s.len < i + n + 4) return false;  // Chunk not yet fully buffered
-  if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
-  if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n') return false;
-  *prefixlen = i + 2;
-  *datalen = n;
-  return true;
 }
 
-static bool mg_is_chunked(struct mg_http_message *hm) {
-  const char *needle = "chunked";
-  struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
-  return te != NULL && mg_vcasecmp(te, needle) == 0;
-}
-
-void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
-  size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
-  mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
-  c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
-}
-
-static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
-                                   struct mg_http_message *hm, bool *next) {
-  //  |  ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
-  //  +------------------+--------------------------+----
-  //  |      hlen        |           chunk1         | ......
-  char *buf = (char *) &c->recv.buf[hlen], *p = buf;
-  size_t len = c->recv.len - hlen;
-  size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
-  size_t mark, pl, dl, del = 0, ofs = 0;
-  bool last = false;
-  if (processed <= len) len -= processed, buf += processed;
-  while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
-    size_t saved = c->recv.len;
-    memmove(p + processed, buf + ofs + pl, dl);
-    // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
-    hm->chunk = mg_str_n(p + processed, dl);
-    mg_call(c, MG_EV_HTTP_CHUNK, hm);
-    ofs += pl + dl + 2, del += pl + 2;  // 2 is for \r\n suffix
-    processed += dl;
-    if (c->recv.len != saved) processed -= dl, buf -= dl;
-    // mg_hexdump(c->recv.buf, hlen + processed);
-    last = (dl == 0);
-  }
-  mg_iobuf_del(&c->recv, hlen + processed, del);
-  mark = ((size_t) c->pfn_data) & MG_DMARK;
-  c->pfn_data = (void *) (processed | mark);
-  if (last) {
-    hm->body.len = processed;
-    hm->message.len = hlen + processed;
-    c->pfn_data = NULL;
-    if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
-    // MG_INFO(("LAST, mark: %lx", mark));
-    // mg_hexdump(c->recv.buf, c->recv.len);
-  }
+static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
+                              socklen_t *len) {
+  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
+  do {
+    memset(usa, 0, sizeof(*usa));
+    fd = accept(sock, &usa->sa, len);
+  } while (MG_SOCK_INTR(fd));
+  return fd;
 }
 
-static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
-                                  struct mg_http_message *hm, bool *next) {
-  size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
-  size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
-  hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
-  if (processed <= hm->chunk.len && !deleted) {
-    hm->chunk.len -= processed;
-    hm->chunk.ptr += processed;
-  }
-  left = hm->body.len < processed ? 0 : hm->body.len - processed;
-  if (hm->chunk.len > left) hm->chunk.len = left;
-  if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
-  processed += hm->chunk.len;
-  deleted = ((size_t) c->pfn_data) & MG_DMARK;  // Re-evaluate after user call
-  if (processed >= hm->body.len) {              // Last, 0-len chunk
-    hm->chunk.len = 0;                          // Reset length
-    mg_call(c, MG_EV_HTTP_CHUNK, hm);           // Call user handler
-    c->pfn_data = NULL;                         // Reset processed counter
-    if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
+static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
+  struct mg_connection *c = NULL;
+  union usa usa;
+  socklen_t sa_len = sizeof(usa);
+  MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
+  if (fd == MG_INVALID_SOCKET) {
+#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS)
+    // AzureRTOS, in non-block socket mode can mark listening socket readable
+    // even it is not. See comment for 'select' func implementation in
+    // nx_bsd.c That's not an error, just should try later
+    if (errno != EAGAIN)
+#endif
+      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1)));
+#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
+    (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
+  } else if ((long) fd >= FD_SETSIZE) {
+    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
+    closesocket(fd);
+#endif
+  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("%lu OOM", lsn->id));
+    closesocket(fd);
   } else {
-    c->pfn_data = (void *) (processed | deleted);  // if it is set
+    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->fd = S2PTR(fd);
+    MG_EPOLL_ADD(c);
+    mg_set_non_blocking_mode(FD(c));
+    setsockopts(c);
+    c->is_accepted = 1;
+    c->is_hexdumping = lsn->is_hexdumping;
+    c->loc = lsn->loc;
+    c->pfn = lsn->pfn;
+    c->pfn_data = lsn->pfn_data;
+    c->fn = lsn->fn;
+    c->fn_data = lsn->fn_data;
+    MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port,
+              &c->rem, mg_print_ip_port, &c->loc));
+    mg_call(c, MG_EV_OPEN, NULL);
+    mg_call(c, MG_EV_ACCEPT, NULL);
   }
 }
 
-static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
-  if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
-    struct mg_http_message hm;
-    // mg_hexdump(c->recv.buf, c->recv.len);
-    while (c->recv.buf != NULL && c->recv.len > 0) {
-      bool next = false;
-      int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
-      if (hlen < 0) {
-        mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
-        break;
-      }
-      if (c->is_resp) break;           // Response is still generated
-      if (hlen == 0) break;            // Request is not buffered yet
-      if (ev == MG_EV_CLOSE) {         // If client did not set Content-Length
-        hm.message.len = c->recv.len;  // and closes now, deliver a MSG
-        hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
-      }
-      if (mg_is_chunked(&hm)) {
-        deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
-      } else {
-        deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
-      }
-      if (next) continue;  // Chunks & request were deleted
-      //  Chunk events are delivered. If we have full body, deliver MSG
-      if (c->recv.len < hm.message.len) break;
-      if (c->is_accepted) c->is_resp = 1;  // Start generating response
-      mg_call(c, MG_EV_HTTP_MSG, &hm);     // User handler can clear is_resp
-      mg_iobuf_del(&c->recv, 0, hm.message.len);
-    }
-  }
-  (void) evd, (void) fnd;
+static bool can_read(const struct mg_connection *c) {
+  return c->is_full == false;
 }
 
-struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url,
-                                      mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = http_cb;
-  return c;
+static bool can_write(const struct mg_connection *c) {
+  return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
 }
 
-struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
-                                     mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = http_cb;
-  return c;
+static bool skip_iotest(const struct mg_connection *c) {
+  return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) ||
+         (can_read(c) == false && can_write(c) == false);
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/iobuf.c"
-#endif
-
-
-
-
-// Not using memset for zeroing memory, cause it can be dropped by compiler
-// See https://github.com/cesanta/mongoose/pull/1265
-static void zeromem(volatile unsigned char *buf, size_t len) {
-  if (buf != NULL) {
-    while (len--) *buf++ = 0;
+static void mg_iotest(struct mg_mgr *mgr, int ms) {
+#if MG_ENABLE_FREERTOS_TCP
+  struct mg_connection *c;
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) continue;
+    if (can_read(c))
+      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
+    if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
+    if (c->is_closing) ms = 1;
   }
-}
-
-static size_t roundup(size_t size, size_t align) {
-  return align == 0 ? size : (size + align - 1) / align * align;
-}
-
-int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
-  int ok = 1;
-  new_size = roundup(new_size, io->align);
-  if (new_size == 0) {
-    zeromem(io->buf, io->size);
-    free(io->buf);
-    io->buf = NULL;
-    io->len = io->size = 0;
-  } else if (new_size != io->size) {
-    // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
-    // porting to some obscure platforms like FreeRTOS
-    void *p = calloc(1, new_size);
-    if (p != NULL) {
-      size_t len = new_size < io->len ? new_size : io->len;
-      if (len > 0 && io->buf != NULL) memmove(p, io->buf, len);
-      zeromem(io->buf, io->size);
-      free(io->buf);
-      io->buf = (unsigned char *) p;
-      io->size = new_size;
+  FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
+    c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
+    c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
+    if (c->fd != MG_INVALID_SOCKET)
+      FreeRTOS_FD_CLR(c->fd, mgr->ss,
+                      eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
+  }
+#elif MG_ENABLE_EPOLL
+  size_t max = 1;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
+    c->is_readable = c->is_writable = 0;
+    if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
+    if (can_write(c)) MG_EPOLL_MOD(c, 1);
+    if (c->is_closing) ms = 1;
+    max++;
+  }
+  struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));
+  int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
+  for (int i = 0; i < n; i++) {
+    struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
+    if (evs[i].events & EPOLLERR) {
+      mg_error(c, "socket error");
+    } else if (c->is_readable == 0) {
+      bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
+      bool wr = evs[i].events & EPOLLOUT;
+      c->is_readable = can_read(c) && rd ? 1U : 0;
+      c->is_writable = can_write(c) && wr ? 1U : 0;
+      if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
+    }
+  }
+  (void) skip_iotest;
+#elif MG_ENABLE_POLL
+  nfds_t n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
+  struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
+  memset(fds, 0, n * sizeof(fds[0]));
+  n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) {
+      // Socket not valid, ignore
+    } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
+      ms = 1;  // Don't wait if TLS is ready
     } else {
-      ok = 0;
-      MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
+      fds[n].fd = FD(c);
+      if (can_read(c)) fds[n].events |= POLLIN;
+      if (can_write(c)) fds[n].events |= POLLOUT;
+      if (c->is_closing) ms = 1;
+      n++;
     }
   }
-  return ok;
-}
-
-int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) {
-  io->buf = NULL;
-  io->align = align;
-  io->size = io->len = 0;
-  return mg_iobuf_resize(io, size);
-}
 
-size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
-                    size_t len) {
-  size_t new_size = roundup(io->len + len, io->align);
-  mg_iobuf_resize(io, new_size);      // Attempt to resize
-  if (new_size != io->size) len = 0;  // Resize failure, append nothing
-  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
-  if (buf != NULL) memmove(io->buf + ofs, buf, len);
-  if (ofs > io->len) io->len += ofs - io->len;
-  io->len += len;
-  return len;
-}
+  // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
+  if (poll(fds, n, ms) < 0) {
+#if MG_ARCH == MG_ARCH_WIN32
+    if (n == 0) Sleep(ms);  // On Windows, poll fails if no sockets
+#endif
+    memset(fds, 0, n * sizeof(fds[0]));
+  }
+  n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
+    if (skip_iotest(c)) {
+      // Socket not valid, ignore
+    } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) {
+      c->is_readable = 1;
+    } else {
+      if (fds[n].revents & POLLERR) {
+        mg_error(c, "socket error");
+      } else {
+        c->is_readable =
+            (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
+        c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
+        if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
+      }
+      n++;
+    }
+  }
+#else
+  struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, *tvp;
+  struct mg_connection *c;
+  fd_set rset, wset, eset;
+  MG_SOCKET_TYPE maxfd = 0;
+  int rc;
 
-size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
-  if (ofs > io->len) ofs = io->len;
-  if (ofs + len > io->len) len = io->len - ofs;
-  if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
-  if (io->buf) zeromem(io->buf + io->len - len, len);
-  io->len -= len;
-  return len;
-}
+  FD_ZERO(&rset);
+  FD_ZERO(&wset);
+  FD_ZERO(&eset);
+  tvp = ms < 0 ? NULL : &tv;
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) continue;
+    FD_SET(FD(c), &eset);
+    if (can_read(c)) FD_SET(FD(c), &rset);
+    if (can_write(c)) FD_SET(FD(c), &wset);
+    if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero;
+    if (FD(c) > maxfd) maxfd = FD(c);
+    if (c->is_closing) ms = 1;
+  }
 
-void mg_iobuf_free(struct mg_iobuf *io) {
-  mg_iobuf_resize(io, 0);
-}
+  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) {
+#if MG_ARCH == MG_ARCH_WIN32
+    if (maxfd == 0) Sleep(ms);  // On Windows, select fails if no sockets
+#else
+    MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc)));
+#endif
+    FD_ZERO(&rset);
+    FD_ZERO(&wset);
+    FD_ZERO(&eset);
+  }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/json.c"
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
+      mg_error(c, "socket error");
+    } else {
+      c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
+      c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
+      if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1;
+    }
+  }
 #endif
+}
 
+static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2]) {
+  socklen_t n = sizeof(usa[0].sin);
+  bool success = false;
 
+  sp[0] = sp[1] = MG_INVALID_SOCKET;
+  (void) memset(&usa[0], 0, sizeof(usa[0]));
+  usa[0].sin.sin_family = AF_INET;
+  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U);  // 127.0.0.1
+  usa[1] = usa[0];
 
-
-static const char *escapeseq(int esc) {
-  return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
+  if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
+      (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
+      bind(sp[0], &usa[0].sa, n) == 0 &&          //
+      bind(sp[1], &usa[1].sa, n) == 0 &&          //
+      getsockname(sp[0], &usa[0].sa, &n) == 0 &&  //
+      getsockname(sp[1], &usa[1].sa, &n) == 0 &&  //
+      connect(sp[0], &usa[1].sa, n) == 0 &&       //
+      connect(sp[1], &usa[0].sa, n) == 0) {       //
+    success = true;
+  }
+  if (!success) {
+    if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
+    if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
+    sp[0] = sp[1] = MG_INVALID_SOCKET;
+  }
+  return success;
 }
 
-static char json_esc(int c, int esc) {
-  const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
-  for (p = esc1; *p != '\0'; p++) {
-    if (*p == c) return esc2[p - esc1];
+// mg_wakeup() event handler
+static void wufn(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_READ) {
+    unsigned long *id = (unsigned long *) c->recv.buf;
+    // MG_INFO(("Got data"));
+    // mg_hexdump(c->recv.buf, c->recv.len);
+    if (c->recv.len >= sizeof(*id)) {
+      struct mg_connection *t;
+      for (t = c->mgr->conns; t != NULL; t = t->next) {
+        if (t->id == *id) {
+          struct mg_str data = mg_str_n((char *) c->recv.buf + sizeof(*id),
+                                        c->recv.len - sizeof(*id));
+          mg_call(t, MG_EV_WAKEUP, &data);
+        }
+      }
+    }
+    c->recv.len = 0;  // Consume received data
+  } else if (ev == MG_EV_CLOSE) {
+    closesocket(c->mgr->pipe);         // When we're closing, close the other
+    c->mgr->pipe = MG_INVALID_SOCKET;  // side of the socketpair, too
   }
-  return 0;
+  (void) ev_data;
 }
 
-static int mg_pass_string(const char *s, int len) {
-  int i;
-  for (i = 0; i < len; i++) {
-    if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
-      i++;
-    } else if (s[i] == '\0') {
-      return MG_JSON_INVALID;
-    } else if (s[i] == '"') {
-      return i;
+bool mg_wakeup_init(struct mg_mgr *mgr) {
+  bool ok = false;
+  if (mgr->pipe == MG_INVALID_SOCKET) {
+    union usa usa[2];
+    MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
+    struct mg_connection *c = NULL;
+    if (!mg_socketpair(sp, usa)) {
+      MG_ERROR(("Cannot create socket pair"));
+    } else if ((c = mg_wrapfd(mgr, (int) sp[1], wufn, NULL)) == NULL) {
+      closesocket(sp[0]);
+      closesocket(sp[1]);
+      sp[0] = sp[1] = MG_INVALID_SOCKET;
+    } else {
+      tomgaddr(&usa[0], &c->rem, false);
+      MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
+      mgr->pipe = sp[0];
+      ok = true;
     }
   }
-  return MG_JSON_INVALID;
+  return ok;
 }
 
-static double mg_atod(const char *p, int len, int *numlen) {
-  double d = 0.0;
-  int i = 0, sign = 1;
-
-  // Sign
-  if (i < len && *p == '-') {
-    sign = -1, i++;
-  } else if (i < len && *p == '+') {
-    i++;
+bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
+               size_t len) {
+  if (mgr->pipe != MG_INVALID_SOCKET && conn_id > 0) {
+    char *extended_buf = (char *) alloca(len + sizeof(conn_id));
+    memcpy(extended_buf, &conn_id, sizeof(conn_id));
+    memcpy(extended_buf + sizeof(conn_id), buf, len);
+    send(mgr->pipe, extended_buf, len + sizeof(conn_id), MSG_NONBLOCKING);
+    return true;
   }
+  return false;
+}
 
-  // Decimal
-  for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
-    d *= 10.0;
-    d += p[i] - '0';
-  }
-  d *= sign;
+void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
+  struct mg_connection *c, *tmp;
+  uint64_t now;
 
-  // Fractional
-  if (i < len && p[i] == '.') {
-    double frac = 0.0, base = 0.1;
-    i++;
-    for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
-      frac += base * (p[i] - '0');
-      base /= 10.0;
+  mg_iotest(mgr, ms);
+  now = mg_millis();
+  mg_timer_poll(&mgr->timers, now);
+
+  for (c = mgr->conns; c != NULL; c = tmp) {
+    bool is_resp = c->is_resp;
+    tmp = c->next;
+    mg_call(c, MG_EV_POLL, &now);
+    if (is_resp && !c->is_resp) {
+      long n = 0;
+      mg_call(c, MG_EV_READ, &n);
+    }
+    MG_VERBOSE(("%lu %c%c %c%c%c%c%c %lu %lu", c->id,
+                c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-',
+                c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c',
+                c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r',
+                c->is_closing ? 'C' : 'c', mg_tls_pending(c), c->rtls.len));
+    if (c->is_resolving || c->is_closing) {
+      // Do nothing
+    } else if (c->is_listening && c->is_udp == 0) {
+      if (c->is_readable) accept_conn(mgr, c);
+    } else if (c->is_connecting) {
+      if (c->is_readable || c->is_writable) connect_conn(c);
+      //} else if (c->is_tls_hs) {
+      //  if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
+    } else {
+      if (c->is_readable) read_conn(c);
+      if (c->is_writable) write_conn(c);
     }
-    d += frac * sign;
-  }
 
-  // Exponential
-  if (i < len && (p[i] == 'e' || p[i] == 'E')) {
-    int j, exp = 0, minus = 0;
-    i++;
-    if (i < len && p[i] == '-') minus = 1, i++;
-    if (i < len && p[i] == '+') i++;
-    while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
-      exp = exp * 10 + (p[i++] - '0');
-    if (minus) exp = -exp;
-    for (j = 0; j < exp; j++) d *= 10.0;
-    for (j = 0; j < -exp; j++) d /= 10.0;
+    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
+    if (c->is_closing) close_conn(c);
   }
-
-  if (numlen != NULL) *numlen = i;
-  return d;
 }
+#endif
 
-int mg_json_get(struct mg_str json, const char *path, int *toklen) {
-  const char *s = json.ptr;
-  int len = (int) json.len;
-  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
-  unsigned char nesting[MG_JSON_MAX_DEPTH];
-  int i = 0;             // Current offset in `s`
-  int j = 0;             // Offset in `s` we're looking for (return value)
-  int depth = 0;         // Current depth (nesting level)
-  int ed = 0;            // Expected depth
-  int pos = 1;           // Current position in `path`
-  int ci = -1, ei = -1;  // Current and expected index in array
+#ifdef MG_ENABLE_LINES
+#line 1 "src/ssi.c"
+#endif
 
-  if (toklen) *toklen = 0;
-  if (path[0] != '$') return MG_JSON_INVALID;
 
-#define MG_CHECKRET(x)                                  \
-  do {                                                  \
-    if (depth == ed && path[pos] == '\0' && ci == ei) { \
-      if (toklen) *toklen = i - j + 1;                  \
-      return j;                                         \
-    }                                                   \
-  } while (0)
 
-// In the ascii table, the distance between `[` and `]` is 2.
-// Ditto for `{` and `}`. Hence +2 in the code below.
-#define MG_EOO(x)                                            \
-  do {                                                       \
-    if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND;   \
-    if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
-    depth--;                                                 \
-    MG_CHECKRET(x);                                          \
-  } while (0)
 
-  for (i = 0; i < len; i++) {
-    unsigned char c = ((unsigned char *) s)[i];
-    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
-    switch (expecting) {
-      case S_VALUE:
-        // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
-        if (depth == ed) j = i;
-        if (c == '{') {
-          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
-          if (depth == ed && path[pos] == '.' && ci == ei) {
-            // If we start the object, reset array indices
-            ed++, pos++, ci = ei = -1;
-          }
-          nesting[depth++] = c;
-          expecting = S_KEY;
-          break;
-        } else if (c == '[') {
-          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
-          if (depth == ed && path[pos] == '[' && ei == ci) {
-            ed++, pos++, ci = 0;
-            for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
-              ei *= 10;
-              ei += path[pos] - '0';
-            }
-            if (path[pos] != 0) pos++;
-          }
-          nesting[depth++] = c;
-          break;
-        } else if (c == ']' && depth > 0) {  // Empty array
-          MG_EOO(']');
-        } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
-          i += 3;
-        } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
-          i += 3;
-        } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
-          i += 4;
-        } else if (c == '-' || ((c >= '0' && c <= '9'))) {
-          int numlen = 0;
-          mg_atod(&s[i], len - i, &numlen);
-          i += numlen - 1;
-        } else if (c == '"') {
-          int n = mg_pass_string(&s[i + 1], len - i - 1);
-          if (n < 0) return n;
-          i += n + 1;
-        } else {
-          return MG_JSON_INVALID;
-        }
-        MG_CHECKRET('V');
-        if (depth == ed && ei >= 0) ci++;
-        expecting = S_COMMA_OR_EOO;
-        break;
+#ifndef MG_MAX_SSI_DEPTH
+#define MG_MAX_SSI_DEPTH 5
+#endif
 
-      case S_KEY:
-        if (c == '"') {
-          int n = mg_pass_string(&s[i + 1], len - i - 1);
-          if (n < 0) return n;
-          if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
-          if (depth < ed) return MG_JSON_NOT_FOUND;
-          if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
-          // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n,
-          //  &s[i + 1], n, depth, ed);
-          // NOTE(cpq): in the check sequence below is important.
-          // strncmp() must go first: it fails fast if the remaining length of
-          // the path is smaller than `n`.
-          if (depth == ed && path[pos - 1] == '.' &&
-              strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
-              (path[pos + n] == '\0' || path[pos + n] == '.' ||
-               path[pos + n] == '[')) {
-            pos += n;
-          }
-          i += n + 1;
-          expecting = S_COLON;
-        } else if (c == '}') {  // Empty object
-          MG_EOO('}');
-          expecting = S_COMMA_OR_EOO;
-        } else {
-          return MG_JSON_INVALID;
-        }
-        break;
+#ifndef MG_SSI_BUFSIZ
+#define MG_SSI_BUFSIZ 1024
+#endif
 
-      case S_COLON:
-        if (c == ':') {
-          expecting = S_VALUE;
+#if MG_ENABLE_SSI
+static char *mg_ssi(const char *path, const char *root, int depth) {
+  struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
+  FILE *fp = fopen(path, "rb");
+  if (fp != NULL) {
+    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
+    int ch, intag = 0;
+    size_t len = 0;
+    buf[0] = arg[0] = '\0';
+    while ((ch = fgetc(fp)) != EOF) {
+      if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
+        buf[len++] = (char) (ch & 0xff);
+        buf[len] = '\0';
+        if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
+          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
+              *p = (char *) path + strlen(path), *data;
+          while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
+          mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
+          if (depth < MG_MAX_SSI_DEPTH &&
+              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
+            mg_iobuf_add(&b, b.len, data, strlen(data));
+            free(data);
+          } else {
+            MG_ERROR(("%s: file=%s error or too deep", path, arg));
+          }
+        } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
+          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
+          mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
+          if (depth < MG_MAX_SSI_DEPTH &&
+              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
+            mg_iobuf_add(&b, b.len, data, strlen(data));
+            free(data);
+          } else {
+            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
+          }
         } else {
-          return MG_JSON_INVALID;
+          // Unknown SSI tag
+          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
+          mg_iobuf_add(&b, b.len, buf, len);
         }
-        break;
-
-      case S_COMMA_OR_EOO:
-        if (depth <= 0) {
-          return MG_JSON_INVALID;
-        } else if (c == ',') {
-          expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
-        } else if (c == ']' || c == '}') {
-          MG_EOO('O');
-          if (depth == ed && ei >= 0) ci++;
-        } else {
-          return MG_JSON_INVALID;
+        intag = 0;
+        len = 0;
+      } else if (ch == '<') {
+        intag = 1;
+        if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
+        len = 0;
+        buf[len++] = (char) (ch & 0xff);
+      } else if (intag) {
+        if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
+          intag = 0;
+        } else if (len >= sizeof(buf) - 2) {
+          MG_ERROR(("%s: SSI tag is too large", path));
+          len = 0;
         }
-        break;
+        buf[len++] = (char) (ch & 0xff);
+      } else {
+        buf[len++] = (char) (ch & 0xff);
+        if (len >= sizeof(buf)) {
+          mg_iobuf_add(&b, b.len, buf, len);
+          len = 0;
+        }
+      }
     }
+    if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
+    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1);  // nul-terminate
+    fclose(fp);
   }
-  return MG_JSON_NOT_FOUND;
+  (void) depth;
+  (void) root;
+  return (char *) b.buf;
 }
 
-bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
-  int n, toklen, found = 0;
-  if ((n = mg_json_get(json, path, &toklen)) >= 0 &&
-      (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) {
-    if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
-    found = 1;
-  }
-  return found;
+void mg_http_serve_ssi(struct mg_connection *c, const char *root,
+                       const char *fullpath) {
+  const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
+  char *data = mg_ssi(fullpath, root, 0);
+  mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
+  free(data);
+}
+#else
+void mg_http_serve_ssi(struct mg_connection *c, const char *root,
+                       const char *fullpath) {
+  mg_http_reply(c, 501, NULL, "SSI not enabled");
+  (void) root, (void) fullpath;
 }
+#endif
 
-bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
-  int found = 0, off = mg_json_get(json, path, NULL);
-  if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
-    if (v != NULL) *v = json.ptr[off] == 't';
-    found = 1;
-  }
-  return found;
+#ifdef MG_ENABLE_LINES
+#line 1 "src/str.c"
+#endif
+
+
+struct mg_str mg_str_s(const char *s) {
+  struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)};
+  return str;
 }
 
-static bool json_unescape(const char *s, size_t len, char *to, size_t n) {
-  size_t i, j;
-  for (i = 0, j = 0; i < len && j < n; i++, j++) {
-    if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
-      //  \uXXXX escape. We could process a simple one-byte chars
-      // \u00xx from the ASCII range. More complex chars would require
-      // dragging in a UTF8 library, which is too much for us
-      if (s[i + 2] != '0' || s[i + 3] != '0') return false;  // Give up
-      ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2);
+struct mg_str mg_str_n(const char *s, size_t n) {
+  struct mg_str str = {(char *) s, n};
+  return str;
+}
 
-      i += 5;
-    } else if (s[i] == '\\' && i + 1 < len) {
-      char c = json_esc(s[i + 1], 0);
-      if (c == 0) return false;
-      to[j] = c;
-      i++;
-    } else {
-      to[j] = s[i];
-    }
+static int mg_tolc(char c) {
+  return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c;
+}
+
+int mg_casecmp(const char *s1, const char *s2) {
+  int diff = 0;
+  do {
+    int c = mg_tolc(*s1++), d = mg_tolc(*s2++);
+    diff = c - d;
+  } while (diff == 0 && s1[-1] != '\0');
+  return diff;
+}
+
+int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
+  size_t i = 0;
+  while (i < str1.len && i < str2.len) {
+    int c1 = str1.buf[i];
+    int c2 = str2.buf[i];
+    if (c1 < c2) return -1;
+    if (c1 > c2) return 1;
+    i++;
   }
-  if (j >= n) return false;
-  if (n > 0) to[j] = '\0';
-  return true;
+  if (i < str1.len) return 1;
+  if (i < str2.len) return -1;
+  return 0;
 }
 
-char *mg_json_get_str(struct mg_str json, const char *path) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && len > 1 && json.ptr[off] == '"') {
-    if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
-        !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result,
-                       (size_t) len)) {
-      free(result);
-      result = NULL;
-    }
+int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) {
+  size_t i = 0;
+  while (i < str1.len && i < str2.len) {
+    int c1 = mg_tolc(str1.buf[i]);
+    int c2 = mg_tolc(str2.buf[i]);
+    if (c1 < c2) return -1;
+    if (c1 > c2) return 1;
+    i++;
   }
-  return result;
+  if (i < str1.len) return 1;
+  if (i < str2.len) return -1;
+  return 0;
 }
 
-char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
-      (result = (char *) calloc(1, (size_t) len)) != NULL) {
-    int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
-    if (slen != NULL) *slen = k;
+bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
+  size_t i = 0, j = 0, ni = 0, nj = 0;
+  if (caps) caps->buf = NULL, caps->len = 0;
+  while (i < p.len || j < s.len) {
+    if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] == p.buf[i])) {
+      if (caps == NULL) {
+      } else if (p.buf[i] == '?') {
+        caps->buf = &s.buf[j], caps->len = 1;     // Finalize `?` cap
+        caps++, caps->buf = NULL, caps->len = 0;  // Init next cap
+      } else if (caps->buf != NULL && caps->len == 0) {
+        caps->len = (size_t) (&s.buf[j] - caps->buf);  // Finalize current cap
+        caps++, caps->len = 0, caps->buf = NULL;       // Init next cap
+      }
+      i++, j++;
+    } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) {
+      if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j];  // Init cap
+      ni = i++, nj = j + 1;
+    } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' || s.buf[j] != '/')) {
+      i = ni, j = nj;
+      if (caps && caps->buf == NULL && caps->len == 0) {
+        caps--, caps->len = 0;  // Restart previous cap
+      }
+    } else {
+      return false;
+    }
   }
-  return result;
+  if (caps && caps->buf && caps->len == 0) {
+    caps->len = (size_t) (&s.buf[j] - caps->buf);
+  }
+  return true;
 }
 
-char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
-      (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
-    mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result);
-    result[len / 2 - 1] = '\0';
-    if (slen != NULL) *slen = len / 2 - 1;
+bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char sep) {
+  if (s.len == 0 || s.buf == NULL) {
+    return false;  // Empty string, nothing to span - fail
+  } else {
+    size_t len = 0;
+    while (len < s.len && s.buf[len] != sep) len++;  // Find separator
+    if (a) *a = mg_str_n(s.buf, len);                // Init a
+    if (b) *b = mg_str_n(s.buf + len, s.len - len);  // Init b
+    if (b && len < s.len) b->buf++, b->len--;        // Skip separator
+    return true;
   }
-  return result;
 }
 
-long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
-  double dv;
-  long result = dflt;
-  if (mg_json_get_num(json, path, &dv)) result = (long) dv;
-  return result;
+bool mg_str_to_num(struct mg_str str, int base, void *val, size_t val_len) {
+  size_t i = 0, ndigits = 0;
+  uint64_t max = val_len == sizeof(uint8_t)   ? 0xFF
+                 : val_len == sizeof(uint16_t) ? 0xFFFF
+                 : val_len == sizeof(uint32_t) ? 0xFFFFFFFF
+                                : (uint64_t) ~0;
+  uint64_t result = 0;
+  if (max == (uint64_t) ~0 && val_len != sizeof(uint64_t)) return false;
+  if (base == 0 && str.len >= 2) {
+    if (str.buf[i] == '0') {
+      i++;
+      base = str.buf[i] == 'b' ? 2 : str.buf[i] == 'x' ? 16 : 10;
+      if (base != 10) ++i;
+    } else {
+      base = 10;
+    }
+  }
+  switch (base) {
+    case 2:
+      while (i < str.len && (str.buf[i] == '0' || str.buf[i] == '1')) {
+        uint64_t digit = (uint64_t) (str.buf[i] - '0');
+        if (result > max/2) return false;  // Overflow
+        result *= 2;
+        if (result > max - digit) return false;  // Overflow
+        result += digit;
+        i++, ndigits++;
+      }
+      break;
+    case 10:
+      while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') {
+        uint64_t digit = (uint64_t) (str.buf[i] - '0');
+        if (result > max/10) return false;  // Overflow
+        result *= 10;
+        if (result > max - digit) return false;  // Overflow
+        result += digit;
+        i++, ndigits++;
+    }
+      break;
+    case 16:
+      while (i < str.len) {
+        char c = str.buf[i];
+        uint64_t digit = (c >= '0' && c <= '9')   ? (uint64_t) (c - '0')
+                         : (c >= 'A' && c <= 'F') ? (uint64_t) (c - '7')
+                         : (c >= 'a' && c <= 'f') ? (uint64_t) (c - 'W')
+                                                  : (uint64_t) ~0;
+        if (digit == (uint64_t) ~0) break;
+        if (result > max/16) return false;  // Overflow
+        result *= 16;
+        if (result > max - digit) return false;  // Overflow
+        result += digit;
+        i++, ndigits++;
+      }
+      break;
+    default:
+      return false;
+  }
+  if (ndigits == 0) return false;
+  if (i != str.len) return false;
+  if (val_len == 1) {
+    *((uint8_t *) val) = (uint8_t) result;
+  } else if (val_len == 2) {
+    *((uint16_t *) val) = (uint16_t) result;
+  } else if (val_len == 4) {
+    *((uint32_t *) val) = (uint32_t) result;
+  } else {
+    *((uint64_t *) val) = (uint64_t) result;
+  }
+  return true;
 }
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/log.c"
+#line 1 "src/timer.c"
 #endif
 
 
 
+#define MG_TIMER_CALLED 4
 
-
-static void default_logger(char c, void *param) {
-  putchar(c);
-  (void) c, (void) param;
-}
-
-static int s_level = MG_LL_INFO;
-static mg_pfn_t s_log_func = default_logger;
-static void *s_log_func_param = NULL;
-
-void mg_log_set_fn(mg_pfn_t fn, void *param) {
-  s_log_func = fn;
-  s_log_func_param = param;
-}
-
-static void logc(unsigned char c) {
-  s_log_func((char) c, s_log_func_param);
-}
-
-static void logs(const char *buf, size_t len) {
-  size_t i;
-  for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
-}
-
-void mg_log_set(int log_level) {
-  MG_DEBUG(("Setting log level to %d", log_level));
-  s_level = log_level;
-}
-
-bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
-  if (level <= s_level) {
-    const char *p = strrchr(file, '/');
-    char buf[41];
-    size_t n;
-    if (p == NULL) p = strrchr(file, '\\');
-    n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(), level,
-                    p == NULL ? file : p + 1, line, fname);
-    if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
-    while (n < sizeof(buf)) buf[n++] = ' ';
-    logs(buf, n - 1);
-    return true;
-  } else {
-    return false;
-  }
+void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
+                   unsigned flags, void (*fn)(void *), void *arg) {
+  t->id = 0, t->period_ms = ms, t->expire = 0;
+  t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
+  *head = t;
 }
 
-void mg_log(const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
-  va_end(ap);
-  logc((unsigned char) '\n');
+void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
+  while (*head && *head != t) head = &(*head)->next;
+  if (*head) *head = t->next;
 }
 
-static unsigned char nibble(unsigned c) {
-  return (unsigned char) (c < 10 ? c + '0' : c + 'W');
+// t: expiration time, prd: period, now: current time. Return true if expired
+bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
+  if (now + prd < *t) *t = 0;                    // Time wrapped? Reset timer
+  if (*t == 0) *t = now + prd;                   // Firt poll? Set expiration
+  if (*t > now) return false;                    // Not expired yet, return
+  *t = (now - *t) > prd ? now + prd : *t + prd;  // Next expiration time
+  return true;                                   // Expired, return true
 }
 
-#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
-void mg_hexdump(const void *buf, size_t len) {
-  const unsigned char *p = (const unsigned char *) buf;
-  unsigned char ascii[16], alen = 0;
-  size_t i;
-  for (i = 0; i < len; i++) {
-    if ((i % 16) == 0) {
-      // Print buffered ascii chars
-      if (i > 0) logs("  ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0;
-      // Print hex address, then \t
-      logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
-          logc(nibble((i >> 4) & 15)), logc('0'), logs("   ", 3);
-    }
-    logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15));  // Two nibbles, e.g. c5
-    logc(' ');                                         // Space after hex number
-    ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.';        // Add to the ascii buf
+void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
+  struct mg_timer *t, *tmp;
+  for (t = *head; t != NULL; t = tmp) {
+    bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
+                !(t->flags & MG_TIMER_CALLED);  // Handle MG_TIMER_NOW only once
+    bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
+    tmp = t->next;
+    if (!once && !expired) continue;
+    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
+      t->fn(t->arg);
+    }
+    t->flags |= MG_TIMER_CALLED;
   }
-  while (alen < 16) logs("   ", 3), ascii[alen++] = ' ';
-  logs("  ", 2), logs((char *) ascii, 16), logc('\n');
 }
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/md5.c"
+#line 1 "src/tls_aes128.c"
 #endif
+/******************************************************************************
+ *
+ * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+ *
+ * This is a simple and straightforward implementation of the AES Rijndael
+ * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
+ * of this work was correctness & accuracy.  It is written in 'C' without any
+ * particular focus upon optimization or speed. It should be endian (memory
+ * byte order) neutral since the few places that care are handled explicitly.
+ *
+ * This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
+ *
+ * It is intended for general purpose use, but was written in support of GRC's
+ * reference implementation of the SQRL (Secure Quick Reliable Login) client.
+ *
+ * See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
+ *
+ * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+ * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+ *
+ *******************************************************************************/
+
+/******************************************************************************/
+#define AES_DECRYPTION 1  // whether AES decryption is supported
+/******************************************************************************/
+
+#define MG_ENCRYPT 1  // specify whether we're encrypting
+#define MG_DECRYPT 0  // or decrypting
+
+
+
+
+
+#if MG_TLS == MG_TLS_BUILTIN
+/******************************************************************************
+ *  AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use
+ ******************************************************************************/
+static void aes_init_keygen_tables(void);
+
+/******************************************************************************
+ *  AES_SETKEY : called to expand the key for encryption or decryption
+ ******************************************************************************/
+static int aes_setkey(aes_context *ctx,  // pointer to context
+                      int mode,          // 1 or 0 for Encrypt/Decrypt
+                      const uchar *key,  // AES input key
+                      uint keysize);  // size in bytes (must be 16, 24, 32 for
+                                      // 128, 192 or 256-bit keys respectively)
+                                      // returns 0 for success
+
+/******************************************************************************
+ *  AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data
+ ******************************************************************************/
+static int aes_cipher(aes_context *ctx,       // pointer to context
+                      const uchar input[16],  // 128-bit block to en/decipher
+                      uchar output[16]);      // 128-bit output result block
+                                              // returns 0 for success
+
+/******************************************************************************
+ *  GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx
+ ******************************************************************************/
+typedef struct {
+  int mode;             // cipher direction: encrypt/decrypt
+  uint64_t len;         // cipher data length processed so far
+  uint64_t add_len;     // total add data length
+  uint64_t HL[16];      // precalculated lo-half HTable
+  uint64_t HH[16];      // precalculated hi-half HTable
+  uchar base_ectr[16];  // first counter-mode cipher output for tag
+  uchar y[16];          // the current cipher-input IV|Counter value
+  uchar buf[16];        // buf working value
+  aes_context aes_ctx;  // cipher context used
+} gcm_context;
+
+/******************************************************************************
+ *  GCM_SETKEY : sets the GCM (and AES) keying material for use
+ ******************************************************************************/
+static int gcm_setkey(
+    gcm_context *ctx,   // caller-provided context ptr
+    const uchar *key,   // pointer to cipher key
+    const uint keysize  // size in bytes (must be 16, 24, 32 for
+                        // 128, 192 or 256-bit keys respectively)
+);                      // returns 0 for success
+
+/******************************************************************************
+ *
+ *  GCM_CRYPT_AND_TAG
+ *
+ *  This either encrypts or decrypts the user-provided data and, either
+ *  way, generates an authentication tag of the requested length. It must be
+ *  called with a GCM context whose key has already been set with GCM_SETKEY.
+ *
+ *  The user would typically call this explicitly to ENCRYPT a buffer of data
+ *  and optional associated data, and produce its an authentication tag.
+ *
+ *  To reverse the process the user would typically call the companion
+ *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
+ *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function
+ *  to perform its decryption and tag generation, which it then compares.
+ *
+ ******************************************************************************/
+static int gcm_crypt_and_tag(
+    gcm_context *ctx,    // gcm context with key already setup
+    int mode,            // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0)
+    const uchar *iv,     // pointer to the 12-byte initialization vector
+    size_t iv_len,       // byte length if the IV. should always be 12
+    const uchar *add,    // pointer to the non-ciphered additional data
+    size_t add_len,      // byte length of the additional AEAD data
+    const uchar *input,  // pointer to the cipher data source
+    uchar *output,       // pointer to the cipher data destination
+    size_t length,       // byte length of the cipher data
+    uchar *tag,          // pointer to the tag to be generated
+    size_t tag_len);     // byte length of the tag to be generated
+
+/******************************************************************************
+ *
+ *  GCM_START
+ *
+ *  Given a user-provided GCM context, this initializes it, sets the encryption
+ *  mode, and preprocesses the initialization vector and additional AEAD data.
+ *
+ ******************************************************************************/
+static int gcm_start(
+    gcm_context *ctx,  // pointer to user-provided GCM context
+    int mode,          // MG_ENCRYPT (1) or MG_DECRYPT (0)
+    const uchar *iv,   // pointer to initialization vector
+    size_t iv_len,     // IV length in bytes (should == 12)
+    const uchar *add,  // pointer to additional AEAD data (NULL if none)
+    size_t add_len);   // length of additional AEAD data (bytes)
+
+/******************************************************************************
+ *
+ *  GCM_UPDATE
+ *
+ *  This is called once or more to process bulk plaintext or ciphertext data.
+ *  We give this some number of bytes of input and it returns the same number
+ *  of output bytes. If called multiple times (which is fine) all but the final
+ *  invocation MUST be called with length mod 16 == 0. (Only the final call can
+ *  have a partial block length of < 128 bits.)
+ *
+ ******************************************************************************/
+static int gcm_update(gcm_context *ctx,  // pointer to user-provided GCM context
+                      size_t length,     // length, in bytes, of data to process
+                      const uchar *input,  // pointer to source data
+                      uchar *output);      // pointer to destination data
+
+/******************************************************************************
+ *
+ *  GCM_FINISH
+ *
+ *  This is called once after all calls to GCM_UPDATE to finalize the GCM.
+ *  It performs the final GHASH to produce the resulting authentication TAG.
+ *
+ ******************************************************************************/
+static int gcm_finish(
+    gcm_context *ctx,  // pointer to user-provided GCM context
+    uchar *tag,        // ptr to tag buffer - NULL if tag_len = 0
+    size_t tag_len);   // length, in bytes, of the tag-receiving buf
+
+/******************************************************************************
+ *
+ *  GCM_ZERO_CTX
+ *
+ *  The GCM context contains both the GCM context and the AES context.
+ *  This includes keying and key-related material which is security-
+ *  sensitive, so it MUST be zeroed after use. This function does that.
+ *
+ ******************************************************************************/
+static void gcm_zero_ctx(gcm_context *ctx);
+
+/******************************************************************************
+ *
+ * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+ *
+ * This is a simple and straightforward implementation of the AES Rijndael
+ * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus
+ * of this work was correctness & accuracy.  It is written in 'C' without any
+ * particular focus upon optimization or speed. It should be endian (memory
+ * byte order) neutral since the few places that care are handled explicitly.
+ *
+ * This implementation of Rijndael was created by Steven M. Gibson of GRC.com.
+ *
+ * It is intended for general purpose use, but was written in support of GRC's
+ * reference implementation of the SQRL (Secure Quick Reliable Login) client.
+ *
+ * See:    http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
+ *
+ * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+ * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+ *
+ *******************************************************************************/
+
+
+
+
+static int aes_tables_inited = 0;  // run-once flag for performing key
+                                   // expasion table generation (see below)
+/*
+ *  The following static local tables must be filled-in before the first use of
+ *  the GCM or AES ciphers. They are used for the AES key expansion/scheduling
+ *  and once built are read-only and thread safe. The "gcm_initialize" function
+ *  must be called once during system initialization to populate these arrays
+ *  for subsequent use by the AES key scheduler. If they have not been built
+ *  before attempted use, an error will be returned to the caller.
+ *
+ *  NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since
+ *  GCM uses AES in counter-mode, where the AES cipher output is XORed with
+ *  the GCM input, we ONLY NEED AES encryption.  Thus, to save space AES
+ *  decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h.
+ */
+// We always need our forward tables
+static uchar FSb[256];     // Forward substitution box (FSb)
+static uint32_t FT0[256];  // Forward key schedule assembly tables
+static uint32_t FT1[256];
+static uint32_t FT2[256];
+static uint32_t FT3[256];
+
+#if AES_DECRYPTION         // We ONLY need reverse for decryption
+static uchar RSb[256];     // Reverse substitution box (RSb)
+static uint32_t RT0[256];  // Reverse key schedule assembly tables
+static uint32_t RT1[256];
+static uint32_t RT2[256];
+static uint32_t RT3[256];
+#endif /* AES_DECRYPTION */
+
+static uint32_t RCON[10];  // AES round constants
 
+/*
+ * Platform Endianness Neutralizing Load and Store Macro definitions
+ * AES wants platform-neutral Little Endian (LE) byte ordering
+ */
+#define GET_UINT32_LE(n, b, i)                                               \
+  {                                                                          \
+    (n) = ((uint32_t) (b)[(i)]) | ((uint32_t) (b)[(i) + 1] << 8) |           \
+          ((uint32_t) (b)[(i) + 2] << 16) | ((uint32_t) (b)[(i) + 3] << 24); \
+  }
 
-
-#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
-
-static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
-  if (MG_BIG_ENDIAN) {
-    do {
-      uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
-                   ((unsigned) buf[1] << 8 | buf[0]);
-      *(uint32_t *) buf = t;
-      buf += 4;
-    } while (--longs);
-  } else {
-    (void) buf, (void) longs;  // Little endian. Do nothing
+#define PUT_UINT32_LE(n, b, i)          \
+  {                                     \
+    (b)[(i)] = (uchar) ((n));           \
+    (b)[(i) + 1] = (uchar) ((n) >> 8);  \
+    (b)[(i) + 2] = (uchar) ((n) >> 16); \
+    (b)[(i) + 3] = (uchar) ((n) >> 24); \
   }
-}
 
-#define F1(x, y, z) (z ^ (x & (y ^ z)))
-#define F2(x, y, z) F1(z, x, y)
-#define F3(x, y, z) (x ^ y ^ z)
-#define F4(x, y, z) (y ^ (x | ~z))
+/*
+ *  AES forward and reverse encryption round processing macros
+ */
+#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3)          \
+  {                                                         \
+    X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \
+         FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF];   \
+                                                            \
+    X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \
+         FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF];   \
+                                                            \
+    X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \
+         FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF];   \
+                                                            \
+    X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \
+         FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF];   \
+  }
 
-#define MD5STEP(f, w, x, y, z, data, s) \
-  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3)          \
+  {                                                         \
+    X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \
+         RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF];   \
+                                                            \
+    X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \
+         RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF];   \
+                                                            \
+    X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \
+         RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF];   \
+                                                            \
+    X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \
+         RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF];   \
+  }
 
 /*
- * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
- * initialization constants.
+ *  These macros improve the readability of the key
+ *  generation initialization code by collapsing
+ *  repetitive common operations into logical pieces.
  */
-void mg_md5_init(mg_md5_ctx *ctx) {
-  ctx->buf[0] = 0x67452301;
-  ctx->buf[1] = 0xefcdab89;
-  ctx->buf[2] = 0x98badcfe;
-  ctx->buf[3] = 0x10325476;
+#define ROTL8(x) ((x << 8) & 0xFFFFFFFF) | (x >> 24)
+#define XTIME(x) ((x << 1) ^ ((x & 0x80) ? 0x1B : 0x00))
+#define MUL(x, y) ((x && y) ? pow[(log[x] + log[y]) % 255] : 0)
+#define MIX(x, y)                     \
+  {                                   \
+    y = ((y << 1) | (y >> 7)) & 0xFF; \
+    x ^= y;                           \
+  }
+#define CPY128     \
+  {                \
+    *RK++ = *SK++; \
+    *RK++ = *SK++; \
+    *RK++ = *SK++; \
+    *RK++ = *SK++; \
+  }
 
-  ctx->bits[0] = 0;
-  ctx->bits[1] = 0;
-}
+/******************************************************************************
+ *
+ *  AES_INIT_KEYGEN_TABLES
+ *
+ *  Fills the AES key expansion tables allocated above with their static
+ *  data. This is not "per key" data, but static system-wide read-only
+ *  table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once
+ *  at system initialization to setup the tables for all subsequent use.
+ *
+ ******************************************************************************/
+void aes_init_keygen_tables(void) {
+  int i, x, y, z;  // general purpose iteration and computation locals
+  int pow[256];
+  int log[256];
+
+  if (aes_tables_inited) return;
+
+  // fill the 'pow' and 'log' tables over GF(2^8)
+  for (i = 0, x = 1; i < 256; i++) {
+    pow[i] = x;
+    log[x] = i;
+    x = (x ^ XTIME(x)) & 0xFF;
+  }
+  // compute the round constants
+  for (i = 0, x = 1; i < 10; i++) {
+    RCON[i] = (uint32_t) x;
+    x = XTIME(x) & 0xFF;
+  }
+  // fill the forward and reverse substitution boxes
+  FSb[0x00] = 0x63;
+#if AES_DECRYPTION  // whether AES decryption is supported
+  RSb[0x63] = 0x00;
+#endif /* AES_DECRYPTION */
+
+  for (i = 1; i < 256; i++) {
+    x = y = pow[255 - log[i]];
+    MIX(x, y);
+    MIX(x, y);
+    MIX(x, y);
+    MIX(x, y);
+    FSb[i] = (uchar) (x ^= 0x63);
+#if AES_DECRYPTION  // whether AES decryption is supported
+    RSb[x] = (uchar) i;
+#endif /* AES_DECRYPTION */
+  }
+  // generate the forward and reverse key expansion tables
+  for (i = 0; i < 256; i++) {
+    x = FSb[i];
+    y = XTIME(x) & 0xFF;
+    z = (y ^ x) & 0xFF;
 
-static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
-  uint32_t a, b, c, d;
+    FT0[i] = ((uint32_t) y) ^ ((uint32_t) x << 8) ^ ((uint32_t) x << 16) ^
+             ((uint32_t) z << 24);
 
-  a = buf[0];
-  b = buf[1];
-  c = buf[2];
-  d = buf[3];
+    FT1[i] = ROTL8(FT0[i]);
+    FT2[i] = ROTL8(FT1[i]);
+    FT3[i] = ROTL8(FT2[i]);
 
-  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
-  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
-  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
-  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
-  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
-  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
-  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
-  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
-  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
-  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
-  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
-  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
-  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
-  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
-  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
-  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+#if AES_DECRYPTION  // whether AES decryption is supported
+    x = RSb[i];
 
-  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
-  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
-  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
-  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
-  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
-  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
-  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
-  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
-  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
-  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
-  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
-  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
-  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
-  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
-  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
-  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+    RT0[i] = ((uint32_t) MUL(0x0E, x)) ^ ((uint32_t) MUL(0x09, x) << 8) ^
+             ((uint32_t) MUL(0x0D, x) << 16) ^ ((uint32_t) MUL(0x0B, x) << 24);
 
-  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
-  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
-  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
-  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
-  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
-  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
-  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
-  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
-  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
-  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
-  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
-  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
-  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
-  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
-  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
-  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+    RT1[i] = ROTL8(RT0[i]);
+    RT2[i] = ROTL8(RT1[i]);
+    RT3[i] = ROTL8(RT2[i]);
+#endif /* AES_DECRYPTION */
+  }
+  aes_tables_inited = 1;  // flag that the tables have been generated
+}  // to permit subsequent use of the AES cipher
+
+/******************************************************************************
+ *
+ *  AES_SET_ENCRYPTION_KEY
+ *
+ *  This is called by 'aes_setkey' when we're establishing a key for
+ *  subsequent encryption.  We give it a pointer to the encryption
+ *  context, a pointer to the key, and the key's length in bytes.
+ *  Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits).
+ *
+ ******************************************************************************/
+static int aes_set_encryption_key(aes_context *ctx, const uchar *key,
+                                  uint keysize) {
+  uint i;                  // general purpose iteration local
+  uint32_t *RK = ctx->rk;  // initialize our RoundKey buffer pointer
+
+  for (i = 0; i < (keysize >> 2); i++) {
+    GET_UINT32_LE(RK[i], key, i << 2);
+  }
 
-  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
-  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
-  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
-  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
-  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
-  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
-  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
-  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
-  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
-  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
-  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
-  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
-  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
-  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
-  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
-  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+  switch (ctx->rounds) {
+    case 10:
+      for (i = 0; i < 10; i++, RK += 4) {
+        RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[3] >> 8) & 0xFF]) ^
+                ((uint32_t) FSb[(RK[3] >> 16) & 0xFF] << 8) ^
+                ((uint32_t) FSb[(RK[3] >> 24) & 0xFF] << 16) ^
+                ((uint32_t) FSb[(RK[3]) & 0xFF] << 24);
+
+        RK[5] = RK[1] ^ RK[4];
+        RK[6] = RK[2] ^ RK[5];
+        RK[7] = RK[3] ^ RK[6];
+      }
+      break;
 
-  buf[0] += a;
-  buf[1] += b;
-  buf[2] += c;
-  buf[3] += d;
-}
+    case 12:
+      for (i = 0; i < 8; i++, RK += 6) {
+        RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[5] >> 8) & 0xFF]) ^
+                ((uint32_t) FSb[(RK[5] >> 16) & 0xFF] << 8) ^
+                ((uint32_t) FSb[(RK[5] >> 24) & 0xFF] << 16) ^
+                ((uint32_t) FSb[(RK[5]) & 0xFF] << 24);
+
+        RK[7] = RK[1] ^ RK[6];
+        RK[8] = RK[2] ^ RK[7];
+        RK[9] = RK[3] ^ RK[8];
+        RK[10] = RK[4] ^ RK[9];
+        RK[11] = RK[5] ^ RK[10];
+      }
+      break;
 
-void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) {
-  uint32_t t;
+    case 14:
+      for (i = 0; i < 7; i++, RK += 8) {
+        RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t) FSb[(RK[7] >> 8) & 0xFF]) ^
+                ((uint32_t) FSb[(RK[7] >> 16) & 0xFF] << 8) ^
+                ((uint32_t) FSb[(RK[7] >> 24) & 0xFF] << 16) ^
+                ((uint32_t) FSb[(RK[7]) & 0xFF] << 24);
+
+        RK[9] = RK[1] ^ RK[8];
+        RK[10] = RK[2] ^ RK[9];
+        RK[11] = RK[3] ^ RK[10];
+
+        RK[12] = RK[4] ^ ((uint32_t) FSb[(RK[11]) & 0xFF]) ^
+                 ((uint32_t) FSb[(RK[11] >> 8) & 0xFF] << 8) ^
+                 ((uint32_t) FSb[(RK[11] >> 16) & 0xFF] << 16) ^
+                 ((uint32_t) FSb[(RK[11] >> 24) & 0xFF] << 24);
+
+        RK[13] = RK[5] ^ RK[12];
+        RK[14] = RK[6] ^ RK[13];
+        RK[15] = RK[7] ^ RK[14];
+      }
+      break;
 
-  t = ctx->bits[0];
-  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
-  ctx->bits[1] += (uint32_t) len >> 29;
+    default:
+      return -1;
+  }
+  return (0);
+}
+
+#if AES_DECRYPTION  // whether AES decryption is supported
+
+/******************************************************************************
+ *
+ *  AES_SET_DECRYPTION_KEY
+ *
+ *  This is called by 'aes_setkey' when we're establishing a
+ *  key for subsequent decryption.  We give it a pointer to
+ *  the encryption context, a pointer to the key, and the key's
+ *  length in bits. Valid lengths are: 128, 192, or 256 bits.
+ *
+ ******************************************************************************/
+static int aes_set_decryption_key(aes_context *ctx, const uchar *key,
+                                  uint keysize) {
+  int i, j;
+  aes_context cty;         // a calling aes context for set_encryption_key
+  uint32_t *RK = ctx->rk;  // initialize our RoundKey buffer pointer
+  uint32_t *SK;
+  int ret;
 
-  t = (t >> 3) & 0x3f;
+  cty.rounds = ctx->rounds;  // initialize our local aes context
+  cty.rk = cty.buf;          // round count and key buf pointer
 
-  if (t) {
-    unsigned char *p = (unsigned char *) ctx->in + t;
+  if ((ret = aes_set_encryption_key(&cty, key, keysize)) != 0) return (ret);
 
-    t = 64 - t;
-    if (len < t) {
-      memcpy(p, buf, len);
-      return;
+  SK = cty.rk + cty.rounds * 4;
+
+  CPY128  // copy a 128-bit block from *SK to *RK
+
+      for (i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8) {
+    for (j = 0; j < 4; j++, SK++) {
+      *RK++ = RT0[FSb[(*SK) & 0xFF]] ^ RT1[FSb[(*SK >> 8) & 0xFF]] ^
+              RT2[FSb[(*SK >> 16) & 0xFF]] ^ RT3[FSb[(*SK >> 24) & 0xFF]];
     }
-    memcpy(p, buf, t);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += t;
-    len -= t;
+  }
+  CPY128  // copy a 128-bit block from *SK to *RK
+      memset(&cty, 0, sizeof(aes_context));  // clear local aes context
+  return (0);
+}
+
+#endif /* AES_DECRYPTION */
+
+/******************************************************************************
+ *
+ *  AES_SETKEY
+ *
+ *  Invoked to establish the key schedule for subsequent encryption/decryption
+ *
+ ******************************************************************************/
+static int aes_setkey(aes_context *ctx,  // AES context provided by our caller
+                      int mode,          // ENCRYPT or DECRYPT flag
+                      const uchar *key,  // pointer to the key
+                      uint keysize)      // key length in bytes
+{
+  // since table initialization is not thread safe, we could either add
+  // system-specific mutexes and init the AES key generation tables on
+  // demand, or ask the developer to simply call "gcm_initialize" once during
+  // application startup before threading begins. That's what we choose.
+  if (!aes_tables_inited) return (-1);  // fail the call when not inited.
+
+  ctx->mode = mode;    // capture the key type we're creating
+  ctx->rk = ctx->buf;  // initialize our round key pointer
+
+  switch (keysize)  // set the rounds count based upon the keysize
+  {
+    case 16:
+      ctx->rounds = 10;
+      break;  // 16-byte, 128-bit key
+    case 24:
+      ctx->rounds = 12;
+      break;  // 24-byte, 192-bit key
+    case 32:
+      ctx->rounds = 14;
+      break;  // 32-byte, 256-bit key
+    default:
+      return (-1);
   }
 
-  while (len >= 64) {
-    memcpy(ctx->in, buf, 64);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += 64;
-    len -= 64;
+#if AES_DECRYPTION
+  if (mode == MG_DECRYPT)  // expand our key for encryption or decryption
+    return (aes_set_decryption_key(ctx, key, keysize));
+  else /* MG_ENCRYPT */
+#endif /* AES_DECRYPTION */
+    return (aes_set_encryption_key(ctx, key, keysize));
+}
+
+/******************************************************************************
+ *
+ *  AES_CIPHER
+ *
+ *  Perform AES encryption and decryption.
+ *  The AES context will have been setup with the encryption mode
+ *  and all keying information appropriate for the task.
+ *
+ ******************************************************************************/
+static int aes_cipher(aes_context *ctx, const uchar input[16],
+                      uchar output[16]) {
+  int i;
+  uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;  // general purpose locals
+
+  RK = ctx->rk;
+
+  GET_UINT32_LE(X0, input, 0);
+  X0 ^= *RK++;  // load our 128-bit
+  GET_UINT32_LE(X1, input, 4);
+  X1 ^= *RK++;  // input buffer in a storage
+  GET_UINT32_LE(X2, input, 8);
+  X2 ^= *RK++;  // memory endian-neutral way
+  GET_UINT32_LE(X3, input, 12);
+  X3 ^= *RK++;
+
+#if AES_DECRYPTION  // whether AES decryption is supported
+
+  if (ctx->mode == MG_DECRYPT) {
+    for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
+      AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
+      AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
+    }
+
+    AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
+
+    X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^
+         ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24);
+
+    X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^
+         ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24);
+
+    X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^
+         ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24);
+
+    X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^
+         ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24);
+  } else /* MG_ENCRYPT */
+  {
+#endif /* AES_DECRYPTION */
+
+    for (i = (ctx->rounds >> 1) - 1; i > 0; i--) {
+      AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
+      AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
+    }
+
+    AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
+
+    X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^
+         ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24);
+
+    X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^
+         ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24);
+
+    X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^
+         ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24);
+
+    X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^
+         ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^
+         ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^
+         ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24);
+
+#if AES_DECRYPTION  // whether AES decryption is supported
   }
+#endif /* AES_DECRYPTION */
+
+  PUT_UINT32_LE(X0, output, 0);
+  PUT_UINT32_LE(X1, output, 4);
+  PUT_UINT32_LE(X2, output, 8);
+  PUT_UINT32_LE(X3, output, 12);
+
+  return (0);
+}
+/* end of aes.c */
+/******************************************************************************
+ *
+ * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL
+ *
+ * This is a simple and straightforward implementation of AES-GCM authenticated
+ * encryption. The focus of this work was correctness & accuracy. It is written
+ * in straight 'C' without any particular focus upon optimization or speed. It
+ * should be endian (memory byte order) neutral since the few places that care
+ * are handled explicitly.
+ *
+ * This implementation of AES-GCM was created by Steven M. Gibson of GRC.com.
+ *
+ * It is intended for general purpose use, but was written in support of GRC's
+ * reference implementation of the SQRL (Secure Quick Reliable Login) client.
+ *
+ * See:    http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
+ *         http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/
+ *         gcm/gcm-revised-spec.pdf
+ *
+ * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE
+ * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK.
+ *
+ *******************************************************************************/
+
+/******************************************************************************
+ *                      ==== IMPLEMENTATION WARNING ====
+ *
+ *  This code was developed for use within SQRL's fixed environmnent. Thus, it
+ *  is somewhat less "general purpose" than it would be if it were designed as
+ *  a general purpose AES-GCM library. Specifically, it bothers with almost NO
+ *  error checking on parameter limits, buffer bounds, etc. It assumes that it
+ *  is being invoked by its author or by someone who understands the values it
+ *  expects to receive. Its behavior will be undefined otherwise.
+ *
+ *  All functions that might fail are defined to return 'ints' to indicate a
+ *  problem. Most do not do so now. But this allows for error propagation out
+ *  of internal functions if robust error checking should ever be desired.
+ *
+ ******************************************************************************/
+
+/* Calculating the "GHASH"
+ *
+ * There are many ways of calculating the so-called GHASH in software, each with
+ * a traditional size vs performance tradeoff.  The GHASH (Galois field hash) is
+ * an intriguing construction which takes two 128-bit strings (also the cipher's
+ * block size and the fundamental operation size for the system) and hashes them
+ * into a third 128-bit result.
+ *
+ * Many implementation solutions have been worked out that use large precomputed
+ * table lookups in place of more time consuming bit fiddling, and this approach
+ * can be scaled easily upward or downward as needed to change the time/space
+ * tradeoff. It's been studied extensively and there's a solid body of theory
+ * and practice.  For example, without using any lookup tables an implementation
+ * might obtain 119 cycles per byte throughput, whereas using a simple, though
+ * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13
+ * cycles per byte.
+ *
+ * And Intel's processors have, since 2010, included an instruction which does
+ * the entire 128x128->128 bit job in just several 64x64->128 bit pieces.
+ *
+ * Since SQRL is interactive, and only processing a few 128-bit blocks, I've
+ * settled upon a relatively slower but appealing small-table compromise which
+ * folds a bunch of not only time consuming but also bit twiddling into a simple
+ * 16-entry table which is attributed to Victor Shoup's 1996 work while at
+ * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on
+ * Universal Hashing."  See: http://www.shoup.net/papers/macs.pdf
+ * See, also section 4.1 of the "gcm-revised-spec" cited above.
+ */
 
-  memcpy(ctx->in, buf, len);
-}
+/*
+ *  This 16-entry table of pre-computed constants is used by the
+ *  GHASH multiplier to improve over a strictly table-free but
+ *  significantly slower 128x128 bit multiple within GF(2^128).
+ */
+static const uint64_t last4[16] = {
+    0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
+    0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
 
-void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
-  unsigned count;
-  unsigned char *p;
-  uint32_t *a;
+/*
+ * Platform Endianness Neutralizing Load and Store Macro definitions
+ * GCM wants platform-neutral Big Endian (BE) byte ordering
+ */
+#define GET_UINT32_BE(n, b, i)                                            \
+  {                                                                       \
+    (n) = ((uint32_t) (b)[(i)] << 24) | ((uint32_t) (b)[(i) + 1] << 16) | \
+          ((uint32_t) (b)[(i) + 2] << 8) | ((uint32_t) (b)[(i) + 3]);     \
+  }
 
-  count = (ctx->bits[0] >> 3) & 0x3F;
+#define PUT_UINT32_BE(n, b, i)          \
+  {                                     \
+    (b)[(i)] = (uchar) ((n) >> 24);     \
+    (b)[(i) + 1] = (uchar) ((n) >> 16); \
+    (b)[(i) + 2] = (uchar) ((n) >> 8);  \
+    (b)[(i) + 3] = (uchar) ((n));       \
+  }
 
-  p = ctx->in + count;
-  *p++ = 0x80;
-  count = 64 - 1 - count;
-  if (count < 8) {
-    memset(p, 0, count);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    memset(ctx->in, 0, 56);
-  } else {
-    memset(p, 0, count - 8);
+/******************************************************************************
+ *
+ *  GCM_INITIALIZE
+ *
+ *  Must be called once to initialize the GCM library.
+ *
+ *  At present, this only calls the AES keygen table generator, which expands
+ *  the AES keying tables for use. This is NOT A THREAD-SAFE function, so it
+ *  MUST be called during system initialization before a multi-threading
+ *  environment is running.
+ *
+ ******************************************************************************/
+int mg_gcm_initialize(void) {
+  aes_init_keygen_tables();
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *  GCM_MULT
+ *
+ *  Performs a GHASH operation on the 128-bit input vector 'x', setting
+ *  the 128-bit output vector to 'x' times H using our precomputed tables.
+ *  'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field.
+ *
+ ******************************************************************************/
+static void gcm_mult(gcm_context *ctx,   // pointer to established context
+                     const uchar x[16],  // pointer to 128-bit input vector
+                     uchar output[16])   // pointer to 128-bit output vector
+{
+  int i;
+  uchar lo, hi, rem;
+  uint64_t zh, zl;
+
+  lo = (uchar) (x[15] & 0x0f);
+  hi = (uchar) (x[15] >> 4);
+  zh = ctx->HH[lo];
+  zl = ctx->HL[lo];
+
+  for (i = 15; i >= 0; i--) {
+    lo = (uchar) (x[i] & 0x0f);
+    hi = (uchar) (x[i] >> 4);
+
+    if (i != 15) {
+      rem = (uchar) (zl & 0x0f);
+      zl = (zh << 60) | (zl >> 4);
+      zh = (zh >> 4);
+      zh ^= (uint64_t) last4[rem] << 48;
+      zh ^= ctx->HH[lo];
+      zl ^= ctx->HL[lo];
+    }
+    rem = (uchar) (zl & 0x0f);
+    zl = (zh << 60) | (zl >> 4);
+    zh = (zh >> 4);
+    zh ^= (uint64_t) last4[rem] << 48;
+    zh ^= ctx->HH[hi];
+    zl ^= ctx->HL[hi];
   }
-  mg_byte_reverse(ctx->in, 14);
+  PUT_UINT32_BE(zh >> 32, output, 0);
+  PUT_UINT32_BE(zh, output, 4);
+  PUT_UINT32_BE(zl >> 32, output, 8);
+  PUT_UINT32_BE(zl, output, 12);
+}
+
+/******************************************************************************
+ *
+ *  GCM_SETKEY
+ *
+ *  This is called to set the AES-GCM key. It initializes the AES key
+ *  and populates the gcm context's pre-calculated HTables.
+ *
+ ******************************************************************************/
+static int gcm_setkey(
+    gcm_context *ctx,    // pointer to caller-provided gcm context
+    const uchar *key,    // pointer to the AES encryption key
+    const uint keysize)  // size in bytes (must be 16, 24, 32 for
+                         // 128, 192 or 256-bit keys respectively)
+{
+  int ret, i, j;
+  uint64_t hi, lo;
+  uint64_t vl, vh;
+  unsigned char h[16];
+
+  memset(ctx, 0, sizeof(gcm_context));  // zero caller-provided GCM context
+  memset(h, 0, 16);                     // initialize the block to encrypt
+
+  // encrypt the null 128-bit block to generate a key-based value
+  // which is then used to initialize our GHASH lookup tables
+  if ((ret = aes_setkey(&ctx->aes_ctx, MG_ENCRYPT, key, keysize)) != 0)
+    return (ret);
+  if ((ret = aes_cipher(&ctx->aes_ctx, h, h)) != 0) return (ret);
+
+  GET_UINT32_BE(hi, h, 0);  // pack h as two 64-bit ints, big-endian
+  GET_UINT32_BE(lo, h, 4);
+  vh = (uint64_t) hi << 32 | lo;
+
+  GET_UINT32_BE(hi, h, 8);
+  GET_UINT32_BE(lo, h, 12);
+  vl = (uint64_t) hi << 32 | lo;
+
+  ctx->HL[8] = vl;  // 8 = 1000 corresponds to 1 in GF(2^128)
+  ctx->HH[8] = vh;
+  ctx->HH[0] = 0;  // 0 corresponds to 0 in GF(2^128)
+  ctx->HL[0] = 0;
+
+  for (i = 4; i > 0; i >>= 1) {
+    uint32_t T = (uint32_t) (vl & 1) * 0xe1000000U;
+    vl = (vh << 63) | (vl >> 1);
+    vh = (vh >> 1) ^ ((uint64_t) T << 32);
+    ctx->HL[i] = vl;
+    ctx->HH[i] = vh;
+  }
+  for (i = 2; i < 16; i <<= 1) {
+    uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i;
+    vh = *HiH;
+    vl = *HiL;
+    for (j = 1; j < i; j++) {
+      HiH[j] = vh ^ ctx->HH[j];
+      HiL[j] = vl ^ ctx->HL[j];
+    }
+  }
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *    GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH.
+ *
+ *  SETKEY:
+ *
+ *   START: Sets the Encryption/Decryption mode.
+ *          Accepts the initialization vector and additional data.
+ *
+ *  UPDATE: Encrypts or decrypts the plaintext or ciphertext.
+ *
+ *  FINISH: Performs a final GHASH to generate the authentication tag.
+ *
+ ******************************************************************************
+ *
+ *  GCM_START
+ *
+ *  Given a user-provided GCM context, this initializes it, sets the encryption
+ *  mode, and preprocesses the initialization vector and additional AEAD data.
+ *
+ ******************************************************************************/
+int gcm_start(gcm_context *ctx,  // pointer to user-provided GCM context
+              int mode,          // GCM_ENCRYPT or GCM_DECRYPT
+              const uchar *iv,   // pointer to initialization vector
+              size_t iv_len,     // IV length in bytes (should == 12)
+              const uchar *add,  // ptr to additional AEAD data (NULL if none)
+              size_t add_len)    // length of additional AEAD data (bytes)
+{
+  int ret;             // our error return if the AES encrypt fails
+  uchar work_buf[16];  // XOR source built from provided IV if len != 16
+  const uchar *p;      // general purpose array pointer
+  size_t use_len;      // byte count to process, up to 16 bytes
+  size_t i;            // local loop iterator
+
+  // since the context might be reused under the same key
+  // we zero the working buffers for this next new process
+  memset(ctx->y, 0x00, sizeof(ctx->y));
+  memset(ctx->buf, 0x00, sizeof(ctx->buf));
+  ctx->len = 0;
+  ctx->add_len = 0;
+
+  ctx->mode = mode;                // set the GCM encryption/decryption mode
+  ctx->aes_ctx.mode = MG_ENCRYPT;  // GCM *always* runs AES in ENCRYPTION mode
+
+  if (iv_len == 12) {            // GCM natively uses a 12-byte, 96-bit IV
+    memcpy(ctx->y, iv, iv_len);  // copy the IV to the top of the 'y' buff
+    ctx->y[15] = 1;              // start "counting" from 1 (not 0)
+  } else  // if we don't have a 12-byte IV, we GHASH whatever we've been given
+  {
+    memset(work_buf, 0x00, 16);               // clear the working buffer
+    PUT_UINT32_BE(iv_len * 8, work_buf, 12);  // place the IV into buffer
+
+    p = iv;
+    while (iv_len > 0) {
+      use_len = (iv_len < 16) ? iv_len : 16;
+      for (i = 0; i < use_len; i++) ctx->y[i] ^= p[i];
+      gcm_mult(ctx, ctx->y, ctx->y);
+      iv_len -= use_len;
+      p += use_len;
+    }
+    for (i = 0; i < 16; i++) ctx->y[i] ^= work_buf[i];
+    gcm_mult(ctx, ctx->y, ctx->y);
+  }
+  if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ctx->base_ectr)) != 0)
+    return (ret);
+
+  ctx->add_len = add_len;
+  p = add;
+  while (add_len > 0) {
+    use_len = (add_len < 16) ? add_len : 16;
+    for (i = 0; i < use_len; i++) ctx->buf[i] ^= p[i];
+    gcm_mult(ctx, ctx->buf, ctx->buf);
+    add_len -= use_len;
+    p += use_len;
+  }
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *  GCM_UPDATE
+ *
+ *  This is called once or more to process bulk plaintext or ciphertext data.
+ *  We give this some number of bytes of input and it returns the same number
+ *  of output bytes. If called multiple times (which is fine) all but the final
+ *  invocation MUST be called with length mod 16 == 0. (Only the final call can
+ *  have a partial block length of < 128 bits.)
+ *
+ ******************************************************************************/
+int gcm_update(gcm_context *ctx,    // pointer to user-provided GCM context
+               size_t length,       // length, in bytes, of data to process
+               const uchar *input,  // pointer to source data
+               uchar *output)       // pointer to destination data
+{
+  int ret;         // our error return if the AES encrypt fails
+  uchar ectr[16];  // counter-mode cipher output for XORing
+  size_t use_len;  // byte count to process, up to 16 bytes
+  size_t i;        // local loop iterator
+
+  ctx->len += length;  // bump the GCM context's running length count
+
+  while (length > 0) {
+    // clamp the length to process at 16 bytes
+    use_len = (length < 16) ? length : 16;
+
+    // increment the context's 128-bit IV||Counter 'y' vector
+    for (i = 16; i > 12; i--)
+      if (++ctx->y[i - 1] != 0) break;
+
+    // encrypt the context's 'y' vector under the established key
+    if ((ret = aes_cipher(&ctx->aes_ctx, ctx->y, ectr)) != 0) return (ret);
+
+    // encrypt or decrypt the input to the output
+    if (ctx->mode == MG_ENCRYPT) {
+      for (i = 0; i < use_len; i++) {
+        // XOR the cipher's ouptut vector (ectr) with our input
+        output[i] = (uchar) (ectr[i] ^ input[i]);
+        // now we mix in our data into the authentication hash.
+        // if we're ENcrypting we XOR in the post-XOR (output)
+        // results, but if we're DEcrypting we XOR in the input
+        // data
+        ctx->buf[i] ^= output[i];
+      }
+    } else {
+      for (i = 0; i < use_len; i++) {
+        // but if we're DEcrypting we XOR in the input data first,
+        // i.e. before saving to ouput data, otherwise if the input
+        // and output buffer are the same (inplace decryption) we
+        // would not get the correct auth tag
 
-  a = (uint32_t *) ctx->in;
-  a[14] = ctx->bits[0];
-  a[15] = ctx->bits[1];
+        ctx->buf[i] ^= input[i];
 
-  mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-  mg_byte_reverse((unsigned char *) ctx->buf, 4);
-  memcpy(digest, ctx->buf, 16);
-  memset((char *) ctx, 0, sizeof(*ctx));
-}
-#endif
+        // XOR the cipher's ouptut vector (ectr) with our input
+        output[i] = (uchar) (ectr[i] ^ input[i]);
+      }
+    }
+    gcm_mult(ctx, ctx->buf, ctx->buf);  // perform a GHASH operation
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/mqtt.c"
-#endif
+    length -= use_len;  // drop the remaining byte count to process
+    input += use_len;   // bump our input pointer forward
+    output += use_len;  // bump our output pointer forward
+  }
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *  GCM_FINISH
+ *
+ *  This is called once after all calls to GCM_UPDATE to finalize the GCM.
+ *  It performs the final GHASH to produce the resulting authentication TAG.
+ *
+ ******************************************************************************/
+int gcm_finish(gcm_context *ctx,  // pointer to user-provided GCM context
+               uchar *tag,        // pointer to buffer which receives the tag
+               size_t tag_len)    // length, in bytes, of the tag-receiving buf
+{
+  uchar work_buf[16];
+  uint64_t orig_len = ctx->len * 8;
+  uint64_t orig_add_len = ctx->add_len * 8;
+  size_t i;
 
+  if (tag_len != 0) memcpy(tag, ctx->base_ectr, tag_len);
 
+  if (orig_len || orig_add_len) {
+    memset(work_buf, 0x00, 16);
 
+    PUT_UINT32_BE((orig_add_len >> 32), work_buf, 0);
+    PUT_UINT32_BE((orig_add_len), work_buf, 4);
+    PUT_UINT32_BE((orig_len >> 32), work_buf, 8);
+    PUT_UINT32_BE((orig_len), work_buf, 12);
 
+    for (i = 0; i < 16; i++) ctx->buf[i] ^= work_buf[i];
+    gcm_mult(ctx, ctx->buf, ctx->buf);
+    for (i = 0; i < tag_len; i++) tag[i] ^= ctx->buf[i];
+  }
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *  GCM_CRYPT_AND_TAG
+ *
+ *  This either encrypts or decrypts the user-provided data and, either
+ *  way, generates an authentication tag of the requested length. It must be
+ *  called with a GCM context whose key has already been set with GCM_SETKEY.
+ *
+ *  The user would typically call this explicitly to ENCRYPT a buffer of data
+ *  and optional associated data, and produce its an authentication tag.
+ *
+ *  To reverse the process the user would typically call the companion
+ *  GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided
+ *  authentication tag.  The GCM_AUTH_DECRYPT function calls this function
+ *  to perform its decryption and tag generation, which it then compares.
+ *
+ ******************************************************************************/
+int gcm_crypt_and_tag(
+    gcm_context *ctx,    // gcm context with key already setup
+    int mode,            // cipher direction: GCM_ENCRYPT or GCM_DECRYPT
+    const uchar *iv,     // pointer to the 12-byte initialization vector
+    size_t iv_len,       // byte length if the IV. should always be 12
+    const uchar *add,    // pointer to the non-ciphered additional data
+    size_t add_len,      // byte length of the additional AEAD data
+    const uchar *input,  // pointer to the cipher data source
+    uchar *output,       // pointer to the cipher data destination
+    size_t length,       // byte length of the cipher data
+    uchar *tag,          // pointer to the tag to be generated
+    size_t tag_len)      // byte length of the tag to be generated
+{                        /*
+                            assuming that the caller has already invoked gcm_setkey to
+                            prepare the gcm context with the keying material, we simply
+                            invoke each of the three GCM sub-functions in turn...
+                         */
+  gcm_start(ctx, mode, iv, iv_len, add, add_len);
+  gcm_update(ctx, length, input, output);
+  gcm_finish(ctx, tag, tag_len);
+  return (0);
+}
+
+/******************************************************************************
+ *
+ *  GCM_ZERO_CTX
+ *
+ *  The GCM context contains both the GCM context and the AES context.
+ *  This includes keying and key-related material which is security-
+ *  sensitive, so it MUST be zeroed after use. This function does that.
+ *
+ ******************************************************************************/
+void gcm_zero_ctx(gcm_context *ctx) {
+  // zero the context originally provided to us
+  memset(ctx, 0, sizeof(gcm_context));
+}
+//
+//  aes-gcm.c
+//  Pods
+//
+//  Created by Markus Kosmal on 20/11/14.
+//
+//
 
+int mg_aes_gcm_encrypt(unsigned char *output,  //
+                       const unsigned char *input, size_t input_length,
+                       const unsigned char *key, const size_t key_len,
+                       const unsigned char *iv, const size_t iv_len,
+                       unsigned char *aead, size_t aead_len, unsigned char *tag,
+                       const size_t tag_len) {
+  int ret = 0;      // our return value
+  gcm_context ctx;  // includes the AES context structure
 
+  gcm_setkey(&ctx, key, (uint) key_len);
 
+  ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead, aead_len, input,
+                          output, input_length, tag, tag_len);
 
-#define MQTT_CLEAN_SESSION 0x02
-#define MQTT_HAS_WILL 0x04
-#define MQTT_WILL_RETAIN 0x20
-#define MQTT_HAS_PASSWORD 0x40
-#define MQTT_HAS_USER_NAME 0x80
+  gcm_zero_ctx(&ctx);
 
-void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags,
-                         uint32_t len) {
-  uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
-  buf[0] = (uint8_t) ((cmd << 4) | flags);
-  do {
-    *vlen = len % 0x80;
-    len /= 0x80;
-    if (len > 0) *vlen |= 0x80;
-    vlen++;
-  } while (len > 0 && vlen < &buf[sizeof(buf)]);
-  mg_send(c, buf, (size_t) (vlen - buf));
+  return (ret);
 }
 
-static void mg_send_u16(struct mg_connection *c, uint16_t value) {
-  mg_send(c, &value, sizeof(value));
+int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input,
+                       size_t input_length, const unsigned char *key,
+                       const size_t key_len, const unsigned char *iv,
+                       const size_t iv_len) {
+  int ret = 0;      // our return value
+  gcm_context ctx;  // includes the AES context structure
+
+  size_t tag_len = 0;
+  unsigned char *tag_buf = NULL;
+
+  gcm_setkey(&ctx, key, (uint) key_len);
+
+  ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0, input, output,
+                          input_length, tag_buf, tag_len);
+
+  gcm_zero_ctx(&ctx);
+
+  return (ret);
 }
+#endif
+// End of aes128 PD
 
-void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
-  char rnd[10], client_id[21], zero = 0;
-  struct mg_str cid = opts->client_id;
-  uint32_t total_len = 7 + 1 + 2 + 2;
-  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
+#ifdef MG_ENABLE_LINES
+#line 1 "src/tls_builtin.c"
+#endif
 
-  if (cid.len == 0) {
-    mg_random(rnd, sizeof(rnd));
-    mg_hex(rnd, sizeof(rnd), client_id);
-    client_id[sizeof(client_id) - 1] = '\0';
-    cid = mg_str(client_id);
-  }
 
-  if (hdr[6] == 0) hdr[6] = 4;  // If version is not set, use 4 (3.1.1)
-  c->is_mqtt5 = hdr[6] == 5;    // Set version 5 flag
-  hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3);  // Connection flags
-  if (opts->user.len > 0) {
-    total_len += 2 + (uint32_t) opts->user.len;
-    hdr[7] |= MQTT_HAS_USER_NAME;
+
+
+#if MG_TLS == MG_TLS_BUILTIN
+
+/* TLS 1.3 Record Content Type (RFC8446 B.1) */
+#define MG_TLS_CHANGE_CIPHER 20
+#define MG_TLS_ALERT 21
+#define MG_TLS_HANDSHAKE 22
+#define MG_TLS_APP_DATA 23
+#define MG_TLS_HEARTBEAT 24
+
+/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
+#define MG_TLS_CLIENT_HELLO 1
+#define MG_TLS_SERVER_HELLO 2
+#define MG_TLS_ENCRYPTED_EXTENSIONS 8
+#define MG_TLS_CERTIFICATE 11
+#define MG_TLS_CERTIFICATE_VERIFY 15
+#define MG_TLS_FINISHED 20
+
+// handshake is re-entrant, so we need to keep track of its state state names
+// refer to RFC8446#A.1
+enum mg_tls_hs_state {
+  // Client state machine:
+  MG_TLS_STATE_CLIENT_START,          // Send ClientHello
+  MG_TLS_STATE_CLIENT_WAIT_SH,        // Wait for ServerHello
+  MG_TLS_STATE_CLIENT_WAIT_EE,        // Wait for EncryptedExtensions
+  MG_TLS_STATE_CLIENT_WAIT_CERT,      // Wait for Certificate
+  MG_TLS_STATE_CLIENT_WAIT_CV,        // Wait for CertificateVerify
+  MG_TLS_STATE_CLIENT_WAIT_FINISHED,  // Wait for Finished
+  MG_TLS_STATE_CLIENT_CONNECTED,      // Done
+
+  // Server state machine:
+  MG_TLS_STATE_SERVER_START,       // Wait for ClientHello
+  MG_TLS_STATE_SERVER_NEGOTIATED,  // Wait for Finished
+  MG_TLS_STATE_SERVER_CONNECTED    // Done
+};
+
+// per-connection TLS data
+struct tls_data {
+  enum mg_tls_hs_state state;  // keep track of connection handshake progress
+
+  struct mg_iobuf send;  // For the receive path, we're reusing c->rtls
+  struct mg_iobuf recv;  // While c->rtls contains full records, recv reuses
+                         // the same underlying buffer but points at individual
+                         // decrypted messages
+  uint8_t content_type;  // Last received record content type
+
+  mg_sha256_ctx sha256;  // incremental SHA-256 hash for TLS handshake
+
+  uint32_t sseq;  // server sequence number, used in encryption
+  uint32_t cseq;  // client sequence number, used in decryption
+
+  uint8_t random[32];      // client random from ClientHello
+  uint8_t session_id[32];  // client session ID between the handshake states
+  uint8_t x25519_cli[32];  // client X25519 key between the handshake states
+  uint8_t x25519_sec[32];  // x25519 secret between the handshake states
+
+  int skip_verification;          // perform checks on server certificate?
+  struct mg_str server_cert_der;  // server certificate in DER format
+  uint8_t server_key[32];         // server EC private key
+  char hostname[254];             // server hostname (client extension)
+
+  uint8_t certhash[32];  // certificate message hash
+  uint8_t pubkey[64];    // server EC public key to verify cert
+  uint8_t sighash[32];   // server EC public key to verify cert
+
+  // keys for AES encryption
+  uint8_t handshake_secret[32];
+  uint8_t server_write_key[16];
+  uint8_t server_write_iv[12];
+  uint8_t server_finished_key[32];
+  uint8_t client_write_key[16];
+  uint8_t client_write_iv[12];
+  uint8_t client_finished_key[32];
+};
+
+#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) | MG_U8P(p)[1]))
+#define MG_LOAD_BE24(p) \
+  ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) | MG_U8P(p)[2]))
+#define MG_STORE_BE16(p, n)           \
+  do {                                \
+    MG_U8P(p)[0] = ((n) >> 8U) & 255; \
+    MG_U8P(p)[1] = (n) & 255;         \
+  } while (0)
+
+#define TLS_RECHDR_SIZE 5  // 1 byte type, 2 bytes version, 2 bytes length
+#define TLS_MSGHDR_SIZE 4  // 1 byte type, 3 bytes length
+
+#if 1
+static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
+                           uint8_t *secret, size_t secretsz) {
+  (void) label;
+  (void) client_random;
+  (void) secret;
+  (void) secretsz;
+}
+#else
+#include <stdio.h>
+static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
+                           uint8_t *secret, size_t secretsz) {
+  char *keylogfile = getenv("SSLKEYLOGFILE");
+  if (keylogfile == NULL) {
+    return;
   }
-  if (opts->pass.len > 0) {
-    total_len += 2 + (uint32_t) opts->pass.len;
-    hdr[7] |= MQTT_HAS_PASSWORD;
+  FILE *f = fopen(keylogfile, "a");
+  fprintf(f, "%s ", label);
+  for (int i = 0; i < 32; i++) {
+    fprintf(f, "%02x", client_random[i]);
   }
-  if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
-    total_len +=
-        4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len;
-    hdr[7] |= MQTT_HAS_WILL;
+  fprintf(f, " ");
+  for (unsigned int i = 0; i < secretsz; i++) {
+    fprintf(f, "%02x", secret[i]);
   }
-  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
-  if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
-  total_len += (uint32_t) cid.len;
-  if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U : 0);
+  fprintf(f, "\n");
+  fclose(f);
+}
+#endif
 
-  mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
-  mg_send(c, hdr, sizeof(hdr));
-  // keepalive == 0 means "do not disconnect us!"
-  mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
+// for derived tls keys we need SHA256([0]*32)
+static uint8_t zeros[32] = {0};
+static uint8_t zeros_sha256_digest[32] = {
+    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
+    0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
+    0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
 
-  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // V5 properties
-  mg_send_u16(c, mg_htons((uint16_t) cid.len));
-  mg_send(c, cid.ptr, cid.len);
+// helper to hexdump buffers inline
+static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) {
+  MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
+}
 
-  if (hdr[7] & MQTT_HAS_WILL) {
-    if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // will props
-    mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
-    mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
-    mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
-    mg_send(c, opts->will_message.ptr, opts->will_message.len);
+// helper utilities to parse ASN.1 DER
+struct mg_der_tlv {
+  uint8_t type;
+  uint32_t len;
+  uint8_t *value;
+};
+
+// parse DER into a TLV record
+static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) {
+  if (dersz < 2) {
+    return -1;
   }
-  if (opts->user.len > 0) {
-    mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
-    mg_send(c, opts->user.ptr, opts->user.len);
+  tlv->type = der[0];
+  tlv->len = der[1];
+  tlv->value = der + 2;
+  if (tlv->len > 0x7f) {
+    uint32_t i, n = tlv->len - 0x80;
+    tlv->len = 0;
+    for (i = 0; i < n; i++) {
+      tlv->len = (tlv->len << 8) | (der[2 + i]);
+    }
+    tlv->value = der + 2 + n;
   }
-  if (opts->pass.len > 0) {
-    mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
-    mg_send(c, opts->pass.ptr, opts->pass.len);
+  if (der + dersz < tlv->value + tlv->len) {
+    return -1;
   }
+  return 0;
 }
 
-void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
-                 struct mg_str data, int qos, bool retain) {
-  uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)), zero = 0;
-  uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
-  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) topic.ptr,
-            (int) data.len, (char *) data.ptr));
-  if (qos > 0) len += 2;
-  if (c->is_mqtt5) len++;
-  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
-  mg_send_u16(c, mg_htons((uint16_t) topic.len));
-  mg_send(c, topic.ptr, topic.len);
-  if (qos > 0) {
-    if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
-    mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
+static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid, size_t oidsz,
+                       struct mg_der_tlv *tlv) {
+  uint8_t *p, *end;
+  struct mg_der_tlv child = {0, 0, NULL};
+  if (mg_der_to_tlv(der, dersz, tlv) < 0) {
+    return -1;                  // invalid DER
+  } else if (tlv->type == 6) {  // found OID, check value
+    return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0);
+  } else if ((tlv->type & 0x20) == 0) {
+    return 0;  // Primitive, but not OID: not found
+  }
+  // Constructed object: scan children
+  p = tlv->value;
+  end = tlv->value + tlv->len;
+  while (end > p) {
+    int r;
+    mg_der_to_tlv(p, (size_t) (end - p), &child);
+    r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv);
+    if (r < 0) return -1;  // error
+    if (r > 0) return 1;   // found OID!
+    p = child.value + child.len;
   }
-  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
-  mg_send(c, data.ptr, data.len);
+  return 0;  // not found
 }
 
-void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) {
-  uint8_t qos_ = qos & 3, zero = 0;
-  uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5 ? 1 : 0);
-  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
-  if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
-  mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
-  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
-  mg_send_u16(c, mg_htons((uint16_t) topic.len));
-  mg_send(c, topic.ptr, topic.len);
-  mg_send(c, &qos_, sizeof(qos_));
+// Did we receive a full TLS record in the c->rtls buffer?
+static bool mg_tls_got_record(struct mg_connection *c) {
+  return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
+         c->rtls.len >=
+             (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
 }
 
-int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
-                  struct mg_mqtt_message *m) {
-  uint8_t lc = 0, *p, *end;
-  uint32_t n = 0, len_len = 0;
+// Remove a single TLS record from the recv buffer
+static void mg_tls_drop_record(struct mg_connection *c) {
+  struct mg_iobuf *rio = &c->rtls;
+  uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
+  mg_iobuf_del(rio, 0, n);
+}
 
-  memset(m, 0, sizeof(*m));
-  m->dgram.ptr = (char *) buf;
-  if (len < 2) return MQTT_INCOMPLETE;
-  m->cmd = (uint8_t) (buf[0] >> 4);
-  m->qos = (buf[0] >> 1) & 3;
+// Remove a single TLS message from decrypted buffer, remove the wrapping
+// record if it was the last message within a record
+static void mg_tls_drop_message(struct mg_connection *c) {
+  uint32_t len;
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (tls->recv.len == 0) {
+    return;
+  }
+  len = MG_LOAD_BE24(tls->recv.buf + 1);
+  mg_sha256_update(&tls->sha256, tls->recv.buf, len + TLS_MSGHDR_SIZE);
+  tls->recv.buf += len + TLS_MSGHDR_SIZE;
+  tls->recv.len -= len + TLS_MSGHDR_SIZE;
+  if (tls->recv.len == 0) {
+    mg_tls_drop_record(c);
+  }
+}
 
-  n = len_len = 0;
-  p = (uint8_t *) buf + 1;
-  while ((size_t) (p - buf) < len) {
-    lc = *((uint8_t *) p++);
-    n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
-    len_len++;
-    if (!(lc & 0x80)) break;
-    if (len_len >= 4) return MQTT_MALFORMED;
+// TLS1.3 secret derivation based on the key label
+static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz,
+                                 uint8_t *data, size_t datasz, uint8_t *hash,
+                                 size_t hashsz) {
+  size_t labelsz = strlen(label);
+  uint8_t secret[32];
+  uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
+  // TODO: assert lengths of label, key, data and hash
+  if (labelsz > 0) memmove(packed + 3, label, labelsz);
+  packed[3 + labelsz] = (uint8_t) datasz;
+  if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
+  packed[4 + labelsz + datasz] = 1;
+
+  mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
+  memmove(hash, secret, hashsz);
+}
+
+// at this point we have x25519 shared secret, we can generate a set of derived
+// handshake encryption keys
+static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+
+  mg_sha256_ctx sha256;
+  uint8_t early_secret[32];
+  uint8_t pre_extract_secret[32];
+  uint8_t hello_hash[32];
+  uint8_t server_hs_secret[32];
+  uint8_t client_hs_secret[32];
+
+  mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
+  mg_tls_derive_secret("tls13 derived", early_secret, 32, zeros_sha256_digest,
+                       32, pre_extract_secret, 32);
+  mg_hmac_sha256(tls->handshake_secret, pre_extract_secret,
+                 sizeof(pre_extract_secret), tls->x25519_sec,
+                 sizeof(tls->x25519_sec));
+  mg_tls_hexdump("hs secret", tls->handshake_secret, 32);
+
+  // mg_sha256_final is not idempotent, need to copy sha256 context to calculate
+  // the digest
+  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
+  mg_sha256_final(hello_hash, &sha256);
+
+  mg_tls_hexdump("hello hash", hello_hash, 32);
+  // derive keys needed for the rest of the handshake
+  mg_tls_derive_secret("tls13 s hs traffic", tls->handshake_secret, 32,
+                       hello_hash, 32, server_hs_secret, 32);
+  mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
+                       tls->server_write_key, 16);
+  mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
+                       tls->server_write_iv, 12);
+  mg_tls_derive_secret("tls13 finished", server_hs_secret, 32, NULL, 0,
+                       tls->server_finished_key, 32);
+
+  mg_tls_derive_secret("tls13 c hs traffic", tls->handshake_secret, 32,
+                       hello_hash, 32, client_hs_secret, 32);
+  mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
+                       tls->client_write_key, 16);
+  mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
+                       tls->client_write_iv, 12);
+  mg_tls_derive_secret("tls13 finished", client_hs_secret, 32, NULL, 0,
+                       tls->client_finished_key, 32);
+
+  mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
+  mg_tls_hexdump("s key", tls->server_write_key, 16);
+  mg_tls_hexdump("s iv", tls->server_write_iv, 12);
+  mg_tls_hexdump("s finished", tls->server_finished_key, 32);
+  mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
+  mg_tls_hexdump("c key", tls->client_write_key, 16);
+  mg_tls_hexdump("c iv", tls->client_write_iv, 16);
+  mg_tls_hexdump("c finished", tls->client_finished_key, 32);
+
+  mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
+                 server_hs_secret, 32);
+  mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
+                 client_hs_secret, 32);
+}
+
+static void mg_tls_generate_application_keys(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  uint8_t hash[32];
+  uint8_t premaster_secret[32];
+  uint8_t master_secret[32];
+  uint8_t server_secret[32];
+  uint8_t client_secret[32];
+
+  mg_sha256_ctx sha256;
+  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
+  mg_sha256_final(hash, &sha256);
+
+  mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32,
+                       zeros_sha256_digest, 32, premaster_secret, 32);
+  mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);
+
+  mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32,
+                       server_secret, 32);
+  mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
+                       tls->server_write_key, 16);
+  mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
+                       tls->server_write_iv, 12);
+  mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32,
+                       client_secret, 32);
+  mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
+                       tls->client_write_key, 16);
+  mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
+                       tls->client_write_iv, 12);
+
+  mg_tls_hexdump("s ap traffic", server_secret, 32);
+  mg_tls_hexdump("s key", tls->server_write_key, 16);
+  mg_tls_hexdump("s iv", tls->server_write_iv, 12);
+  mg_tls_hexdump("s finished", tls->server_finished_key, 32);
+  mg_tls_hexdump("c ap traffic", client_secret, 32);
+  mg_tls_hexdump("c key", tls->client_write_key, 16);
+  mg_tls_hexdump("c iv", tls->client_write_iv, 16);
+  mg_tls_hexdump("c finished", tls->client_finished_key, 32);
+  tls->sseq = tls->cseq = 0;
+
+  mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random, server_secret, 32);
+  mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random, client_secret, 32);
+}
+
+// AES GCM encryption of the message + put encoded data into the write buffer
+static void mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
+                           size_t msgsz, uint8_t msgtype) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *wio = &tls->send;
+  uint8_t *outmsg;
+  uint8_t *tag;
+  size_t encsz = msgsz + 16 + 1;
+  uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
+                    (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
+  uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
+                                (uint8_t) ((encsz >> 8) & 0xff),
+                                (uint8_t) (encsz & 0xff)};
+  uint8_t nonce[12];
+
+  mg_gcm_initialize();
+
+  if (c->is_client) {
+    memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
+    nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
+    nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
+    nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
+    nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
+  } else {
+    memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
+    nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
+    nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
+    nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
+    nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
   }
-  end = p + n;
-  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
-  m->dgram.len = (size_t) (end - buf);
 
-  switch (m->cmd) {
-    case MQTT_CMD_CONNACK:
-      if (end - p < 2) return MQTT_MALFORMED;
-      m->ack = p[1];
-      break;
-    case MQTT_CMD_PUBACK:
-    case MQTT_CMD_PUBREC:
-    case MQTT_CMD_PUBREL:
-    case MQTT_CMD_PUBCOMP:
-    case MQTT_CMD_SUBSCRIBE:
-    case MQTT_CMD_SUBACK:
-    case MQTT_CMD_UNSUBSCRIBE:
-    case MQTT_CMD_UNSUBACK:
-      if (p + 2 > end) return MQTT_MALFORMED;
-      m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
-      p += 2;
-      break;
-    case MQTT_CMD_PUBLISH: {
-      if (p + 2 > end) return MQTT_MALFORMED;
-      m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
-      m->topic.ptr = (char *) p + 2;
-      p += 2 + m->topic.len;
-      if (p > end) return MQTT_MALFORMED;
-      if (m->qos > 0) {
-        if (p + 2 > end) return MQTT_MALFORMED;
-        m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
-        p += 2;
-      }
-      if (p > end) return MQTT_MALFORMED;
-      if (version == 5 && p + 2 < end) p += 1 + p[0];  // Skip options
-      if (p > end) return MQTT_MALFORMED;
-      m->data.ptr = (char *) p;
-      m->data.len = (size_t) (end - p);
-      break;
+  mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr));
+  mg_iobuf_resize(wio, wio->len + encsz);
+  outmsg = wio->buf + wio->len;
+  tag = wio->buf + wio->len + msgsz + 1;
+  memmove(outmsg, msg, msgsz);
+  outmsg[msgsz] = msgtype;
+  if (c->is_client) {
+    mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->client_write_key,
+                       sizeof(tls->client_write_key), nonce, sizeof(nonce),
+                       associated_data, sizeof(associated_data), tag, 16);
+    tls->cseq++;
+  } else {
+    mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->server_write_key,
+                       sizeof(tls->server_write_key), nonce, sizeof(nonce),
+                       associated_data, sizeof(associated_data), tag, 16);
+    tls->sseq++;
+  }
+  wio->len += encsz;
+}
+
+// read an encrypted record, decrypt it in place
+static int mg_tls_recv_record(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *rio = &c->rtls;
+  uint16_t msgsz;
+  uint8_t *msg;
+  uint8_t nonce[12];
+  int r;
+  if (tls->recv.len > 0) {
+    return 0; /* some data from previous record is still present */
+  }
+  for (;;) {
+    if (!mg_tls_got_record(c)) {
+      return MG_IO_WAIT;
     }
-    default:
+    if (rio->buf[0] == MG_TLS_APP_DATA) {
       break;
+    } else if (rio->buf[0] ==
+               MG_TLS_CHANGE_CIPHER) {  // Skip ChangeCipher messages
+      mg_tls_drop_record(c);
+    } else if (rio->buf[0] == MG_TLS_ALERT) {  // Skip Alerts
+      MG_INFO(("TLS ALERT packet received"));
+      mg_tls_drop_record(c);
+    } else {
+      mg_error(c, "unexpected packet");
+      return -1;
+    }
   }
-  return MQTT_OK;
+
+  mg_gcm_initialize();
+  msgsz = MG_LOAD_BE16(rio->buf + 3);
+  msg = rio->buf + 5;
+  if (c->is_client) {
+    memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv));
+    nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U);
+    nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U);
+    nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U);
+    nonce[11] ^= (uint8_t) ((tls->sseq) & 255U);
+    mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key,
+                       sizeof(tls->server_write_key), nonce, sizeof(nonce));
+    tls->sseq++;
+  } else {
+    memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv));
+    nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U);
+    nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U);
+    nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U);
+    nonce[11] ^= (uint8_t) ((tls->cseq) & 255U);
+    mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key,
+                       sizeof(tls->client_write_key), nonce, sizeof(nonce));
+    tls->cseq++;
+  }
+  r = msgsz - 16 - 1;
+  tls->content_type = msg[msgsz - 16 - 1];
+  tls->recv.buf = msg;
+  tls->recv.size = tls->recv.len = msgsz - 16 - 1;
+  return r;
 }
 
-static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
-                    void *fn_data) {
-  if (ev == MG_EV_READ) {
-    for (;;) {
-      uint8_t version = c->is_mqtt5 ? 5 : 4;
-      struct mg_mqtt_message mm;
-      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
-      if (rc == MQTT_MALFORMED) {
-        MG_ERROR(("%lu MQTT malformed message", c->id));
-        c->is_closing = 1;
-        break;
-      } else if (rc == MQTT_OK) {
-        MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd,
-                    (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
-        switch (mm.cmd) {
-          case MQTT_CMD_CONNACK:
-            mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
-            if (mm.ack == 0) {
-              MG_DEBUG(("%lu Connected", c->id));
-            } else {
-              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
-              c->is_closing = 1;
-            }
-            break;
-          case MQTT_CMD_PUBLISH: {
-            MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
-                      mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
-            if (mm.qos > 0) {
-              uint16_t id = mg_htons(mm.id);
-              mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
-              mg_send(c, &id, sizeof(id));
-            }
-            mg_call(c, MG_EV_MQTT_MSG, &mm);
-            break;
-          }
-        }
-        mg_call(c, MG_EV_MQTT_CMD, &mm);
-        mg_iobuf_del(&c->recv, 0, mm.dgram.len);
-      } else {
-        break;
+static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
+                                         uint8_t hash[32]) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  uint8_t sig_content[130] = {
+      "                                "
+      "                                "
+      "TLS 1.3, server CertificateVerify\0"};
+  mg_sha256_ctx sha256;
+  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
+  mg_sha256_final(sig_content + 98, &sha256);
+
+  mg_sha256_init(&sha256);
+  mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
+  mg_sha256_final(hash, &sha256);
+}
+
+// read and parse ClientHello record
+static int mg_tls_server_recv_hello(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *rio = &c->rtls;
+  uint8_t session_id_len;
+  uint16_t j;
+  uint16_t cipher_suites_len;
+  uint16_t ext_len;
+  uint8_t *ext;
+  uint16_t msgsz;
+
+  if (!mg_tls_got_record(c)) {
+    return MG_IO_WAIT;
+  }
+  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
+    mg_error(c, "not a client hello packet");
+    return -1;
+  }
+  msgsz = MG_LOAD_BE16(rio->buf + 3);
+  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
+  // store client random
+  memmove(tls->random, rio->buf + 11, sizeof(tls->random));
+  // store session_id
+  session_id_len = rio->buf[43];
+  if (session_id_len == sizeof(tls->session_id)) {
+    memmove(tls->session_id, rio->buf + 44, session_id_len);
+  } else if (session_id_len != 0) {
+    MG_INFO(("bad session id len"));
+  }
+  cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
+  ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len);
+  ext = rio->buf + 50 + session_id_len + cipher_suites_len;
+  for (j = 0; j < ext_len;) {
+    uint16_t k;
+    uint16_t key_exchange_len;
+    uint8_t *key_exchange;
+    uint16_t n = MG_LOAD_BE16(ext + j + 2);
+    if (ext[j] != 0x00 ||
+        ext[j + 1] != 0x33) {  // not a key share extension, ignore
+      j += (uint16_t) (n + 4);
+      continue;
+    }
+    key_exchange_len = MG_LOAD_BE16(ext + j + 5);
+    key_exchange = ext + j + 6;
+    for (k = 0; k < key_exchange_len;) {
+      uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
+      if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) {
+        memmove(tls->x25519_cli, key_exchange + k + 4, m);
+        mg_tls_drop_record(c);
+        return 0;
       }
+      k += (uint16_t) (m + 4);
     }
+    j += (uint16_t) (n + 4);
   }
-  (void) ev_data;
-  (void) fn_data;
-}
-
-void mg_mqtt_ping(struct mg_connection *nc) {
-  mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
-}
-
-void mg_mqtt_pong(struct mg_connection *nc) {
-  mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
+  mg_error(c, "bad client hello");
+  return -1;
 }
 
-void mg_mqtt_disconnect(struct mg_connection *nc) {
-  mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0);
-}
+#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
+#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
+#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B
+
+// put ServerHello record into wio buffer
+static void mg_tls_server_send_hello(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *wio = &tls->send;
+
+  uint8_t msg_server_hello[122] = {
+    // server hello, tls 1.2
+    0x02,
+    0x00,
+    0x00,
+    0x76,
+    0x03,
+    0x03,
+    // random (32 bytes)
+    PLACEHOLDER_32B,
+    // session ID length + session ID (32 bytes)
+    0x20,
+    PLACEHOLDER_32B,
+#if defined(CHACHA20) && CHACHA20
+    // TLS_CHACHA20_POLY1305_SHA256 + no compression
+    0x13,
+    0x03,
+    0x00,
+#else
+    // TLS_AES_128_GCM_SHA256 + no compression
+    0x13,
+    0x01,
+    0x00,
+#endif
+    // extensions + keyshare
+    0x00,
+    0x2e,
+    0x00,
+    0x33,
+    0x00,
+    0x24,
+    0x00,
+    0x1d,
+    0x00,
+    0x20,
+    // x25519 keyshare
+    PLACEHOLDER_32B,
+    // supported versions (tls1.3 == 0x304)
+    0x00,
+    0x2b,
+    0x00,
+    0x02,
+    0x03,
+    0x04
+  };
 
-struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
-                                      const struct mg_mqtt_opts *opts,
-                                      mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
-  if (c != NULL) {
-    struct mg_mqtt_opts empty;
-    memset(&empty, 0, sizeof(empty));
-    mg_mqtt_login(c, opts == NULL ? &empty : opts);
-    c->pfn = mqtt_cb;
+  // calculate keyshare
+  uint8_t x25519_pub[X25519_BYTES];
+  uint8_t x25519_prv[X25519_BYTES];
+  mg_random(x25519_prv, sizeof(x25519_prv));
+  mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1);
+  mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1);
+  mg_tls_hexdump("s x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec));
+
+  // fill in the gaps: random + session ID + keyshare
+  memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
+  memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id));
+  memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));
+
+  // server hello message
+  mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5);
+  mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello));
+  mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello));
+
+  // change cipher message
+  mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6);
+}
+
+static void mg_tls_server_send_ext(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  // server extensions
+  uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
+  mg_sha256_update(&tls->sha256, ext, sizeof(ext));
+  mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
+}
+
+static void mg_tls_server_send_cert(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  // server DER certificate (empty)
+  size_t n = tls->server_cert_der.len;
+  uint8_t *cert = (uint8_t *) calloc(1, 13 + n);
+  if (cert == NULL) {
+    mg_error(c, "tls cert oom");
+    return;
   }
-  return c;
-}
+  cert[0] = 0x0b;                                // handshake header
+  cert[1] = (uint8_t) (((n + 9) >> 16) & 255U);  // 3 bytes: payload length
+  cert[2] = (uint8_t) (((n + 9) >> 8) & 255U);
+  cert[3] = (uint8_t) ((n + 9) & 255U);
+  cert[4] = 0;                                   // request context
+  cert[5] = (uint8_t) (((n + 5) >> 16) & 255U);  // 3 bytes: cert (s) length
+  cert[6] = (uint8_t) (((n + 5) >> 8) & 255U);
+  cert[7] = (uint8_t) ((n + 5) & 255U);
+  cert[8] =
+      (uint8_t) (((n) >> 16) & 255U);  // 3 bytes: first (and only) cert len
+  cert[9] = (uint8_t) (((n) >> 8) & 255U);
+  cert[10] = (uint8_t) (n & 255U);
+  // bytes 11+ are certificate in DER format
+  memmove(cert + 11, tls->server_cert_der.buf, n);
+  cert[11 + n] = cert[12 + n] = 0;  // certificate extensions (none)
+  mg_sha256_update(&tls->sha256, cert, 13 + n);
+  mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE);
+  free(cert);
+}
+
+// type adapter between uECC hash context and our sha256 implementation
+typedef struct SHA256_HashContext {
+  MG_UECC_HashContext uECC;
+  mg_sha256_ctx ctx;
+} SHA256_HashContext;
+
+static void init_SHA256(const MG_UECC_HashContext *base) {
+  SHA256_HashContext *c = (SHA256_HashContext *) base;
+  mg_sha256_init(&c->ctx);
+}
+
+static void update_SHA256(const MG_UECC_HashContext *base,
+                          const uint8_t *message, unsigned message_size) {
+  SHA256_HashContext *c = (SHA256_HashContext *) base;
+  mg_sha256_update(&c->ctx, message, message_size);
+}
+static void finish_SHA256(const MG_UECC_HashContext *base,
+                          uint8_t *hash_result) {
+  SHA256_HashContext *c = (SHA256_HashContext *) base;
+  mg_sha256_final(hash_result, &c->ctx);
+}
+
+static void mg_tls_server_send_cert_verify(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  // server certificate verify packet
+  uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
+  size_t sigsz, verifysz = 0;
+  uint8_t hash[32] = {0}, tmp[2 * 32 + 64] = {0};
+  struct SHA256_HashContext ctx = {
+      {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
+      {{0}, 0, 0, {0}}};
+  int neg1, neg2;
+  uint8_t sig[64] = {0};
+
+  mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash);
+
+  mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash), &ctx.uECC,
+                             sig, mg_uecc_secp256r1());
+
+  neg1 = !!(sig[0] & 0x80);
+  neg2 = !!(sig[32] & 0x80);
+  verify[8] = 0x30;  // ASN.1 SEQUENCE
+  verify[9] = (uint8_t) (68 + neg1 + neg2);
+  verify[10] = 0x02;  // ASN.1 INTEGER
+  verify[11] = (uint8_t) (32 + neg1);
+  memmove(verify + 12 + neg1, sig, 32);
+  verify[12 + 32 + neg1] = 0x02;  // ASN.1 INTEGER
+  verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
+  memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);
+
+  sigsz = (size_t) (70 + neg1 + neg2);
+  verifysz = 8U + sigsz;
+  verify[3] = (uint8_t) (sigsz + 4);
+  verify[7] = (uint8_t) sigsz;
+
+  mg_sha256_update(&tls->sha256, verify, verifysz);
+  mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
+}
+
+static void mg_tls_server_send_finish(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *wio = &tls->send;
+  mg_sha256_ctx sha256;
+  uint8_t hash[32];
+  uint8_t finish[36] = {0x14, 0, 0, 32};
+  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
+  mg_sha256_final(hash, &sha256);
+  mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash, 32);
+  mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
+  mg_io_send(c, wio->buf, wio->len);
+  wio->len = 0;
+
+  mg_sha256_update(&tls->sha256, finish, sizeof(finish));
+}
+
+static int mg_tls_server_recv_finish(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  // we have to backup sha256 value to restore it later, since Finished record
+  // is exceptional and is not supposed to be added to the rolling hash
+  // calculation.
+  mg_sha256_ctx sha256 = tls->sha256;
+  if (mg_tls_recv_record(c) < 0) {
+    return -1;
+  }
+  if (tls->recv.buf[0] != MG_TLS_FINISHED) {
+    mg_error(c, "expected Finish but got msg 0x%02x", tls->recv.buf[0]);
+    return -1;
+  }
+  mg_tls_drop_message(c);
 
-struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
-                                     mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
-  return c;
+  // restore hash
+  tls->sha256 = sha256;
+  return 0;
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/net.c"
+static void mg_tls_client_send_hello(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *wio = &tls->send;
+
+  const char *hostname = tls->hostname;
+  size_t hostnamesz = strlen(tls->hostname);
+  uint8_t x25519_pub[X25519_BYTES];
+
+  uint8_t msg_client_hello[162 + 32] = {
+    // TLS Client Hello header reported as TLS1.2 (5)
+    0x16,
+    0x03,
+    0x01,
+    0x00,
+    0xfe,
+    // server hello, tls 1.2 (6)
+    0x01,
+    0x00,
+    0x00,
+    0x8c,
+    0x03,
+    0x03,
+    // random (32 bytes)
+    PLACEHOLDER_32B,
+    // session ID length + session ID (32 bytes)
+    0x20,
+    PLACEHOLDER_32B,
+#if defined(CHACHA20) && CHACHA20
+    // TLS_CHACHA20_POLY1305_SHA256 + no compression
+    0x13,
+    0x03,
+    0x00,
+#else
+    0x00,
+    0x02,  // size = 2 bytes
+    0x13,
+    0x01,  // TLS_AES_128_GCM_SHA256
+    0x01,
+    0x00,  // no compression
 #endif
 
+    // extensions + keyshare
+    0x00,
+    0xfe,
+    // x25519 keyshare
+    0x00,
+    0x33,
+    0x00,
+    0x26,
+    0x00,
+    0x24,
+    0x00,
+    0x1d,
+    0x00,
+    0x20,
+    PLACEHOLDER_32B,
+    // supported groups (x25519)
+    0x00,
+    0x0a,
+    0x00,
+    0x04,
+    0x00,
+    0x02,
+    0x00,
+    0x1d,
+    // supported versions (tls1.3 == 0x304)
+    0x00,
+    0x2b,
+    0x00,
+    0x03,
+    0x02,
+    0x03,
+    0x04,
+    // session ticket (none)
+    0x00,
+    0x23,
+    0x00,
+    0x00,
+    // signature algorithms (we don't care, so list all the common ones)
+    0x00,
+    0x0d,
+    0x00,
+    0x24,
+    0x00,
+    0x22,
+    0x04,
+    0x03,
+    0x05,
+    0x03,
+    0x06,
+    0x03,
+    0x08,
+    0x07,
+    0x08,
+    0x08,
+    0x08,
+    0x1a,
+    0x08,
+    0x1b,
+    0x08,
+    0x1c,
+    0x08,
+    0x09,
+    0x08,
+    0x0a,
+    0x08,
+    0x0b,
+    0x08,
+    0x04,
+    0x08,
+    0x05,
+    0x08,
+    0x06,
+    0x04,
+    0x01,
+    0x05,
+    0x01,
+    0x06,
+    0x01,
+    // server name
+    0x00,
+    0x00,
+    0x00,
+    0xfe,
+    0x00,
+    0xfe,
+    0x00,
+    0x00,
+    0xfe
+  };
 
+  // patch ClientHello with correct hostname length + offset:
+  MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189);
+  MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185);
+  MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110);
+  MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5);
+  MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3);
+  MG_STORE_BE16(msg_client_hello + 192, hostnamesz);
+
+  // calculate keyshare
+  mg_random(tls->x25519_cli, sizeof(tls->x25519_cli));
+  mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);
+
+  // fill in the gaps: random + session ID + keyshare
+  mg_random(tls->session_id, sizeof(tls->session_id));
+  mg_random(tls->random, sizeof(tls->random));
+  memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
+  memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
+  memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));
+
+  // server hello message
+  mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello));
+  mg_iobuf_add(wio, wio->len, hostname, strlen(hostname));
+  mg_sha256_update(&tls->sha256, msg_client_hello + 5,
+                   sizeof(msg_client_hello) - 5);
+  mg_sha256_update(&tls->sha256, (uint8_t *) hostname, strlen(hostname));
+
+  // change cipher message
+  mg_iobuf_add(wio, wio->len, (const char *) "\x14\x03\x03\x00\x01\x01", 6);
+  mg_io_send(c, wio->buf, wio->len);
+  wio->len = 0;
+}
+
+static int mg_tls_client_recv_hello(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *rio = &c->rtls;
+  uint16_t msgsz;
+  uint8_t *ext;
+  uint16_t ext_len;
+  int j;
+
+  if (!mg_tls_got_record(c)) {
+    return MG_IO_WAIT;
+  }
+  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_SERVER_HELLO) {
+    if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
+      mg_error(c, "tls alert %d", rio->buf[6]);
+      return -1;
+    }
+    MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0], rio->buf[5]));
+    mg_error(c, "not a server hello packet");
+    return -1;
+  }
 
-
-
-
-
-size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) {
-  size_t old = c->send.len;
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  return c->send.len - old;
-}
-
-size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
-  size_t len = 0;
-  va_list ap;
-  va_start(ap, fmt);
-  len = mg_vprintf(c, fmt, &ap);
-  va_end(ap);
-  return len;
-}
-
-static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
-  if (mg_vcasecmp(&str, "localhost") != 0) return false;
-  addr->ip = mg_htonl(0x7f000001);
-  addr->is_ip6 = false;
-  return true;
+  msgsz = MG_LOAD_BE16(rio->buf + 3);
+  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
+
+  ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
+  ext = rio->buf + 5 + 39 + 32 + 3 + 2;
+
+  for (j = 0; j < ext_len;) {
+    uint16_t ext_type = MG_LOAD_BE16(ext + j);
+    uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2);
+    uint16_t group;
+    uint8_t *key_exchange;
+    uint16_t key_exchange_len;
+    if (ext_type != 0x0033) {  // not a key share extension, ignore
+      j += (uint16_t) (ext_len2 + 4);
+      continue;
+    }
+    group = MG_LOAD_BE16(ext + j + 4);
+    if (group != 0x001d) {
+      mg_error(c, "bad key exchange group");
+      return -1;
+    }
+    key_exchange_len = MG_LOAD_BE16(ext + j + 6);
+    key_exchange = ext + j + 8;
+    if (key_exchange_len != 32) {
+      mg_error(c, "bad key exchange length");
+      return -1;
+    }
+    mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1);
+    mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
+    mg_tls_drop_record(c);
+    /* generate handshake keys */
+    mg_tls_generate_handshake_keys(c);
+    return 0;
+  }
+  mg_error(c, "bad client hello");
+  return -1;
 }
 
-static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
-  if (str.len > 0) return false;
-  addr->ip = 0;
-  addr->is_ip6 = false;
-  return true;
+static int mg_tls_client_recv_ext(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (mg_tls_recv_record(c) < 0) {
+    return -1;
+  }
+  if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
+    mg_error(c, "expected server extensions but got msg 0x%02x",
+             tls->recv.buf[0]);
+    return -1;
+  }
+  mg_tls_drop_message(c);
+  return 0;
 }
 
-static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
-  uint8_t data[4] = {0, 0, 0, 0};
-  size_t i, num_dots = 0;
-  for (i = 0; i < str.len; i++) {
-    if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-      int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
-      if (octet > 255) return false;
-      data[num_dots] = (uint8_t) octet;
-    } else if (str.ptr[i] == '.') {
-      if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false;
-      num_dots++;
-    } else {
-      return false;
-    }
+static int mg_tls_client_recv_cert(struct mg_connection *c) {
+  uint8_t *cert;
+  uint32_t certsz;
+  struct mg_der_tlv oid, pubkey, seq, subj;
+  int subj_match = 0;
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (mg_tls_recv_record(c) < 0) {
+    return -1;
+  }
+  if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) {
+    mg_error(c, "expected server certificate but got msg 0x%02x",
+             tls->recv.buf[0]);
+    return -1;
+  }
+  if (tls->skip_verification) {
+    mg_tls_drop_message(c);
+    return 0;
   }
-  if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
-  memcpy(&addr->ip, data, sizeof(data));
-  addr->is_ip6 = false;
-  return true;
-}
 
-static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
-  int i;
-  if (str.len < 14) return false;
-  if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false;
-  for (i = 2; i < 6; i++) {
-    if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
+  if (tls->recv.len < 11) {
+    mg_error(c, "certificate list too short");
+    return -1;
   }
-  if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false;
-  memset(addr->ip6, 0, sizeof(addr->ip6));
-  addr->ip6[10] = addr->ip6[11] = 255;
-  memcpy(&addr->ip6[12], &addr->ip, 4);
-  addr->is_ip6 = true;
-  return true;
-}
 
-static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
-  size_t i, j = 0, n = 0, dc = 42;
-  if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
-  if (mg_v4mapped(str, addr)) return true;
-  for (i = 0; i < str.len; i++) {
-    if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
-        (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
-        (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
-      unsigned long val;
-      if (i > j + 3) return false;
-      // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j]));
-      val = mg_unhexn(&str.ptr[j], i - j + 1);
-      addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
-      addr->ip6[n + 1] = (uint8_t) (val & 255);
-    } else if (str.ptr[i] == ':') {
-      j = i + 1;
-      if (i > 0 && str.ptr[i - 1] == ':') {
-        dc = n;  // Double colon
-        if (i > 1 && str.ptr[i - 2] == ':') return false;
-      } else if (i > 0) {
-        n += 2;
+  cert = tls->recv.buf + 11;
+  certsz = MG_LOAD_BE24(tls->recv.buf + 8);
+  if (certsz > tls->recv.len - 11) {
+    mg_error(c, "certificate too long: %d vs %d", certsz, tls->recv.len - 11);
+    return -1;
+  }
+
+  do {
+    // secp256r1 public key
+    if (mg_der_find(cert, certsz,
+                    (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8,
+                    &oid) < 0) {
+      mg_error(c, "certificate secp256r1 public key OID not found");
+      return -1;
+    }
+    if (mg_der_to_tlv(oid.value + oid.len,
+                      (size_t) (cert + certsz - (oid.value + oid.len)),
+                      &pubkey) < 0) {
+      mg_error(c, "certificate secp256r1 public key not found");
+      return -1;
+    }
+
+    // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32 content bytes
+    if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] != 0 ||
+        pubkey.value[1] != 4) {
+      mg_error(c, "unsupported public key bitstring encoding");
+      return -1;
+    }
+    memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2);
+  } while (0);
+
+  // Subject Alternative Names
+  do {
+    if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3, &oid) < 0) {
+      mg_error(c, "certificate does not contain subject alternative names");
+      return -1;
+    }
+    if (mg_der_to_tlv(oid.value + oid.len,
+                      (size_t) (cert + certsz - (oid.value + oid.len)),
+                      &seq) < 0) {
+      mg_error(c, "certificate subject alternative names not found");
+      return -1;
+    }
+    if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) {
+      mg_error(
+          c,
+          "certificate subject alternative names is not a constructed object");
+      return -1;
+    }
+    MG_VERBOSE(("verify hostname %s", tls->hostname));
+    while (seq.len > 0) {
+      if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) {
+        mg_error(c, "bad subject alternative name");
+        return -1;
       }
-      if (n > 14) return false;
-      addr->ip6[n] = addr->ip6[n + 1] = 0;  // For trailing ::
-    } else {
-      return false;
+      MG_VERBOSE(("subj=%.*s", subj.len, subj.value));
+      if (mg_match(mg_str((const char *) tls->hostname),
+                   mg_str_n((const char *) subj.value, subj.len), NULL)) {
+        subj_match = 1;
+        break;
+      }
+      seq.len = (uint32_t) (seq.value + seq.len - (subj.value + subj.len));
+      seq.value = subj.value + subj.len;
+    }
+    if (!subj_match) {
+      mg_error(c, "certificate did not match the hostname");
+      return -1;
     }
+  } while (0);
+
+  mg_tls_drop_message(c);
+  mg_tls_calc_cert_verify_hash(c, tls->sighash);
+  return 0;
+}
+
+static int mg_tls_client_recv_cert_verify(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (mg_tls_recv_record(c) < 0) {
+    return -1;
   }
-  if (n < 14 && dc == 42) return false;
-  if (n < 14) {
-    memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2);
-    memset(&addr->ip6[dc], 0, 14 - n);
+  if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
+    mg_error(c, "expected server certificate verify but got msg 0x%02x",
+             tls->recv.buf[0]);
+    return -1;
+  }
+  // Ignore CertificateVerify is strict checks are not required
+  if (tls->skip_verification) {
+    mg_tls_drop_message(c);
+    return 0;
   }
-  addr->is_ip6 = true;
-  return true;
-}
 
-bool mg_aton(struct mg_str str, struct mg_addr *addr) {
-  // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
-  return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
-         mg_aton6(str, addr);
+  // Extract certificate signature and verify it using pubkey and sighash
+  do {
+    uint8_t sig[64];
+    struct mg_der_tlv seq, a, b;
+    if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) < 0) {
+      mg_error(c, "verification message is not an ASN.1 DER sequence");
+      return -1;
+    }
+    if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) {
+      mg_error(c, "missing first part of the signature");
+      return -1;
+    }
+    if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) {
+      mg_error(c, "missing second part of the signature");
+      return -1;
+    }
+    // Integers may be padded with zeroes
+    if (a.len > 32) {
+      a.value = a.value + (a.len - 32);
+      a.len = 32;
+    }
+    if (b.len > 32) {
+      b.value = b.value + (b.len - 32);
+      b.len = 32;
+    }
+
+    memmove(sig, a.value, a.len);
+    memmove(sig + 32, b.value, b.len);
+
+    if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig,
+                       mg_uecc_secp256r1()) != 1) {
+      mg_error(c, "failed to verify certificate");
+      return -1;
+    }
+  } while (0);
+
+  mg_tls_drop_message(c);
+  return 0;
 }
 
-struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
-  struct mg_connection *c =
-      (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
-  if (c != NULL) {
-    c->mgr = mgr;
-    c->send.align = c->recv.align = MG_IO_SIZE;
-    c->id = ++mgr->nextid;
+static int mg_tls_client_recv_finish(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (mg_tls_recv_record(c) < 0) {
+    return -1;
   }
-  return c;
+  if (tls->recv.buf[0] != MG_TLS_FINISHED) {
+    mg_error(c, "expected server finished but got msg 0x%02x",
+             tls->recv.buf[0]);
+    return -1;
+  }
+  mg_tls_drop_message(c);
+  return 0;
 }
 
-void mg_close_conn(struct mg_connection *c) {
-  mg_resolve_cancel(c);  // Close any pending DNS query
-  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
-  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
-  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
-  // Order of operations is important. `MG_EV_CLOSE` event must be fired
-  // before we deallocate received data, see #1331
-  mg_call(c, MG_EV_CLOSE, NULL);
-  MG_DEBUG(("%lu %p closed", c->id, c->fd));
-
-  mg_tls_free(c);
-  mg_iobuf_free(&c->recv);
-  mg_iobuf_free(&c->send);
-  memset(c, 0, sizeof(*c));
-  free(c);
+static void mg_tls_client_send_finish(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  struct mg_iobuf *wio = &tls->send;
+  mg_sha256_ctx sha256;
+  uint8_t hash[32];
+  uint8_t finish[36] = {0x14, 0, 0, 32};
+  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
+  mg_sha256_final(hash, &sha256);
+  mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash, 32);
+  mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
+  mg_io_send(c, wio->buf, wio->len);
+  wio->len = 0;
+}
+
+static void mg_tls_client_handshake(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  switch (tls->state) {
+    case MG_TLS_STATE_CLIENT_START:
+      mg_tls_client_send_hello(c);
+      tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
+      // Fallthrough
+    case MG_TLS_STATE_CLIENT_WAIT_SH:
+      if (mg_tls_client_recv_hello(c) < 0) {
+        break;
+      }
+      tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
+      // Fallthrough
+    case MG_TLS_STATE_CLIENT_WAIT_EE:
+      if (mg_tls_client_recv_ext(c) < 0) {
+        break;
+      }
+      tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
+      // Fallthrough
+    case MG_TLS_STATE_CLIENT_WAIT_CERT:
+      if (mg_tls_client_recv_cert(c) < 0) {
+        break;
+      }
+      tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
+      // Fallthrough
+    case MG_TLS_STATE_CLIENT_WAIT_CV:
+      if (mg_tls_client_recv_cert_verify(c) < 0) {
+        break;
+      }
+      tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED;
+      // Fallthrough
+    case MG_TLS_STATE_CLIENT_WAIT_FINISHED:
+      if (mg_tls_client_recv_finish(c) < 0) {
+        break;
+      }
+      mg_tls_client_send_finish(c);
+      mg_tls_generate_application_keys(c);
+      tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
+      c->is_tls_hs = 0;
+      break;
+    default: mg_error(c, "unexpected client state: %d", tls->state); break;
+  }
 }
 
-struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
-                                 mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = NULL;
-  if (url == NULL || url[0] == '\0') {
-    MG_ERROR(("null url"));
-  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
-    MG_ERROR(("OOM"));
-  } else {
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->is_udp = (strncmp(url, "udp:", 4) == 0);
-    c->fd = (void *) (size_t) MG_INVALID_SOCKET;
-    c->fn = fn;
-    c->is_client = true;
-    c->fn_data = fn_data;
-    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
-    mg_call(c, MG_EV_OPEN, NULL);
-    mg_resolve(c, url);
+static void mg_tls_server_handshake(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  switch (tls->state) {
+    case MG_TLS_STATE_SERVER_START:
+      if (mg_tls_server_recv_hello(c) < 0) {
+        return;
+      }
+      mg_tls_server_send_hello(c);
+      mg_tls_generate_handshake_keys(c);
+      mg_tls_server_send_ext(c);
+      mg_tls_server_send_cert(c);
+      mg_tls_server_send_cert_verify(c);
+      mg_tls_server_send_finish(c);
+      tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
+      // fallthrough
+    case MG_TLS_STATE_SERVER_NEGOTIATED:
+      if (mg_tls_server_recv_finish(c) < 0) {
+        return;
+      }
+      mg_tls_generate_application_keys(c);
+      tls->state = MG_TLS_STATE_SERVER_CONNECTED;
+      c->is_tls_hs = 0;
+      return;
+    default: mg_error(c, "unexpected server state: %d", tls->state); break;
   }
-  return c;
 }
 
-struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
-                                mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = NULL;
-  if ((c = mg_alloc_conn(mgr)) == NULL) {
-    MG_ERROR(("OOM %s", url));
-  } else if (!mg_open_listener(c, url)) {
-    MG_ERROR(("Failed: %s, errno %d", url, errno));
-    free(c);
-    c = NULL;
+void mg_tls_handshake(struct mg_connection *c) {
+  if (c->is_client) {
+    mg_tls_client_handshake(c);
   } else {
-    c->is_listening = 1;
-    c->is_udp = strncmp(url, "udp:", 4) == 0;
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->fn = fn;
-    c->fn_data = fn_data;
-    mg_call(c, MG_EV_OPEN, NULL);
-    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
+    mg_tls_server_handshake(c);
   }
-  return c;
 }
 
-struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
-                                mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_alloc_conn(mgr);
-  if (c != NULL) {
-    c->fd = (void *) (size_t) fd;
-    c->fn = fn;
-    c->fn_data = fn_data;
-    MG_EPOLL_ADD(c);
-    mg_call(c, MG_EV_OPEN, NULL);
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+static int mg_parse_pem(const struct mg_str pem, const struct mg_str label,
+                        struct mg_str *der) {
+  size_t n = 0, m = 0;
+  char *s;
+  const char *c;
+  struct mg_str caps[5];
+  if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END #-----#"), caps)) {
+    der->buf = mg_mprintf("%.*s", pem.len, pem.buf);
+    der->len = pem.len;
+    return 0;
+  }
+  if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label) != 0) {
+    return -1;  // bad label
+  }
+  if ((s = (char *) calloc(1, caps[2].len)) == NULL) {
+    return -1;
   }
-  return c;
-}
 
-struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
-                              unsigned flags, void (*fn)(void *), void *arg) {
-  struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
-  if (t != NULL) {
-    mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
-    t->id = mgr->timerid++;
+  for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
+    if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
+      continue;
+    }
+    s[n++] = *c;
   }
-  return t;
+  m = mg_base64_decode(s, n, s, n);
+  if (m == 0) {
+    free(s);
+    return -1;
+  }
+  der->buf = s;
+  der->len = m;
+  return 0;
 }
 
-void mg_mgr_free(struct mg_mgr *mgr) {
-  struct mg_connection *c;
-  struct mg_timer *tmp, *t = mgr->timers;
-  while (t != NULL) tmp = t->next, free(t), t = tmp;
-  mgr->timers = NULL;  // Important. Next call to poll won't touch timers
-  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
-  mg_mgr_poll(mgr, 0);
-#if MG_ENABLE_FREERTOS_TCP
-  FreeRTOS_DeleteSocketSet(mgr->ss);
-#endif
-  MG_DEBUG(("All connections closed"));
-#if MG_ENABLE_EPOLL
-  if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
-#endif
-}
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
+  struct mg_str key;
+  struct tls_data *tls = (struct tls_data *) calloc(1, sizeof(struct tls_data));
+  if (tls == NULL) {
+    mg_error(c, "tls oom");
+    return;
+  }
 
-void mg_mgr_init(struct mg_mgr *mgr) {
-  memset(mgr, 0, sizeof(*mgr));
-#if MG_ENABLE_EPOLL
-  if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll: %d", errno));
-#else
-  mgr->epoll_fd = -1;
-#endif
-#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
-  // clang-format off
-  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
-  // clang-format on
-#elif MG_ENABLE_FREERTOS_TCP
-  mgr->ss = FreeRTOS_CreateSocketSet();
-#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
-  // Ignore SIGPIPE signal, so if client cancels the request, it
-  // won't kill the whole process.
-  signal(SIGPIPE, SIG_IGN);
-#endif
-  mgr->dnstimeout = 3000;
-  mgr->dns4.url = "udp://8.8.8.8:53";
-  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
-}
+  tls->state =
+      c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/rpc.c"
-#endif
+  tls->skip_verification = opts->skip_verification;
+  tls->send.align = MG_IO_SIZE;
 
+  c->tls = tls;
+  c->is_tls = c->is_tls_hs = 1;
+  mg_sha256_init(&tls->sha256);
 
-void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
-                void (*fn)(struct mg_rpc_req *), void *fn_data) {
-  struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
-  if (rpc != NULL) {
-    rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data;
-    rpc->next = *head, *head = rpc;
+  // save hostname (client extension)
+  if (opts->name.len > 0) {
+    if (opts->name.len >= sizeof(tls->hostname) - 1) {
+      mg_error(c, "hostname too long");
+    }
+    strncpy((char *) tls->hostname, opts->name.buf, sizeof(tls->hostname) - 1);
+    tls->hostname[opts->name.len] = 0;
   }
-}
 
-void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
-  struct mg_rpc *r;
-  while ((r = *head) != NULL) {
-    if (r->fn == fn || fn == NULL) {
-      *head = r->next;
-      free((void *) r->method.ptr);
-      free(r);
-    } else {
-      head = &(*head)->next;
-    }
+  if (c->is_client) {
+    tls->server_cert_der.buf = NULL;
+    return;
   }
-}
 
-static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
-  struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
-  while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
-  if (h != NULL) {
-    r->rpc = h;
-    h->fn(r);
+  // parse PEM or DER certificate
+  if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"), &tls->server_cert_der) <
+      0) {
+    MG_ERROR(("Failed to load certificate"));
+    return;
+  }
+
+  // parse PEM or DER EC key
+  if (opts->key.buf == NULL) {
+    mg_error(c, "certificate provided without a private key");
+    return;
+  }
+
+  if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) == 0) {
+    if (key.len < 39) {
+      MG_ERROR(("EC private key too short"));
+      return;
+    }
+    // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
+    // 30 nn 02 01 01 04 20 [key] ...
+    if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) {
+      MG_ERROR(("EC private key: ASN.1 bad sequence"));
+      return;
+    }
+    if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
+      MG_ERROR(("EC private key: ASN.1 bad data"));
+    }
+    memmove(tls->server_key, key.buf + 7, 32);
+    free((void *) key.buf);
+  } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) {
+    mg_error(c, "PKCS8 private key format is not supported");
   } else {
-    mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr);
+    mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY");
   }
 }
 
-void mg_rpc_process(struct mg_rpc_req *r) {
-  int len, off = mg_json_get(r->frame, "$.method", &len);
-  if (off > 0 && r->frame.ptr[off] == '"') {
-    struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2);
-    mg_rpc_call(r, method);
-  } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
-             (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
-    mg_rpc_call(r, mg_str(""));  // JSON response! call "" method handler
-  } else {
-    mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len, r->frame.ptr);  // Invalid
+void mg_tls_free(struct mg_connection *c) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  if (tls != NULL) {
+    mg_iobuf_free(&tls->send);
+    free((void *) tls->server_cert_der.buf);
   }
+  free(c->tls);
+  c->tls = NULL;
 }
 
-void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
-  int len, off = mg_json_get(r->frame, "$.id", &len);
-  if (off > 0) {
-    mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len,
-               &r->frame.ptr[off], "result");
-    mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
-    mg_xprintf(r->pfn, r->pfn_data, "}");
+long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  long n = MG_IO_WAIT;
+  if (len > MG_IO_SIZE) len = MG_IO_SIZE;
+  mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA);
+  while (tls->send.len > 0 &&
+         (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
+    mg_iobuf_del(&tls->send, 0, (size_t) n);
   }
+  if (n == MG_IO_ERR || n == MG_IO_WAIT) return n;
+  return (long) len;
 }
 
-void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_rpc_vok(r, fmt, &ap);
-  va_end(ap);
-}
+long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
+  int r = 0;
+  struct tls_data *tls = (struct tls_data *) c->tls;
+  size_t minlen;
 
-void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) {
-  int len, off = mg_json_get(r->frame, "$.id", &len);
-  mg_xprintf(r->pfn, r->pfn_data, "{");
-  if (off > 0) {
-    mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len, &r->frame.ptr[off]);
+  r = mg_tls_recv_record(c);
+  if (r < 0) {
+    return r;
   }
-  mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error", "code", code,
-             "message");
-  mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
-  mg_xprintf(r->pfn, r->pfn_data, "}}");
+  if (tls->content_type != MG_TLS_APP_DATA) {
+    tls->recv.len = 0;
+    mg_tls_drop_record(c);
+    return MG_IO_WAIT;
+  }
+  minlen = len < tls->recv.len ? len : tls->recv.len;
+  memmove(buf, tls->recv.buf, minlen);
+  tls->recv.buf += minlen;
+  tls->recv.len -= minlen;
+  if (tls->recv.len == 0) {
+    mg_tls_drop_record(c);
+  }
+  return (long) minlen;
 }
 
-void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_rpc_verr(r, code, fmt, &ap);
-  va_end(ap);
+size_t mg_tls_pending(struct mg_connection *c) {
+  return mg_tls_got_record(c) ? 1 : 0;
 }
 
-static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
-  struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
-  size_t len = 0;
-  for (h = *head; h != NULL; h = h->next) {
-    if (h->method.len == 0) continue;  // Ignore response handler
-    len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",",
-                      (int) h->method.len, h->method.ptr);
-  }
-  return len;
+void mg_tls_ctx_init(struct mg_mgr *mgr) {
+  (void) mgr;
 }
 
-void mg_rpc_list(struct mg_rpc_req *r) {
-  mg_rpc_ok(r, "[%M]", print_methods, r->head);
+void mg_tls_ctx_free(struct mg_mgr *mgr) {
+  (void) mgr;
 }
+#endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/sha1.c"
+#line 1 "src/tls_dummy.c"
+#endif
+
+
+#if MG_TLS == MG_TLS_NONE
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
+  (void) opts;
+  mg_error(c, "TLS is not enabled");
+}
+void mg_tls_handshake(struct mg_connection *c) {
+  (void) c;
+}
+void mg_tls_free(struct mg_connection *c) {
+  (void) c;
+}
+long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
+  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
+}
+long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
+  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
+}
+size_t mg_tls_pending(struct mg_connection *c) {
+  (void) c;
+  return 0;
+}
+void mg_tls_ctx_init(struct mg_mgr *mgr) {
+  (void) mgr;
+}
+void mg_tls_ctx_free(struct mg_mgr *mgr) {
+  (void) mgr;
+}
 #endif
-/* Copyright(c) By Steve Reid <steve@edmweb.com> */
-/* 100% Public Domain */
 
+#ifdef MG_ENABLE_LINES
+#line 1 "src/tls_mbed.c"
+#endif
 
 
-union char64long16 {
-  unsigned char c[64];
-  uint32_t l[16];
-};
 
-#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+#if MG_TLS == MG_TLS_MBED
 
-static uint32_t blk0(union char64long16 *block, int i) {
-  if (MG_BIG_ENDIAN) {
-  } else {
-    block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
-                  (rol(block->l[i], 8) & 0x00FF00FF);
+#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
+#define MG_MBEDTLS_RNG_GET , mg_mbed_rng, NULL
+#else
+#define MG_MBEDTLS_RNG_GET
+#endif
+
+static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) {
+  mg_random(buf, len);
+  (void) ctx;
+  return 0;
+}
+
+static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) {
+  int rc;
+  if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true;
+  if (str.buf[0] == '-') str.len++;  // PEM, include trailing NUL
+  if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf, str.len)) != 0) {
+    MG_ERROR(("cert err %#x", -rc));
+    return false;
   }
-  return block->l[i];
+  return true;
 }
 
-/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
-#undef blk
-#undef R0
-#undef R1
-#undef R2
-#undef R3
-#undef R4
+static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) {
+  int rc;
+  if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true;
+  if (str.buf[0] == '-') str.len++;  // PEM, include trailing NUL
+  if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len, NULL,
+                                 0 MG_MBEDTLS_RNG_GET)) != 0) {
+    MG_ERROR(("key err %#x", -rc));
+    return false;
+  }
+  return true;
+}
 
-#define blk(i)                                                               \
-  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
-                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
-                          1))
-#define R0(v, w, x, y, z, i)                                          \
-  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
-  w = rol(w, 30);
-#define R1(v, w, x, y, z, i)                                  \
-  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
-  w = rol(w, 30);
-#define R2(v, w, x, y, z, i)                          \
-  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
-  w = rol(w, 30);
-#define R3(v, w, x, y, z, i)                                        \
-  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
-  w = rol(w, 30);
-#define R4(v, w, x, y, z, i)                          \
-  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
-  w = rol(w, 30);
+void mg_tls_free(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  if (tls != NULL) {
+    mbedtls_ssl_free(&tls->ssl);
+    mbedtls_pk_free(&tls->pk);
+    mbedtls_x509_crt_free(&tls->ca);
+    mbedtls_x509_crt_free(&tls->cert);
+    mbedtls_ssl_config_free(&tls->conf);
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+    mbedtls_ssl_ticket_free(&tls->ticket);
+#endif
+    free(tls);
+    c->tls = NULL;
+  }
+}
 
-static void mg_sha1_transform(uint32_t state[5],
-                              const unsigned char buffer[64]) {
-  uint32_t a, b, c, d, e;
-  union char64long16 block[1];
+static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
+  long n = mg_io_send((struct mg_connection *) ctx, buf, len);
+  MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *) ctx)->id, n, errno));
+  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
+  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
+  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
+  return (int) n;
+}
 
-  memcpy(block, buffer, 64);
-  a = state[0];
-  b = state[1];
-  c = state[2];
-  d = state[3];
-  e = state[4];
-  R0(a, b, c, d, e, 0);
-  R0(e, a, b, c, d, 1);
-  R0(d, e, a, b, c, 2);
-  R0(c, d, e, a, b, 3);
-  R0(b, c, d, e, a, 4);
-  R0(a, b, c, d, e, 5);
-  R0(e, a, b, c, d, 6);
-  R0(d, e, a, b, c, 7);
-  R0(c, d, e, a, b, 8);
-  R0(b, c, d, e, a, 9);
-  R0(a, b, c, d, e, 10);
-  R0(e, a, b, c, d, 11);
-  R0(d, e, a, b, c, 12);
-  R0(c, d, e, a, b, 13);
-  R0(b, c, d, e, a, 14);
-  R0(a, b, c, d, e, 15);
-  R1(e, a, b, c, d, 16);
-  R1(d, e, a, b, c, 17);
-  R1(c, d, e, a, b, 18);
-  R1(b, c, d, e, a, 19);
-  R2(a, b, c, d, e, 20);
-  R2(e, a, b, c, d, 21);
-  R2(d, e, a, b, c, 22);
-  R2(c, d, e, a, b, 23);
-  R2(b, c, d, e, a, 24);
-  R2(a, b, c, d, e, 25);
-  R2(e, a, b, c, d, 26);
-  R2(d, e, a, b, c, 27);
-  R2(c, d, e, a, b, 28);
-  R2(b, c, d, e, a, 29);
-  R2(a, b, c, d, e, 30);
-  R2(e, a, b, c, d, 31);
-  R2(d, e, a, b, c, 32);
-  R2(c, d, e, a, b, 33);
-  R2(b, c, d, e, a, 34);
-  R2(a, b, c, d, e, 35);
-  R2(e, a, b, c, d, 36);
-  R2(d, e, a, b, c, 37);
-  R2(c, d, e, a, b, 38);
-  R2(b, c, d, e, a, 39);
-  R3(a, b, c, d, e, 40);
-  R3(e, a, b, c, d, 41);
-  R3(d, e, a, b, c, 42);
-  R3(c, d, e, a, b, 43);
-  R3(b, c, d, e, a, 44);
-  R3(a, b, c, d, e, 45);
-  R3(e, a, b, c, d, 46);
-  R3(d, e, a, b, c, 47);
-  R3(c, d, e, a, b, 48);
-  R3(b, c, d, e, a, 49);
-  R3(a, b, c, d, e, 50);
-  R3(e, a, b, c, d, 51);
-  R3(d, e, a, b, c, 52);
-  R3(c, d, e, a, b, 53);
-  R3(b, c, d, e, a, 54);
-  R3(a, b, c, d, e, 55);
-  R3(e, a, b, c, d, 56);
-  R3(d, e, a, b, c, 57);
-  R3(c, d, e, a, b, 58);
-  R3(b, c, d, e, a, 59);
-  R4(a, b, c, d, e, 60);
-  R4(e, a, b, c, d, 61);
-  R4(d, e, a, b, c, 62);
-  R4(c, d, e, a, b, 63);
-  R4(b, c, d, e, a, 64);
-  R4(a, b, c, d, e, 65);
-  R4(e, a, b, c, d, 66);
-  R4(d, e, a, b, c, 67);
-  R4(c, d, e, a, b, 68);
-  R4(b, c, d, e, a, 69);
-  R4(a, b, c, d, e, 70);
-  R4(e, a, b, c, d, 71);
-  R4(d, e, a, b, c, 72);
-  R4(c, d, e, a, b, 73);
-  R4(b, c, d, e, a, 74);
-  R4(a, b, c, d, e, 75);
-  R4(e, a, b, c, d, 76);
-  R4(d, e, a, b, c, 77);
-  R4(c, d, e, a, b, 78);
-  R4(b, c, d, e, a, 79);
-  state[0] += a;
-  state[1] += b;
-  state[2] += c;
-  state[3] += d;
-  state[4] += e;
-  /* Erase working structures. The order of operations is important,
-   * used to ensure that compiler doesn't optimize those out. */
-  memset(block, 0, sizeof(block));
-  a = b = c = d = e = 0;
-  (void) a;
-  (void) b;
-  (void) c;
-  (void) d;
-  (void) e;
+static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
+  long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
+  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
+  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
+  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
+  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
+  return (int) n;
+}
+
+void mg_tls_handshake(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  int rc = mbedtls_ssl_handshake(&tls->ssl);
+  if (rc == 0) {  // Success
+    MG_DEBUG(("%lu success", c->id));
+    c->is_tls_hs = 0;
+    mg_call(c, MG_EV_TLS_HS, NULL);
+  } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
+             rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
+    MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
+                c->is_tls_hs, rc, -rc));
+  } else {
+    mg_error(c, "TLS handshake: -%#x", -rc);  // Error
+  }
 }
 
-void mg_sha1_init(mg_sha1_ctx *context) {
-  context->state[0] = 0x67452301;
-  context->state[1] = 0xEFCDAB89;
-  context->state[2] = 0x98BADCFE;
-  context->state[3] = 0x10325476;
-  context->state[4] = 0xC3D2E1F0;
-  context->count[0] = context->count[1] = 0;
+static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
+  n = (int) strlen(s2) - 1;
+  MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
+  (void) s;
 }
 
-void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
-                    size_t len) {
-  size_t i, j;
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
+  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
+  int rc = 0;
+  c->tls = tls;
+  if (c->tls == NULL) {
+    mg_error(c, "TLS OOM");
+    goto fail;
+  }
+  if (c->is_listening) goto fail;
+  MG_DEBUG(("%lu Setting TLS", c->id));
+  MG_PROF_ADD(c, "mbedtls_init_start");
+#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 && \
+    defined(MBEDTLS_PSA_CRYPTO_C)
+  psa_crypto_init();  // https://github.com/Mbed-TLS/mbedtls/issues/9072#issuecomment-2084845711
+#endif
+  mbedtls_ssl_init(&tls->ssl);
+  mbedtls_ssl_config_init(&tls->conf);
+  mbedtls_x509_crt_init(&tls->ca);
+  mbedtls_x509_crt_init(&tls->cert);
+  mbedtls_pk_init(&tls->pk);
+  mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
+#if defined(MG_MBEDTLS_DEBUG_LEVEL)
+  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
+#endif
+  if ((rc = mbedtls_ssl_config_defaults(
+           &tls->conf,
+           c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
+           MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
+    mg_error(c, "tls defaults %#x", -rc);
+    goto fail;
+  }
+  mbedtls_ssl_conf_rng(&tls->conf, mg_mbed_rng, c);
 
-  j = context->count[0];
-  if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
-  context->count[1] += (uint32_t) (len >> 29);
-  j = (j >> 3) & 63;
-  if ((j + len) > 63) {
-    memcpy(&context->buffer[j], data, (i = 64 - j));
-    mg_sha1_transform(context->state, context->buffer);
-    for (; i + 63 < len; i += 64) {
-      mg_sha1_transform(context->state, &data[i]);
+  if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
+    // NOTE: MBEDTLS_SSL_VERIFY_NONE is not supported for TLS1.3 on client side
+    // See https://github.com/Mbed-TLS/mbedtls/issues/7075
+    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
+  } else {
+    if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail;
+    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
+    if (c->is_client && opts->name.buf != NULL && opts->name.buf[0] != '\0') {
+      char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf);
+      mbedtls_ssl_set_hostname(&tls->ssl, host);
+      MG_DEBUG(("%lu hostname verification: %s", c->id, host));
+      free(host);
     }
-    j = 0;
-  } else
-    i = 0;
-  memcpy(&context->buffer[j], &data[i], len - i);
+    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
+  }
+  if (!mg_load_cert(opts->cert, &tls->cert)) goto fail;
+  if (!mg_load_key(opts->key, &tls->pk)) goto fail;
+  if (tls->cert.version &&
+      (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk)) != 0) {
+    mg_error(c, "own cert %#x", -rc);
+    goto fail;
+  }
+
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+  mbedtls_ssl_conf_session_tickets_cb(
+      &tls->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
+      &((struct mg_tls_ctx *) c->mgr->tls_ctx)->tickets);
+#endif
+
+  if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
+    mg_error(c, "setup err %#x", -rc);
+    goto fail;
+  }
+  c->is_tls = 1;
+  c->is_tls_hs = 1;
+  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
+  MG_PROF_ADD(c, "mbedtls_init_end");
+  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
+    mg_tls_handshake(c);
+  }
+  return;
+fail:
+  mg_tls_free(c);
 }
 
-void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
-  unsigned i;
-  unsigned char finalcount[8], c;
+size_t mg_tls_pending(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
+}
 
-  for (i = 0; i < 8; i++) {
-    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
-                                      ((3 - (i & 3)) * 8)) &
-                                     255);
+long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
+  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
+    return MG_IO_WAIT;
+#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
+  if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) {
+    return MG_IO_WAIT;
   }
-  c = 0200;
-  mg_sha1_update(context, &c, 1);
-  while ((context->count[0] & 504) != 448) {
-    c = 0000;
-    mg_sha1_update(context, &c, 1);
+#endif
+  if (n <= 0) return MG_IO_ERR;
+  return n;
+}
+
+long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
+  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
+    return MG_IO_WAIT;
+  if (n <= 0) return MG_IO_ERR;
+  return n;
+}
+
+void mg_tls_ctx_init(struct mg_mgr *mgr) {
+  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1, sizeof(*ctx));
+  if (ctx == NULL) {
+    MG_ERROR(("TLS context init OOM"));
+  } else {
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+    int rc;
+    mbedtls_ssl_ticket_init(&ctx->tickets);
+    if ((rc = mbedtls_ssl_ticket_setup(&ctx->tickets, mg_mbed_rng, NULL,
+                                       MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
+        0) {
+      MG_ERROR((" mbedtls_ssl_ticket_setup %#x", -rc));
+    }
+#endif
+    mgr->tls_ctx = ctx;
   }
-  mg_sha1_update(context, finalcount, 8);
-  for (i = 0; i < 20; i++) {
-    digest[i] =
-        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+}
+
+void mg_tls_ctx_free(struct mg_mgr *mgr) {
+  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
+  if (ctx != NULL) {
+#ifdef MBEDTLS_SSL_SESSION_TICKETS
+    mbedtls_ssl_ticket_free(&ctx->tickets);
+#endif
+    free(ctx);
+    mgr->tls_ctx = NULL;
   }
-  memset(context, '\0', sizeof(*context));
-  memset(&finalcount, '\0', sizeof(finalcount));
 }
+#endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/sntp.c"
+#line 1 "src/tls_openssl.c"
 #endif
 
 
 
+#if MG_TLS == MG_TLS_OPENSSL || MG_TLS == MG_TLS_WOLFSSL
 
+static int tls_err_cb(const char *s, size_t len, void *c) {
+  int n = (int) len - 1;
+  MG_ERROR(("%lu %.*s", ((struct mg_connection *) c)->id, n, s));
+  return 0;  // undocumented
+}
 
+static int mg_tls_err(struct mg_connection *c, struct mg_tls *tls, int res) {
+  int err = SSL_get_error(tls->ssl, res);
+  // We've just fetched the last error from the queue.
+  // Now we need to clear the error queue. If we do not, then the following
+  // can happen (actually reported):
+  //  - A new connection is accept()-ed with cert error (e.g. self-signed cert)
+  //  - Since all accept()-ed connections share listener's context,
+  //  - *ALL* SSL accepted connection report read error on the next poll cycle.
+  //    Thus a single errored connection can close all the rest, unrelated ones.
+  // Clearing the error keeps the shared SSL_CTX in an OK state.
 
-#define SNTP_TIME_OFFSET 2208988800U  // (1970 - 1900) in seconds
-#define SNTP_MAX_FRAC 4294967295.0    // 2 ** 32 - 1
+  if (err != 0) ERR_print_errors_cb(tls_err_cb, c);
+  ERR_clear_error();
+  if (err == SSL_ERROR_WANT_READ) return 0;
+  if (err == SSL_ERROR_WANT_WRITE) return 0;
+  return err;
+}
 
-static int64_t gettimestamp(const uint32_t *data) {
-  uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
-  if (sec) sec -= SNTP_TIME_OFFSET;
-  return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0);
+static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) {
+  BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len);
+  STACK_OF(X509_INFO) *certs =
+      bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL;
+  if (bio) BIO_free(bio);
+  return certs;
 }
 
-int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
-  int64_t res = -1;
-  int mode = len > 0 ? buf[0] & 7 : 0;
-  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
-  if (len < 48) {
-    MG_ERROR(("%s", "corrupt packet"));
-  } else if (mode != 4 && mode != 5) {
-    MG_ERROR(("%s", "not a server reply"));
-  } else if (buf[1] == 0) {
-    MG_ERROR(("%s", "server sent a kiss of death"));
-  } else if (version == 4 || version == 3) {
-    // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
-    int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
-    int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
-    int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
-    int64_t t3 = (int64_t) mg_millis();
-    int64_t delta = (t3 - t0) - (t2 - t1);
-    MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
-    res = t2 + delta / 2;
-  } else {
-    MG_ERROR(("unexpected version: %d", version));
+static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) * certs) {
+  X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
+  for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
+    X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
+    if (cert_info->x509 && !X509_STORE_add_cert(cert_store, cert_info->x509))
+      return false;
+  }
+  return true;
+}
+
+static EVP_PKEY *load_key(struct mg_str s) {
+  BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
+  EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0, NULL) : NULL;
+  if (bio) BIO_free(bio);
+  return key;
+}
+
+static X509 *load_cert(struct mg_str s) {
+  BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len);
+  X509 *cert = bio == NULL ? NULL
+               : s.buf[0] == '-'
+                   ? PEM_read_bio_X509(bio, NULL, NULL, NULL)  // PEM
+                   : d2i_X509_bio(bio, NULL);                  // DER
+  if (bio) BIO_free(bio);
+  return cert;
+}
+
+static long mg_bio_ctrl(BIO *b, int cmd, long larg, void *pargs) {
+  long ret = 0;
+  if (cmd == BIO_CTRL_PUSH) ret = 1;
+  if (cmd == BIO_CTRL_POP) ret = 1;
+  if (cmd == BIO_CTRL_FLUSH) ret = 1;
+#if MG_TLS == MG_TLS_OPENSSL
+  if (cmd == BIO_C_SET_NBIO) ret = 1;
+#endif
+  // MG_DEBUG(("%d -> %ld", cmd, ret));
+  (void) b, (void) cmd, (void) larg, (void) pargs;
+  return ret;
+}
+
+static int mg_bio_read(BIO *bio, char *buf, int len) {
+  struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
+  long res = mg_io_recv(c, buf, (size_t) len);
+  // MG_DEBUG(("%p %d %ld", buf, len, res));
+  len = res > 0 ? (int) res : -1;
+  if (res == MG_IO_WAIT) BIO_set_retry_read(bio);
+  return len;
+}
+
+static int mg_bio_write(BIO *bio, const char *buf, int len) {
+  struct mg_connection *c = (struct mg_connection *) BIO_get_data(bio);
+  long res = mg_io_send(c, buf, (size_t) len);
+  // MG_DEBUG(("%p %d %ld", buf, len, res));
+  len = res > 0 ? (int) res : -1;
+  if (res == MG_IO_WAIT) BIO_set_retry_write(bio);
+  return len;
+}
+
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
+  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
+  const char *id = "mongoose";
+  static unsigned char s_initialised = 0;
+  BIO *bio = NULL;
+  int rc;
+
+  if (tls == NULL) {
+    mg_error(c, "TLS OOM");
+    goto fail;
+  }
+
+  if (!s_initialised) {
+    SSL_library_init();
+    s_initialised++;
+  }
+  MG_DEBUG(("%lu Setting TLS", c->id));
+  tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
+                          : SSL_CTX_new(SSLv23_server_method());
+  if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
+    mg_error(c, "SSL_new");
+    goto fail;
+  }
+  SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
+                             (unsigned) strlen(id));
+  // Disable deprecated protocols
+  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
+  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
+  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
+  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
+#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
+  SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
+#endif
+#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
+  SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
+#endif
+
+#if MG_TLS == MG_TLS_WOLFSSL && !defined(OPENSSL_COMPATIBLE_DEFAULTS)
+  if (opts->ca.len == 0 || mg_strcmp(opts->ca, mg_str("*")) == 0) {
+    // Older versions require that either the CA is loaded or SSL_VERIFY_NONE
+    // explicitly set
+    SSL_set_verify(tls->ssl, SSL_VERIFY_NONE, NULL);
+  }
+#endif
+  if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') {
+    SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                   NULL);
+    STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca);
+    rc = add_ca_certs(tls->ctx, certs);
+    sk_X509_INFO_pop_free(certs, X509_INFO_free);
+    if (!rc) {
+      mg_error(c, "CA err");
+      goto fail;
+    }
+  }
+  if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') {
+    X509 *cert = load_cert(opts->cert);
+    rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert);
+    X509_free(cert);
+    if (cert == NULL || rc != 1) {
+      mg_error(c, "CERT err %d", mg_tls_err(c, tls, rc));
+      goto fail;
+    }
+  }
+  if (opts->key.buf != NULL && opts->key.buf[0] != '\0') {
+    EVP_PKEY *key = load_key(opts->key);
+    rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key);
+    EVP_PKEY_free(key);
+    if (key == NULL || rc != 1) {
+      mg_error(c, "KEY err %d", mg_tls_err(c, tls, rc));
+      goto fail;
+    }
+  }
+
+  SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+#if MG_TLS == MG_TLS_OPENSSL && OPENSSL_VERSION_NUMBER > 0x10002000L
+  (void) SSL_set_ecdh_auto(tls->ssl, 1);
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+  if (opts->name.len > 0) {
+    char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf);
+#if MG_TLS != MG_TLS_WOLFSSL || LIBWOLFSSL_VERSION_HEX >= 0x05005002
+    SSL_set1_host(tls->ssl, s);
+#else
+    X509_VERIFY_PARAM_set1_host(SSL_get0_param(tls->ssl), s, 0);
+#endif
+    SSL_set_tlsext_host_name(tls->ssl, s);
+    free(s);
   }
-  return res;
-}
+#endif
+#if MG_TLS == MG_TLS_WOLFSSL
+  tls->bm = BIO_meth_new(0, "bio_mg");
+#else
+  tls->bm = BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "bio_mg");
+#endif
+  BIO_meth_set_write(tls->bm, mg_bio_write);
+  BIO_meth_set_read(tls->bm, mg_bio_read);
+  BIO_meth_set_ctrl(tls->bm, mg_bio_ctrl);
 
-static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
-  if (ev == MG_EV_READ) {
-    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
-    if (milliseconds > 0) {
-      MG_INFO(("%lu got time: %lld ms from epoch", c->id, milliseconds));
-      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
-      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
-                  (unsigned) (milliseconds % 1000)));
-    }
-    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
-  } else if (ev == MG_EV_CONNECT) {
-    mg_sntp_request(c);
-  } else if (ev == MG_EV_CLOSE) {
+  bio = BIO_new(tls->bm);
+  BIO_set_data(bio, c);
+  SSL_set_bio(tls->ssl, bio, bio);
+
+  c->tls = tls;
+  c->is_tls = 1;
+  c->is_tls_hs = 1;
+  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
+    mg_tls_handshake(c);
   }
-  (void) fnd;
-  (void) evd;
+  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
+  return;
+fail:
+  free(tls);
 }
 
-void mg_sntp_request(struct mg_connection *c) {
-  if (c->is_resolving) {
-    MG_ERROR(("%lu wait until resolved", c->id));
+void mg_tls_handshake(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  int rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
+  if (rc == 1) {
+    MG_DEBUG(("%lu success", c->id));
+    c->is_tls_hs = 0;
+    mg_call(c, MG_EV_TLS_HS, NULL);
   } else {
-    int64_t now = (int64_t) mg_millis();  // Use int64_t, for vc98
-    uint8_t buf[48] = {0};
-    uint32_t *t = (uint32_t *) &buf[40];
-    double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
-    buf[0] = (0 << 6) | (4 << 3) | 3;
-    t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
-    t[1] = mg_htonl((uint32_t) frac);
-    mg_send(c, buf, sizeof(buf));
+    int code = mg_tls_err(c, tls, rc);
+    if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
   }
 }
 
-struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
-                                      mg_event_handler_t fn, void *fnd) {
-  struct mg_connection *c = NULL;
-  if (url == NULL) url = "udp://time.google.com:123";
-  if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
-  return c;
+void mg_tls_free(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  if (tls == NULL) return;
+  SSL_free(tls->ssl);
+  SSL_CTX_free(tls->ctx);
+  BIO_meth_free(tls->bm);
+  free(tls);
+  c->tls = NULL;
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/sock.c"
-#endif
-
+size_t mg_tls_pending(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
+}
 
+long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  int n = SSL_read(tls->ssl, buf, (int) len);
+  if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
+  if (n <= 0) return MG_IO_ERR;
+  return n;
+}
 
+long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  int n = SSL_write(tls->ssl, buf, (int) len);
+  if (n < 0 && mg_tls_err(c, tls, n) == 0) return MG_IO_WAIT;
+  if (n <= 0) return MG_IO_ERR;
+  return n;
+}
 
+void mg_tls_ctx_init(struct mg_mgr *mgr) {
+  (void) mgr;
+}
 
+void mg_tls_ctx_free(struct mg_mgr *mgr) {
+  (void) mgr;
+}
+#endif
 
+#ifdef MG_ENABLE_LINES
+#line 1 "src/tls_uecc.c"
+#endif
+/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */
 
 
 
 
-#if MG_ENABLE_SOCKET
+#if MG_TLS == MG_TLS_BUILTIN
 
-#ifndef closesocket
-#define closesocket(x) close(x)
+#ifndef MG_UECC_RNG_MAX_TRIES
+#define MG_UECC_RNG_MAX_TRIES 64
 #endif
 
-#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
-#define S2PTR(s_) ((void *) (size_t) (s_))
-
-#ifndef MSG_NONBLOCKING
-#define MSG_NONBLOCKING 0
+#if MG_UECC_ENABLE_VLI_API
+#define MG_UECC_VLI_API
+#else
+#define MG_UECC_VLI_API static
 #endif
 
-#ifndef AF_INET6
-#define AF_INET6 10
+#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM == mg_uecc_arm) || \
+    (MG_UECC_PLATFORM == mg_uecc_arm_thumb) ||                                \
+    (MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
+#define CONCATX(a, ...) a##__VA_ARGS__
+#define CONCAT(a, ...) CONCATX(a, __VA_ARGS__)
+
+#define STRX(a) #a
+#define STR(a) STRX(a)
+
+#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__))))
+#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__))))
+#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__))))
+#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__))))
+#define EVAL4(...) __VA_ARGS__
+
+#define DEC_1 0
+#define DEC_2 1
+#define DEC_3 2
+#define DEC_4 3
+#define DEC_5 4
+#define DEC_6 5
+#define DEC_7 6
+#define DEC_8 7
+#define DEC_9 8
+#define DEC_10 9
+#define DEC_11 10
+#define DEC_12 11
+#define DEC_13 12
+#define DEC_14 13
+#define DEC_15 14
+#define DEC_16 15
+#define DEC_17 16
+#define DEC_18 17
+#define DEC_19 18
+#define DEC_20 19
+#define DEC_21 20
+#define DEC_22 21
+#define DEC_23 22
+#define DEC_24 23
+#define DEC_25 24
+#define DEC_26 25
+#define DEC_27 26
+#define DEC_28 27
+#define DEC_29 28
+#define DEC_30 29
+#define DEC_31 30
+#define DEC_32 31
+
+#define DEC(N) CONCAT(DEC_, N)
+
+#define SECOND_ARG(_, val, ...) val
+#define SOME_CHECK_0 ~, 0
+#define GET_SECOND_ARG(...) SECOND_ARG(__VA_ARGS__, SOME, )
+#define SOME_OR_0(N) GET_SECOND_ARG(CONCAT(SOME_CHECK_, N))
+
+#define EMPTY(...)
+#define DEFER(...) __VA_ARGS__ EMPTY()
+
+#define REPEAT_NAME_0() REPEAT_0
+#define REPEAT_NAME_SOME() REPEAT_SOME
+#define REPEAT_0(...)
+#define REPEAT_SOME(N, stuff) \
+  DEFER(CONCAT(REPEAT_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), stuff) stuff
+#define REPEAT(N, stuff) EVAL(REPEAT_SOME(N, stuff))
+
+#define REPEATM_NAME_0() REPEATM_0
+#define REPEATM_NAME_SOME() REPEATM_SOME
+#define REPEATM_0(...)
+#define REPEATM_SOME(N, macro) \
+  macro(N) DEFER(CONCAT(REPEATM_NAME_, SOME_OR_0(DEC(N))))()(DEC(N), macro)
+#define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro))
 #endif
 
-union usa {
-  struct sockaddr sa;
-  struct sockaddr_in sin;
-#if MG_ENABLE_IPV6
-  struct sockaddr_in6 sin6;
-#endif
-};
+// 
 
-static socklen_t tousa(struct mg_addr *a, union usa *usa) {
-  socklen_t len = sizeof(usa->sin);
-  memset(usa, 0, sizeof(*usa));
-  usa->sin.sin_family = AF_INET;
-  usa->sin.sin_port = a->port;
-  *(uint32_t *) &usa->sin.sin_addr = a->ip;
-#if MG_ENABLE_IPV6
-  if (a->is_ip6) {
-    usa->sin.sin_family = AF_INET6;
-    usa->sin6.sin6_port = a->port;
-    memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6));
-    len = sizeof(usa->sin6);
-  }
+#if (MG_UECC_WORD_SIZE == 1)
+#if MG_UECC_SUPPORTS_secp160r1
+#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */
 #endif
-  return len;
-}
-
-static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
-  a->is_ip6 = is_ip6;
-  a->port = usa->sin.sin_port;
-  memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip));
-#if MG_ENABLE_IPV6
-  if (is_ip6) {
-    memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6));
-    a->port = usa->sin6.sin6_port;
-  }
+#if MG_UECC_SUPPORTS_secp192r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 24
 #endif
-}
-
-static bool mg_sock_would_block(void) {
-  int err = MG_SOCKET_ERRNO;
-  return err == EINPROGRESS || err == EWOULDBLOCK
-#ifndef WINCE
-         || err == EAGAIN || err == EINTR
+#if MG_UECC_SUPPORTS_secp224r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 28
 #endif
-#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
-         || err == WSAEINTR || err == WSAEWOULDBLOCK
+#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 32
 #endif
-      ;
-}
-
-static bool mg_sock_conn_reset(void) {
-  int err = MG_SOCKET_ERRNO;
-#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
-  return err == WSAECONNRESET;
-#else
-  return err == EPIPE || err == ECONNRESET;
+#elif (MG_UECC_WORD_SIZE == 4)
+#if MG_UECC_SUPPORTS_secp160r1
+#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */
 #endif
-}
+#if MG_UECC_SUPPORTS_secp192r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 6
+#endif
+#if MG_UECC_SUPPORTS_secp224r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 7
+#endif
+#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 8
+#endif
+#elif (MG_UECC_WORD_SIZE == 8)
+#if MG_UECC_SUPPORTS_secp160r1
+#define MG_UECC_MAX_WORDS 3
+#endif
+#if MG_UECC_SUPPORTS_secp192r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 3
+#endif
+#if MG_UECC_SUPPORTS_secp224r1
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 4
+#endif
+#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1)
+#undef MG_UECC_MAX_WORDS
+#define MG_UECC_MAX_WORDS 4
+#endif
+#endif /* MG_UECC_WORD_SIZE */
+
+#define BITS_TO_WORDS(num_bits)                                \
+  ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \
+                  (MG_UECC_WORD_SIZE * 8)))
+#define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8)
+
+struct MG_UECC_Curve_t {
+  wordcount_t num_words;
+  wordcount_t num_bytes;
+  bitcount_t num_n_bits;
+  mg_uecc_word_t p[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t n[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2];
+  mg_uecc_word_t b[MG_UECC_MAX_WORDS];
+  void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                          mg_uecc_word_t *Z1, MG_UECC_Curve curve);
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+  void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve);
+#endif
+  void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x,
+                 MG_UECC_Curve curve);
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+  void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t *product);
+#endif
+};
 
-static void setlocaddr(MG_SOCKET_TYPE fd, struct mg_addr *addr) {
-  union usa usa;
-  socklen_t n = sizeof(usa);
-  if (getsockname(fd, &usa.sa, &n) == 0) {
-    tomgaddr(&usa, addr, n != sizeof(usa.sin));
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+static void bcopy(uint8_t *dst, const uint8_t *src, unsigned num_bytes) {
+  while (0 != num_bytes) {
+    num_bytes--;
+    dst[num_bytes] = src[num_bytes];
   }
 }
+#endif
 
-static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
-  if (n == MG_IO_WAIT) {
-    // Do nothing
-  } else if (n <= 0) {
-    c->is_closing = 1;  // Termination. Don't call mg_error(): #1529
-  } else if (n > 0) {
-    if (c->is_hexdumping) {
-      union usa usa;
-      socklen_t slen = sizeof(usa.sin);
-      if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0;  // Ignore result
-      MG_INFO(("\n-- %lu %I %s %I %ld", c->id, 4, &usa.sin.sin_addr,
-               r ? "<-" : "->", 4, &c->rem.ip, n));
+static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
+                                          const mg_uecc_word_t *right,
+                                          wordcount_t num_words);
 
-      mg_hexdump(buf, (size_t) n);
-    }
-    if (r) {
-      c->recv.len += (size_t) n;
-      mg_call(c, MG_EV_READ, &n);
-    } else {
-      mg_iobuf_del(&c->send, 0, (size_t) n);
-      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
-      if (c->send.len == 0) {
-        MG_EPOLL_MOD(c, 0);
-      }
-      mg_call(c, MG_EV_WRITE, &n);
-    }
-  }
-}
+#if (MG_UECC_PLATFORM == mg_uecc_arm ||       \
+     MG_UECC_PLATFORM == mg_uecc_arm_thumb || \
+     MG_UECC_PLATFORM == mg_uecc_arm_thumb2)
 
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
-  long n;
-  if (c->is_udp) {
-    union usa usa;
-    socklen_t slen = tousa(&c->rem, &usa);
-    n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
-    if (n > 0) setlocaddr(FD(c), &c->loc);
-  } else {
-    n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
-#if MG_ARCH == MG_ARCH_RTX
-    if (n == EWOULDBLOCK) return MG_IO_WAIT;
 #endif
-  }
-  if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
-  if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
 
-bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
-  if (c->is_udp) {
-    long n = mg_io_send(c, buf, len);
-    MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
-              (int) c->recv.len, n, MG_SOCKET_ERRNO));
-    iolog(c, (char *) buf, n, false);
-    return n > 0;
-  } else {
-    return mg_iobuf_add(&c->send, c->send.len, buf, len);
-  }
-}
+#if (MG_UECC_PLATFORM == mg_uecc_avr)
 
-static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
-#if defined(MG_CUSTOM_NONBLOCK)
-  MG_CUSTOM_NONBLOCK(fd);
-#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
-  unsigned long on = 1;
-  ioctlsocket(fd, FIONBIO, &on);
-#elif MG_ARCH == MG_ARCH_RTX
-  unsigned long on = 1;
-  ioctlsocket(fd, FIONBIO, &on);
-#elif MG_ENABLE_FREERTOS_TCP
-  const BaseType_t off = 0;
-  if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0;
-  if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0;
-#elif MG_ENABLE_LWIP
-  lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
-#elif MG_ARCH == MG_ARCH_AZURERTOS
-  fcntl(fd, F_SETFL, O_NONBLOCK);
-#elif MG_ARCH == MG_ARCH_TIRTOS
-  int val = 0;
-  setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
-  // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
-  int sz = sizeof(val);
-  getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
-  val /= 2;  // set send low-water mark at half send buffer size
-  setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
-#else
-  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);  // Non-blocking mode
-  fcntl(fd, F_SETFD, FD_CLOEXEC);                          // Set close-on-exec
 #endif
-}
 
-bool mg_open_listener(struct mg_connection *c, const char *url) {
-  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
-  bool success = false;
-  c->loc.port = mg_htons(mg_url_port(url));
-  if (!mg_aton(mg_url_host(url), &c->loc)) {
-    MG_ERROR(("invalid listening URL: %s", url));
-  } else {
-    union usa usa;
-    socklen_t slen = tousa(&c->loc, &usa);
-    int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
-    int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
-    int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
-    (void) on;
+#ifndef asm_clear
+#define asm_clear 0
+#endif
+#ifndef asm_set
+#define asm_set 0
+#endif
+#ifndef asm_add
+#define asm_add 0
+#endif
+#ifndef asm_sub
+#define asm_sub 0
+#endif
+#ifndef asm_mult
+#define asm_mult 0
+#endif
+#ifndef asm_rshift1
+#define asm_rshift1 0
+#endif
+#ifndef asm_mmod_fast_secp256r1
+#define asm_mmod_fast_secp256r1 0
+#endif
 
-    if ((fd = socket(af, type, proto)) == MG_INVALID_SOCKET) {
-      MG_ERROR(("socket: %d", MG_SOCKET_ERRNO));
-#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
-     (defined(LWIP_SOCKET) && SO_REUSE == 1))
-    } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
-                          sizeof(on)) != 0) {
-      // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of
-      //    SO_REUSEADDR on UNIX and Windows is different. On Windows,
-      //    SO_REUSEADDR allows to bind a socket to a port without error even
-      //    if the port is already open by another program. This is not the
-      //    behavior SO_REUSEADDR was designed for, and leads to hard-to-track
-      //    failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows
-      //    unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
-      // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by
-      // defining
-      //    SO_REUSE (in lwipopts.h), otherwise the code below will compile
-      //    but won't work! (setsockopt will return EINVAL)
-      MG_ERROR(("reuseaddr: %d", MG_SOCKET_ERRNO));
-#endif
-#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)
-    } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on,
-                          sizeof(on)) != 0) {
-      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
-      MG_ERROR(("exclusiveaddruse: %d", MG_SOCKET_ERRNO));
+#if defined(default_RNG_defined) && default_RNG_defined
+static MG_UECC_RNG_Function g_rng_function = &default_RNG;
+#else
+static MG_UECC_RNG_Function g_rng_function = 0;
 #endif
-    } else if (bind(fd, &usa.sa, slen) != 0) {
-      MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
-    } else if ((type == SOCK_STREAM &&
-                listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
-      // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
-      // In case port was set to 0, get the real port number
-      MG_ERROR(("listen: %d", MG_SOCKET_ERRNO));
-    } else {
-      setlocaddr(fd, &c->loc);
-      mg_set_non_blocking_mode(fd);
-      c->fd = S2PTR(fd);
-      MG_EPOLL_ADD(c);
-      success = true;
-    }
-  }
-  if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
-  return success;
+
+void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) {
+  g_rng_function = rng_function;
+}
+
+MG_UECC_RNG_Function mg_uecc_get_rng(void) {
+  return g_rng_function;
 }
 
-long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
-  long n = 0;
-  if (c->is_udp) {
-    union usa usa;
-    socklen_t slen = tousa(&c->rem, &usa);
-    n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
-    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
-  } else {
-    n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
+int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) {
+  return BITS_TO_BYTES(curve->num_n_bits);
+}
+
+int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) {
+  return 2 * curve->num_bytes;
+}
+
+#if !asm_clear
+MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli,
+                                       wordcount_t num_words) {
+  wordcount_t i;
+  for (i = 0; i < num_words; ++i) {
+    vli[i] = 0;
   }
-  if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT;
-  if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
 }
+#endif /* !asm_clear */
 
-// NOTE(lsm): do only one iteration of reads, cause some systems
-// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
-static void read_conn(struct mg_connection *c) {
-  long n = -1;
-  if (c->recv.len >= MG_MAX_RECV_SIZE) {
-    mg_error(c, "max_recv_buf_size reached");
-  } else if (c->recv.size <= c->recv.len &&
-             !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
-    mg_error(c, "oom");
-  } else {
-    char *buf = (char *) &c->recv.buf[c->recv.len];
-    size_t len = c->recv.size - c->recv.len;
-    n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf, len);
-    MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
-              (long) c->send.len, (long) c->send.size, (long) c->recv.len,
-              (long) c->recv.size, n, MG_SOCKET_ERRNO));
-    iolog(c, buf, n, true);
+/* Constant-time comparison to zero - secure way to compare long integers */
+/* Returns 1 if vli == 0, 0 otherwise. */
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli,
+                                                  wordcount_t num_words) {
+  mg_uecc_word_t bits = 0;
+  wordcount_t i;
+  for (i = 0; i < num_words; ++i) {
+    bits |= vli[i];
   }
+  return (bits == 0);
 }
 
-static void write_conn(struct mg_connection *c) {
-  char *buf = (char *) c->send.buf;
-  size_t len = c->send.len;
-  long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len);
-  MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
-            (long) c->send.len, (long) c->send.size, (long) c->recv.len,
-            (long) c->recv.size, n, MG_SOCKET_ERRNO));
-  iolog(c, buf, n, false);
+/* Returns nonzero if bit 'bit' of vli is set. */
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli,
+                                                   bitcount_t bit) {
+  return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] &
+          ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK)));
 }
 
-static void close_conn(struct mg_connection *c) {
-  if (FD(c) != MG_INVALID_SOCKET) {
-#if MG_ENABLE_EPOLL
-    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
-#endif
-    closesocket(FD(c));
-#if MG_ENABLE_FREERTOS_TCP
-    FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
-#endif
+/* Counts the number of words in vli. */
+static wordcount_t vli_numDigits(const mg_uecc_word_t *vli,
+                                 const wordcount_t max_words) {
+  wordcount_t i;
+  /* Search from the end until we find a non-zero digit.
+     We do it in reverse because we expect that most digits will be nonzero. */
+  for (i = max_words - 1; i >= 0 && vli[i] == 0; --i) {
   }
-  mg_close_conn(c);
+
+  return (i + 1);
 }
 
-static void connect_conn(struct mg_connection *c) {
-  union usa usa;
-  socklen_t n = sizeof(usa);
-  // Use getpeername() to test whether we have connected
-  if (getpeername(FD(c), &usa.sa, &n) == 0) {
-    c->is_connecting = 0;
-    mg_call(c, MG_EV_CONNECT, NULL);
-    MG_EPOLL_MOD(c, 0);
-    if (c->is_tls_hs) mg_tls_handshake(c);
-  } else {
-    mg_error(c, "socket error");
+/* Counts the number of bits required to represent vli. */
+MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli,
+                                               const wordcount_t max_words) {
+  mg_uecc_word_t i;
+  mg_uecc_word_t digit;
+
+  wordcount_t num_digits = vli_numDigits(vli, max_words);
+  if (num_digits == 0) {
+    return 0;
   }
-}
 
-static void setsockopts(struct mg_connection *c) {
-#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
-    MG_ARCH == MG_ARCH_TIRTOS
-  (void) c;
-#else
-  int on = 1;
-#if !defined(SOL_TCP)
-#define SOL_TCP IPPROTO_TCP
-#endif
-  if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0)
-    (void) 0;
-  if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) !=
-      0)
-    (void) 0;
-#endif
+  digit = vli[num_digits - 1];
+  for (i = 0; digit; ++i) {
+    digit >>= 1;
+  }
+
+  return (((bitcount_t) ((num_digits - 1) << MG_UECC_WORD_BITS_SHIFT)) +
+          (bitcount_t) i);
 }
 
-void mg_connect_resolved(struct mg_connection *c) {
-  int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
-  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;  // c->rem has resolved IP
-  c->fd = S2PTR(socket(af, type, 0));               // Create outbound socket
-  c->is_resolving = 0;                              // Clear resolving flag
-  if (FD(c) == MG_INVALID_SOCKET) {
-    mg_error(c, "socket(): %d", MG_SOCKET_ERRNO);
-  } else if (c->is_udp) {
-    MG_EPOLL_ADD(c);
-#if MG_ARCH == MG_ARCH_TIRTOS
-    union usa usa;  // TI-RTOS NDK requires binding to receive on UDP sockets
-    socklen_t slen = tousa(&c->loc, &usa);
-    if (bind(c->fd, &usa.sa, slen) != 0)
-      MG_ERROR(("bind: %d", MG_SOCKET_ERRNO));
-#endif
-    mg_call(c, MG_EV_RESOLVE, NULL);
-    mg_call(c, MG_EV_CONNECT, NULL);
-  } else {
-    union usa usa;
-    socklen_t slen = tousa(&c->rem, &usa);
-    mg_set_non_blocking_mode(FD(c));
-    setsockopts(c);
-    MG_EPOLL_ADD(c);
-    mg_call(c, MG_EV_RESOLVE, NULL);
-    if ((rc = connect(FD(c), &usa.sa, slen)) == 0) {
-      mg_call(c, MG_EV_CONNECT, NULL);
-    } else if (mg_sock_would_block()) {
-      MG_DEBUG(("%lu %p -> %I:%hu pend", c->id, c->fd, 4, &c->rem.ip,
-                mg_ntohs(c->rem.port)));
-      c->is_connecting = 1;
-    } else {
-      mg_error(c, "connect: %d", MG_SOCKET_ERRNO);
+/* Sets dest = src. */
+#if !asm_set
+MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest,
+                                     const mg_uecc_word_t *src,
+                                     wordcount_t num_words) {
+  wordcount_t i;
+  for (i = 0; i < num_words; ++i) {
+    dest[i] = src[i];
+  }
+}
+#endif /* !asm_set */
+
+/* Returns sign of left - right. */
+static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left,
+                                          const mg_uecc_word_t *right,
+                                          wordcount_t num_words) {
+  wordcount_t i;
+  for (i = num_words - 1; i >= 0; --i) {
+    if (left[i] > right[i]) {
+      return 1;
+    } else if (left[i] < right[i]) {
+      return -1;
     }
   }
-  (void) rc;
+  return 0;
 }
 
-static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
-                              socklen_t *len) {
-  MG_SOCKET_TYPE s = MG_INVALID_SOCKET;
-  do {
-    memset(usa, 0, sizeof(*usa));
-    s = accept(sock, &usa->sa, len);
-  } while (s == MG_INVALID_SOCKET && errno == EINTR);
-  return s;
+/* Constant-time comparison function - secure way to compare long integers */
+/* Returns one if left == right, zero otherwise. */
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left,
+                                                 const mg_uecc_word_t *right,
+                                                 wordcount_t num_words) {
+  mg_uecc_word_t diff = 0;
+  wordcount_t i;
+  for (i = num_words - 1; i >= 0; --i) {
+    diff |= (left[i] ^ right[i]);
+  }
+  return (diff == 0);
+}
+
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
+                                               const mg_uecc_word_t *left,
+                                               const mg_uecc_word_t *right,
+                                               wordcount_t num_words);
+
+/* Returns sign of left - right, in constant time. */
+MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left,
+                                            const mg_uecc_word_t *right,
+                                            wordcount_t num_words) {
+  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right, num_words);
+  mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words);
+  return (cmpresult_t) (!equal - 2 * neg);
+}
+
+/* Computes vli = vli >> 1. */
+#if !asm_rshift1
+MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli,
+                                         wordcount_t num_words) {
+  mg_uecc_word_t *end = vli;
+  mg_uecc_word_t carry = 0;
+
+  vli += num_words;
+  while (vli-- > end) {
+    mg_uecc_word_t temp = *vli;
+    *vli = (temp >> 1) | carry;
+    carry = temp << (MG_UECC_WORD_BITS - 1);
+  }
 }
+#endif /* !asm_rshift1 */
+
+/* Computes result = left + right, returning carry. Can modify in place. */
+#if !asm_add
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result,
+                                               const mg_uecc_word_t *left,
+                                               const mg_uecc_word_t *right,
+                                               wordcount_t num_words) {
+  mg_uecc_word_t carry = 0;
+  wordcount_t i;
+  for (i = 0; i < num_words; ++i) {
+    mg_uecc_word_t sum = left[i] + right[i] + carry;
+    if (sum != left[i]) {
+      carry = (sum < left[i]);
+    }
+    result[i] = sum;
+  }
+  return carry;
+}
+#endif /* !asm_add */
+
+/* Computes result = left - right, returning borrow. Can modify in place. */
+#if !asm_sub
+MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result,
+                                               const mg_uecc_word_t *left,
+                                               const mg_uecc_word_t *right,
+                                               wordcount_t num_words) {
+  mg_uecc_word_t borrow = 0;
+  wordcount_t i;
+  for (i = 0; i < num_words; ++i) {
+    mg_uecc_word_t diff = left[i] - right[i] - borrow;
+    if (diff != left[i]) {
+      borrow = (diff > left[i]);
+    }
+    result[i] = diff;
+  }
+  return borrow;
+}
+#endif /* !asm_sub */
+
+#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) ||               \
+    (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0) && \
+     ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8)))
+static void muladd(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0,
+                   mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
+#if MG_UECC_WORD_SIZE == 8
+  uint64_t a0 = a & 0xffffffff;
+  uint64_t a1 = a >> 32;
+  uint64_t b0 = b & 0xffffffff;
+  uint64_t b1 = b >> 32;
+
+  uint64_t i0 = a0 * b0;
+  uint64_t i1 = a0 * b1;
+  uint64_t i2 = a1 * b0;
+  uint64_t i3 = a1 * b1;
+
+  uint64_t p0, p1;
+
+  i2 += (i0 >> 32);
+  i2 += i1;
+  if (i2 < i1) { /* overflow */
+    i3 += 0x100000000;
+  }
 
-static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
-  struct mg_connection *c = NULL;
-  union usa usa;
-  socklen_t sa_len = sizeof(usa);
-  MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
-  if (fd == MG_INVALID_SOCKET) {
-#if MG_ARCH == MG_ARCH_AZURERTOS
-    // AzureRTOS, in non-block socket mode can mark listening socket readable
-    // even it is not. See comment for 'select' func implementation in
-    // nx_bsd.c That's not an error, just should try later
-    if (MG_SOCKET_ERRNO != EAGAIN)
-#endif
-      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCKET_ERRNO));
-#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
-    (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL
-  } else if ((long) fd >= FD_SETSIZE) {
-    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
-    closesocket(fd);
+  p0 = (i0 & 0xffffffff) | (i2 << 32);
+  p1 = i3 + (i2 >> 32);
+
+  *r0 += p0;
+  *r1 += (p1 + (*r0 < p0));
+  *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
+#else
+  mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
+  mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0;
+  r01 += p;
+  *r2 += (r01 < p);
+  *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS);
+  *r0 = (mg_uecc_word_t) r01;
 #endif
-  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
-    MG_ERROR(("%lu OOM", lsn->id));
-    closesocket(fd);
-  } else {
-    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->fd = S2PTR(fd);
-    MG_EPOLL_ADD(c);
-    mg_set_non_blocking_mode(FD(c));
-    setsockopts(c);
-    c->is_accepted = 1;
-    c->is_hexdumping = lsn->is_hexdumping;
-    c->loc = lsn->loc;
-    c->pfn = lsn->pfn;
-    c->pfn_data = lsn->pfn_data;
-    c->fn = lsn->fn;
-    c->fn_data = lsn->fn_data;
-    MG_DEBUG(("%lu %p accepted %I.%hu -> %I.%hu", c->id, c->fd, 4, &c->rem.ip,
-              mg_ntohs(c->rem.port), 4, &c->loc.ip, mg_ntohs(c->loc.port)));
-    mg_call(c, MG_EV_OPEN, NULL);
-    mg_call(c, MG_EV_ACCEPT, NULL);
+}
+#endif /* muladd needed */
+
+#if !asm_mult
+MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result,
+                                      const mg_uecc_word_t *left,
+                                      const mg_uecc_word_t *right,
+                                      wordcount_t num_words) {
+  mg_uecc_word_t r0 = 0;
+  mg_uecc_word_t r1 = 0;
+  mg_uecc_word_t r2 = 0;
+  wordcount_t i, k;
+
+  /* Compute each digit of result in sequence, maintaining the carries. */
+  for (k = 0; k < num_words; ++k) {
+    for (i = 0; i <= k; ++i) {
+      muladd(left[i], right[k - i], &r0, &r1, &r2);
+    }
+    result[k] = r0;
+    r0 = r1;
+    r1 = r2;
+    r2 = 0;
   }
+  for (k = num_words; k < num_words * 2 - 1; ++k) {
+    for (i = (wordcount_t) ((k + 1) - num_words); i < num_words; ++i) {
+      muladd(left[i], right[k - i], &r0, &r1, &r2);
+    }
+    result[k] = r0;
+    r0 = r1;
+    r1 = r2;
+    r2 = 0;
+  }
+  result[num_words * 2 - 1] = r0;
 }
+#endif /* !asm_mult */
 
-static bool mg_socketpair(MG_SOCKET_TYPE sp[2], union usa usa[2], bool udp) {
-  MG_SOCKET_TYPE sock;
-  socklen_t n = sizeof(usa[0].sin);
-  bool success = false;
+#if MG_UECC_SQUARE_FUNC
 
-  sock = sp[0] = sp[1] = MG_INVALID_SOCKET;
-  (void) memset(&usa[0], 0, sizeof(usa[0]));
-  usa[0].sin.sin_family = AF_INET;
-  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U);  // 127.0.0.1
-  usa[1] = usa[0];
+#if !asm_square
+static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0,
+                    mg_uecc_word_t *r1, mg_uecc_word_t *r2) {
+#if MG_UECC_WORD_SIZE == 8
+  uint64_t a0 = a & 0xffffffffull;
+  uint64_t a1 = a >> 32;
+  uint64_t b0 = b & 0xffffffffull;
+  uint64_t b1 = b >> 32;
 
-  if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
-      (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET &&
-      bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 &&
-      getsockname(sp[0], &usa[0].sa, &n) == 0 &&
-      getsockname(sp[1], &usa[1].sa, &n) == 0 &&
-      connect(sp[0], &usa[1].sa, n) == 0 &&
-      connect(sp[1], &usa[0].sa, n) == 0) {
-    success = true;
-  } else if (!udp &&
-             (sock = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
-             bind(sock, &usa[0].sa, n) == 0 &&
-             listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
-             getsockname(sock, &usa[0].sa, &n) == 0 &&
-             (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != MG_INVALID_SOCKET &&
-             connect(sp[0], &usa[0].sa, n) == 0 &&
-             (sp[1] = raccept(sock, &usa[1], &n)) != MG_INVALID_SOCKET) {
-    success = true;
-  }
-  if (success) {
-    mg_set_non_blocking_mode(sp[1]);
-  } else {
-    if (sp[0] != MG_INVALID_SOCKET) closesocket(sp[0]);
-    if (sp[1] != MG_INVALID_SOCKET) closesocket(sp[1]);
-    sp[0] = sp[1] = MG_INVALID_SOCKET;
+  uint64_t i0 = a0 * b0;
+  uint64_t i1 = a0 * b1;
+  uint64_t i2 = a1 * b0;
+  uint64_t i3 = a1 * b1;
+
+  uint64_t p0, p1;
+
+  i2 += (i0 >> 32);
+  i2 += i1;
+  if (i2 < i1) { /* overflow */
+    i3 += 0x100000000ull;
   }
-  if (sock != MG_INVALID_SOCKET) closesocket(sock);
-  return success;
+
+  p0 = (i0 & 0xffffffffull) | (i2 << 32);
+  p1 = i3 + (i2 >> 32);
+
+  *r2 += (p1 >> 63);
+  p1 = (p1 << 1) | (p0 >> 63);
+  p0 <<= 1;
+
+  *r0 += p0;
+  *r1 += (p1 + (*r0 < p0));
+  *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0));
+#else
+  mg_uecc_dword_t p = (mg_uecc_dword_t) a * b;
+  mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0;
+  *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1));
+  p *= 2;
+  r01 += p;
+  *r2 += (r01 < p);
+  *r1 = r01 >> MG_UECC_WORD_BITS;
+  *r0 = (mg_uecc_word_t) r01;
+#endif
 }
 
-int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data,
-              bool udp) {
-  union usa usa[2];
-  MG_SOCKET_TYPE sp[2] = {MG_INVALID_SOCKET, MG_INVALID_SOCKET};
-  struct mg_connection *c = NULL;
-  if (!mg_socketpair(sp, usa, udp)) {
-    MG_ERROR(("Cannot create socket pair"));
-  } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) {
-    closesocket(sp[0]);
-    closesocket(sp[1]);
-    sp[0] = sp[1] = MG_INVALID_SOCKET;
-  } else {
-    tomgaddr(&usa[0], &c->rem, false);
-    MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
+MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
+                                        const mg_uecc_word_t *left,
+                                        wordcount_t num_words) {
+  mg_uecc_word_t r0 = 0;
+  mg_uecc_word_t r1 = 0;
+  mg_uecc_word_t r2 = 0;
+
+  wordcount_t i, k;
+
+  for (k = 0; k < num_words * 2 - 1; ++k) {
+    mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words);
+    for (i = min; i <= k && i <= k - i; ++i) {
+      if (i < k - i) {
+        mul2add(left[i], left[k - i], &r0, &r1, &r2);
+      } else {
+        muladd(left[i], left[k - i], &r0, &r1, &r2);
+      }
+    }
+    result[k] = r0;
+    r0 = r1;
+    r1 = r2;
+    r2 = 0;
   }
-  return (int) sp[0];
-}
 
-static bool can_read(const struct mg_connection *c) {
-  return c->is_full == false;
+  result[num_words * 2 - 1] = r0;
 }
+#endif /* !asm_square */
 
-static bool can_write(const struct mg_connection *c) {
-  return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
-}
+#else /* MG_UECC_SQUARE_FUNC */
 
-static bool skip_iotest(const struct mg_connection *c) {
-  return (c->is_closing || c->is_resolving || FD(c) == MG_INVALID_SOCKET) ||
-         (can_read(c) == false && can_write(c) == false);
+#if MG_UECC_ENABLE_VLI_API
+MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result,
+                                        const mg_uecc_word_t *left,
+                                        wordcount_t num_words) {
+  mg_uecc_vli_mult(result, left, left, num_words);
 }
+#endif /* MG_UECC_ENABLE_VLI_API */
 
-static void mg_iotest(struct mg_mgr *mgr, int ms) {
-#if MG_ENABLE_FREERTOS_TCP
-  struct mg_connection *c;
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    c->is_readable = c->is_writable = 0;
-    if (skip_iotest(c)) continue;
-    if (can_read(c))
-      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
-    if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
-  }
-  FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
-    c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
-    c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
-    FreeRTOS_FD_CLR(c->fd, mgr->ss,
-                    eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
+#endif /* MG_UECC_SQUARE_FUNC */
+
+/* Computes result = (left + right) % mod.
+   Assumes that left < mod and right < mod, and that result does not overlap
+   mod. */
+MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result,
+                                        const mg_uecc_word_t *left,
+                                        const mg_uecc_word_t *right,
+                                        const mg_uecc_word_t *mod,
+                                        wordcount_t num_words) {
+  mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right, num_words);
+  if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) != 1) {
+    /* result > mod (result = mod + remainder), so subtract mod to get
+     * remainder. */
+    mg_uecc_vli_sub(result, result, mod, num_words);
   }
-#elif MG_ENABLE_EPOLL
-  size_t max = 1;
-  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
-    c->is_readable = c->is_writable = 0;
-    if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
-    if (can_write(c)) MG_EPOLL_MOD(c, 1);
-    max++;
+}
+
+/* Computes result = (left - right) % mod.
+   Assumes that left < mod and right < mod, and that result does not overlap
+   mod. */
+MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result,
+                                        const mg_uecc_word_t *left,
+                                        const mg_uecc_word_t *right,
+                                        const mg_uecc_word_t *mod,
+                                        wordcount_t num_words) {
+  mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right, num_words);
+  if (l_borrow) {
+    /* In this case, result == -diff == (max int) - diff. Since -x % d == d - x,
+       we can get the correct result from result + mod (with overflow). */
+    mg_uecc_vli_add(result, result, mod, num_words);
   }
-  struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));
-  int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
-  for (int i = 0; i < n; i++) {
-    struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
-    if (evs[i].events & EPOLLERR) {
-      mg_error(c, "socket error");
-    } else if (c->is_readable == 0) {
-      bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
-      bool wr = evs[i].events & EPOLLOUT;
-      c->is_readable = can_read(c) && rd ? 1U : 0;
-      c->is_writable = can_write(c) && wr ? 1U : 0;
+}
+
+/* Computes result = product % mod, where product is 2N words long. */
+/* Currently only designed to work for curve_p or curve_n. */
+MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result,
+                                      mg_uecc_word_t *product,
+                                      const mg_uecc_word_t *mod,
+                                      wordcount_t num_words) {
+  mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_word_t *v[2] = {tmp, product};
+  mg_uecc_word_t index;
+
+  /* Shift mod so its highest set bit is at the maximum position. */
+  bitcount_t shift = (bitcount_t) ((num_words * 2 * MG_UECC_WORD_BITS) -
+                                   mg_uecc_vli_numBits(mod, num_words));
+  wordcount_t word_shift = (wordcount_t) (shift / MG_UECC_WORD_BITS);
+  wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS);
+  mg_uecc_word_t carry = 0;
+  mg_uecc_vli_clear(mod_multiple, word_shift);
+  if (bit_shift > 0) {
+    for (index = 0; index < (mg_uecc_word_t) num_words; ++index) {
+      mod_multiple[(mg_uecc_word_t) word_shift + index] =
+          (mg_uecc_word_t) (mod[index] << bit_shift) | carry;
+      carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift);
     }
+  } else {
+    mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words);
   }
-  (void) skip_iotest;
-#elif MG_ENABLE_POLL
-  nfds_t n = 0;
-  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
-  struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
-  memset(fds, 0, n * sizeof(fds[0]));
-  n = 0;
-  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
-    c->is_readable = c->is_writable = 0;
-    if (skip_iotest(c)) {
-      // Socket not valid, ignore
-    } else if (mg_tls_pending(c) > 0) {
-      ms = 1;  // Don't wait if TLS is ready
-    } else {
-      fds[n].fd = FD(c);
-      if (can_read(c)) fds[n].events |= POLLIN;
-      if (can_write(c)) fds[n].events |= POLLOUT;
-      n++;
+
+  for (index = 1; shift >= 0; --shift) {
+    mg_uecc_word_t borrow = 0;
+    wordcount_t i;
+    for (i = 0; i < num_words * 2; ++i) {
+      mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow;
+      if (diff != v[index][i]) {
+        borrow = (diff > v[index][i]);
+      }
+      v[1 - index][i] = diff;
     }
+    index = !(index ^ borrow); /* Swap the index if there was no borrow */
+    mg_uecc_vli_rshift1(mod_multiple, num_words);
+    mod_multiple[num_words - 1] |= mod_multiple[num_words]
+                                   << (MG_UECC_WORD_BITS - 1);
+    mg_uecc_vli_rshift1(mod_multiple + num_words, num_words);
+  }
+  mg_uecc_vli_set(result, v[index], num_words);
+}
+
+/* Computes result = (left * right) % mod. */
+MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result,
+                                         const mg_uecc_word_t *left,
+                                         const mg_uecc_word_t *right,
+                                         const mg_uecc_word_t *mod,
+                                         wordcount_t num_words) {
+  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_vli_mult(product, left, right, num_words);
+  mg_uecc_vli_mmod(result, product, mod, num_words);
+}
+
+MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result,
+                                              const mg_uecc_word_t *left,
+                                              const mg_uecc_word_t *right,
+                                              MG_UECC_Curve curve) {
+  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_vli_mult(product, left, right, curve->num_words);
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+  curve->mmod_fast(result, product);
+#else
+  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
+#endif
+}
+
+#if MG_UECC_SQUARE_FUNC
+
+#if MG_UECC_ENABLE_VLI_API
+/* Computes result = left^2 % mod. */
+MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
+                                           const mg_uecc_word_t *left,
+                                           const mg_uecc_word_t *mod,
+                                           wordcount_t num_words) {
+  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_vli_square(product, left, num_words);
+  mg_uecc_vli_mmod(result, product, mod, num_words);
+}
+#endif /* MG_UECC_ENABLE_VLI_API */
+
+MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
+                                                const mg_uecc_word_t *left,
+                                                MG_UECC_Curve curve) {
+  mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS];
+  mg_uecc_vli_square(product, left, curve->num_words);
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+  curve->mmod_fast(result, product);
+#else
+  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
+#endif
+}
+
+#else /* MG_UECC_SQUARE_FUNC */
+
+#if MG_UECC_ENABLE_VLI_API
+MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result,
+                                           const mg_uecc_word_t *left,
+                                           const mg_uecc_word_t *mod,
+                                           wordcount_t num_words) {
+  mg_uecc_vli_modMult(result, left, left, mod, num_words);
+}
+#endif /* MG_UECC_ENABLE_VLI_API */
+
+MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result,
+                                                const mg_uecc_word_t *left,
+                                                MG_UECC_Curve curve) {
+  mg_uecc_vli_modMult_fast(result, left, left, curve);
+}
+
+#endif /* MG_UECC_SQUARE_FUNC */
+
+#define EVEN(vli) (!(vli[0] & 1))
+static void vli_modInv_update(mg_uecc_word_t *uv, const mg_uecc_word_t *mod,
+                              wordcount_t num_words) {
+  mg_uecc_word_t carry = 0;
+  if (!EVEN(uv)) {
+    carry = mg_uecc_vli_add(uv, uv, mod, num_words);
   }
+  mg_uecc_vli_rshift1(uv, num_words);
+  if (carry) {
+    uv[num_words - 1] |= HIGH_BIT_SET;
+  }
+}
 
-  // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
-  if (poll(fds, n, ms) < 0) {
-#if MG_ARCH == MG_ARCH_WIN32
-    if (n == 0) Sleep(ms);  // On Windows, poll fails if no sockets
-#endif
-    memset(fds, 0, n * sizeof(fds[0]));
+/* Computes result = (1 / input) % mod. All VLIs are the same size.
+   See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" */
+MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result,
+                                        const mg_uecc_word_t *input,
+                                        const mg_uecc_word_t *mod,
+                                        wordcount_t num_words) {
+  mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS],
+      u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS];
+  cmpresult_t cmpResult;
+
+  if (mg_uecc_vli_isZero(input, num_words)) {
+    mg_uecc_vli_clear(result, num_words);
+    return;
   }
-  n = 0;
-  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
-    if (skip_iotest(c)) {
-      // Socket not valid, ignore
-    } else if (mg_tls_pending(c) > 0) {
-      c->is_readable = 1;
+
+  mg_uecc_vli_set(a, input, num_words);
+  mg_uecc_vli_set(b, mod, num_words);
+  mg_uecc_vli_clear(u, num_words);
+  u[0] = 1;
+  mg_uecc_vli_clear(v, num_words);
+  while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) != 0) {
+    if (EVEN(a)) {
+      mg_uecc_vli_rshift1(a, num_words);
+      vli_modInv_update(u, mod, num_words);
+    } else if (EVEN(b)) {
+      mg_uecc_vli_rshift1(b, num_words);
+      vli_modInv_update(v, mod, num_words);
+    } else if (cmpResult > 0) {
+      mg_uecc_vli_sub(a, a, b, num_words);
+      mg_uecc_vli_rshift1(a, num_words);
+      if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) {
+        mg_uecc_vli_add(u, u, mod, num_words);
+      }
+      mg_uecc_vli_sub(u, u, v, num_words);
+      vli_modInv_update(u, mod, num_words);
     } else {
-      if (fds[n].revents & POLLERR) {
-        mg_error(c, "socket error");
-      } else {
-        c->is_readable =
-            (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
-        c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
+      mg_uecc_vli_sub(b, b, a, num_words);
+      mg_uecc_vli_rshift1(b, num_words);
+      if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) {
+        mg_uecc_vli_add(v, v, mod, num_words);
       }
-      n++;
+      mg_uecc_vli_sub(v, v, u, num_words);
+      vli_modInv_update(v, mod, num_words);
     }
   }
-#else
-  struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0};
-  struct mg_connection *c;
-  fd_set rset, wset, eset;
-  MG_SOCKET_TYPE maxfd = 0;
-  int rc;
+  mg_uecc_vli_set(result, u, num_words);
+}
 
-  FD_ZERO(&rset);
-  FD_ZERO(&wset);
-  FD_ZERO(&eset);
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    c->is_readable = c->is_writable = 0;
-    if (skip_iotest(c)) continue;
-    FD_SET(FD(c), &eset);
-    if (can_read(c)) FD_SET(FD(c), &rset);
-    if (can_write(c)) FD_SET(FD(c), &wset);
-    if (mg_tls_pending(c) > 0) tv = tv_zero;
-    if (FD(c) > maxfd) maxfd = FD(c);
+/* ------ Point operations ------ */
+
+/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */
+
+#ifndef _UECC_CURVE_SPECIFIC_H_
+#define _UECC_CURVE_SPECIFIC_H_
+
+#define num_bytes_secp160r1 20
+#define num_bytes_secp192r1 24
+#define num_bytes_secp224r1 28
+#define num_bytes_secp256r1 32
+#define num_bytes_secp256k1 32
+
+#if (MG_UECC_WORD_SIZE == 1)
+
+#define num_words_secp160r1 20
+#define num_words_secp192r1 24
+#define num_words_secp224r1 28
+#define num_words_secp256r1 32
+#define num_words_secp256k1 32
+
+#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) \
+  0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h
+#define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d
+
+#elif (MG_UECC_WORD_SIZE == 4)
+
+#define num_words_secp160r1 5
+#define num_words_secp192r1 6
+#define num_words_secp224r1 7
+#define num_words_secp256r1 8
+#define num_words_secp256k1 8
+
+#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 0x##h##g##f##e
+#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a
+
+#elif (MG_UECC_WORD_SIZE == 8)
+
+#define num_words_secp160r1 3
+#define num_words_secp192r1 3
+#define num_words_secp224r1 4
+#define num_words_secp256r1 4
+#define num_words_secp256k1 4
+
+#define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##h##g##f##e##d##c##b##a##U
+#define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U
+
+#endif /* MG_UECC_WORD_SIZE */
+
+#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
+    MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1
+static void double_jacobian_default(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                                    mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
+  /* t1 = X, t2 = Y, t3 = Z */
+  mg_uecc_word_t t4[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t t5[MG_UECC_MAX_WORDS];
+  wordcount_t num_words = curve->num_words;
+
+  if (mg_uecc_vli_isZero(Z1, num_words)) {
+    return;
   }
 
-  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) < 0) {
-#if MG_ARCH == MG_ARCH_WIN32
-    if (maxfd == 0) Sleep(ms);  // On Windows, select fails if no sockets
+  mg_uecc_vli_modSquare_fast(t4, Y1, curve);   /* t4 = y1^2 */
+  mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */
+  mg_uecc_vli_modSquare_fast(t4, t4, curve);   /* t4 = y1^4 */
+  mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */
+  mg_uecc_vli_modSquare_fast(Z1, Z1, curve);   /* t3 = z1^2 */
+
+  mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */
+  mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */
+  mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */
+  mg_uecc_vli_modMult_fast(X1, X1, Z1, curve);         /* t1 = x1^2 - z1^4 */
+
+  mg_uecc_vli_modAdd(Z1, X1, X1, curve->p,
+                     num_words); /* t3 = 2*(x1^2 - z1^4) */
+  mg_uecc_vli_modAdd(X1, X1, Z1, curve->p,
+                     num_words); /* t1 = 3*(x1^2 - z1^4) */
+  if (mg_uecc_vli_testBit(X1, 0)) {
+    mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p, num_words);
+    mg_uecc_vli_rshift1(X1, num_words);
+    X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1);
+  } else {
+    mg_uecc_vli_rshift1(X1, num_words);
+  }
+  /* t1 = 3/2*(x1^2 - z1^4) = B */
+
+  mg_uecc_vli_modSquare_fast(Z1, X1, curve);           /* t3 = B^2 */
+  mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */
+  mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */
+  mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */
+  mg_uecc_vli_modMult_fast(X1, X1, t5, curve);         /* t1 = B * (A - x3) */
+  mg_uecc_vli_modSub(t4, X1, t4, curve->p,
+                     num_words); /* t4 = B * (A - x3) - y1^4 = y3 */
+
+  mg_uecc_vli_set(X1, Z1, num_words);
+  mg_uecc_vli_set(Z1, Y1, num_words);
+  mg_uecc_vli_set(Y1, t4, num_words);
+}
+
+/* Computes result = x^3 + ax + b. result must not overlap x. */
+static void x_side_default(mg_uecc_word_t *result, const mg_uecc_word_t *x,
+                           MG_UECC_Curve curve) {
+  mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */
+  wordcount_t num_words = curve->num_words;
+
+  mg_uecc_vli_modSquare_fast(result, x, curve);                /* r = x^2 */
+  mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */
+  mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */
+  mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
+                     num_words); /* r = x^3 - 3x + b */
+}
+#endif /* MG_UECC_SUPPORTS_secp... */
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \
+    MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1
+/* Compute a = sqrt(a) (mod curve_p). */
+static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve curve) {
+  bitcount_t i;
+  mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1};
+  mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1};
+  wordcount_t num_words = curve->num_words;
+
+  /* When curve->p == 3 (mod 4), we can compute
+     sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */
+  mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */
+  for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) {
+    mg_uecc_vli_modSquare_fast(l_result, l_result, curve);
+    if (mg_uecc_vli_testBit(p1, i)) {
+      mg_uecc_vli_modMult_fast(l_result, l_result, a, curve);
+    }
+  }
+  mg_uecc_vli_set(a, l_result, num_words);
+}
+#endif /* MG_UECC_SUPPORTS_secp... */
+#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
+
+#if MG_UECC_SUPPORTS_secp160r1
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product);
+#endif
+
+static const struct MG_UECC_Curve_t curve_secp160r1 = {
+    num_words_secp160r1,
+    num_bytes_secp160r1,
+    161, /* num_n_bits */
+    {BYTES_TO_WORDS_8(FF, FF, FF, 7F, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(57, 22, 75, CA, D3, AE, 27, F9),
+     BYTES_TO_WORDS_8(C8, F4, 01, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, 01, 00, 00, 00)},
+    {BYTES_TO_WORDS_8(82, FC, CB, 13, B9, 8B, C3, 68),
+     BYTES_TO_WORDS_8(89, 69, 64, 46, 28, 73, F5, 8E),
+     BYTES_TO_WORDS_4(68, B5, 96, 4A),
+
+     BYTES_TO_WORDS_8(32, FB, C5, 7A, 37, 51, 23, 04),
+     BYTES_TO_WORDS_8(12, C9, DC, 59, 7D, 94, 68, 31),
+     BYTES_TO_WORDS_4(55, 28, A6, 23)},
+    {BYTES_TO_WORDS_8(45, FA, 65, C5, AD, D4, D4, 81),
+     BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54),
+     BYTES_TO_WORDS_4(FC, BE, 97, 1C)},
+    &double_jacobian_default,
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+    &mod_sqrt_default,
+#endif
+    &x_side_default,
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+    &vli_mmod_fast_secp160r1
+#endif
+};
+
+MG_UECC_Curve mg_uecc_secp160r1(void) {
+  return &curve_secp160r1;
+}
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1)
+/* Computes result = product % curve_p
+    see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354
+
+    Note that this only works if log2(omega) < log2(p) / 2 */
+static void omega_mult_secp160r1(mg_uecc_word_t *result,
+                                 const mg_uecc_word_t *right);
+#if MG_UECC_WORD_SIZE == 8
+static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product) {
+  mg_uecc_word_t tmp[2 * num_words_secp160r1];
+  mg_uecc_word_t copy;
+
+  mg_uecc_vli_clear(tmp, num_words_secp160r1);
+  mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
+
+  omega_mult_secp160r1(tmp,
+                       product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */
+
+  product[num_words_secp160r1 - 1] &= 0xffffffff;
+  copy = tmp[num_words_secp160r1 - 1];
+  tmp[num_words_secp160r1 - 1] &= 0xffffffff;
+  mg_uecc_vli_add(result, product, tmp,
+                  num_words_secp160r1); /* (C, r) = r + q */
+  mg_uecc_vli_clear(product, num_words_secp160r1);
+  tmp[num_words_secp160r1 - 1] = copy;
+  omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c */
+  mg_uecc_vli_add(result, result, product,
+                  num_words_secp160r1); /* (C1, r) = r + Rq*c */
+
+  while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p,
+                                num_words_secp160r1) > 0) {
+    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
+  }
+}
+
+static void omega_mult_secp160r1(uint64_t *result, const uint64_t *right) {
+  uint32_t carry;
+  unsigned i;
+
+  /* Multiply by (2^31 + 1). */
+  carry = 0;
+  for (i = 0; i < num_words_secp160r1; ++i) {
+    uint64_t tmp = (right[i] >> 32) | (right[i + 1] << 32);
+    result[i] = (tmp << 31) + tmp + carry;
+    carry = (tmp >> 33) + (result[i] < tmp || (carry && result[i] == tmp));
+  }
+  result[i] = carry;
+}
 #else
-    MG_ERROR(("select: %d %d", rc, MG_SOCKET_ERRNO));
+static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product) {
+  mg_uecc_word_t tmp[2 * num_words_secp160r1];
+  mg_uecc_word_t carry;
+
+  mg_uecc_vli_clear(tmp, num_words_secp160r1);
+  mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1);
+
+  omega_mult_secp160r1(tmp,
+                       product + num_words_secp160r1); /* (Rq, q) = q * c */
+
+  carry = mg_uecc_vli_add(result, product, tmp,
+                          num_words_secp160r1); /* (C, r) = r + q */
+  mg_uecc_vli_clear(product, num_words_secp160r1);
+  omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */
+  carry += mg_uecc_vli_add(result, result, product,
+                           num_words_secp160r1); /* (C1, r) = r + Rq*c */
+
+  while (carry > 0) {
+    --carry;
+    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
+  }
+  if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) >
+      0) {
+    mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1);
+  }
+}
 #endif
-    FD_ZERO(&rset);
-    FD_ZERO(&wset);
-    FD_ZERO(&eset);
+
+#if MG_UECC_WORD_SIZE == 1
+static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) {
+  uint8_t carry;
+  uint8_t i;
+
+  /* Multiply by (2^31 + 1). */
+  mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */
+  mg_uecc_vli_rshift1(result + 4, num_words_secp160r1);    /* 2^31 */
+  result[3] = right[0] << 7; /* get last bit from shift */
+
+  carry = mg_uecc_vli_add(result, result, right,
+                          num_words_secp160r1); /* 2^31 + 1 */
+  for (i = num_words_secp160r1; carry; ++i) {
+    uint16_t sum = (uint16_t) result[i] + carry;
+    result[i] = (uint8_t) sum;
+    carry = sum >> 8;
   }
+}
+#elif MG_UECC_WORD_SIZE == 4
+static void omega_mult_secp160r1(uint32_t *result, const uint32_t *right) {
+  uint32_t carry;
+  unsigned i;
 
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
-      mg_error(c, "socket error");
-    } else {
-      c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
-      c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
-      if (mg_tls_pending(c) > 0) c->is_readable = 1;
-    }
+  /* Multiply by (2^31 + 1). */
+  mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */
+  mg_uecc_vli_rshift1(result + 1, num_words_secp160r1);    /* 2^31 */
+  result[0] = right[0] << 31; /* get last bit from shift */
+
+  carry = mg_uecc_vli_add(result, result, right,
+                          num_words_secp160r1); /* 2^31 + 1 */
+  for (i = num_words_secp160r1; carry; ++i) {
+    uint64_t sum = (uint64_t) result[i] + carry;
+    result[i] = (uint32_t) sum;
+    carry = sum >> 32;
   }
+}
+#endif /* MG_UECC_WORD_SIZE */
+#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */
+
+#endif /* MG_UECC_SUPPORTS_secp160r1 */
+
+#if MG_UECC_SUPPORTS_secp192r1
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product);
 #endif
+
+static const struct MG_UECC_Curve_t curve_secp192r1 = {
+    num_words_secp192r1,
+    num_bytes_secp192r1,
+    192, /* num_n_bits */
+    {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(31, 28, D2, B4, B1, C9, 6B, 14),
+     BYTES_TO_WORDS_8(36, F8, DE, 99, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(12, 10, FF, 82, FD, 0A, FF, F4),
+     BYTES_TO_WORDS_8(00, 88, A1, 43, EB, 20, BF, 7C),
+     BYTES_TO_WORDS_8(F6, 90, 30, B0, 0E, A8, 8D, 18),
+
+     BYTES_TO_WORDS_8(11, 48, 79, 1E, A1, 77, F9, 73),
+     BYTES_TO_WORDS_8(D5, CD, 24, 6B, ED, 11, 10, 63),
+     BYTES_TO_WORDS_8(78, DA, C8, FF, 95, 2B, 19, 07)},
+    {BYTES_TO_WORDS_8(B1, B9, 46, C1, EC, DE, B8, FE),
+     BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F),
+     BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)},
+    &double_jacobian_default,
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+    &mod_sqrt_default,
+#endif
+    &x_side_default,
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+    &vli_mmod_fast_secp192r1
+#endif
+};
+
+MG_UECC_Curve mg_uecc_secp192r1(void) {
+  return &curve_secp192r1;
 }
 
-void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
-  struct mg_connection *c, *tmp;
-  uint64_t now;
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+/* Computes result = product % curve_p.
+   See algorithm 5 and 6 from http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf
+ */
+#if MG_UECC_WORD_SIZE == 1
+static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) {
+  uint8_t tmp[num_words_secp192r1];
+  uint8_t carry;
+
+  mg_uecc_vli_set(result, product, num_words_secp192r1);
+
+  mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1);
+  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
+  tmp[8] = product[24];
+  tmp[9] = product[25];
+  tmp[10] = product[26];
+  tmp[11] = product[27];
+  tmp[12] = product[28];
+  tmp[13] = product[29];
+  tmp[14] = product[30];
+  tmp[15] = product[31];
+  tmp[16] = product[32];
+  tmp[17] = product[33];
+  tmp[18] = product[34];
+  tmp[19] = product[35];
+  tmp[20] = product[36];
+  tmp[21] = product[37];
+  tmp[22] = product[38];
+  tmp[23] = product[39];
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  tmp[0] = tmp[8] = product[40];
+  tmp[1] = tmp[9] = product[41];
+  tmp[2] = tmp[10] = product[42];
+  tmp[3] = tmp[11] = product[43];
+  tmp[4] = tmp[12] = product[44];
+  tmp[5] = tmp[13] = product[45];
+  tmp[6] = tmp[14] = product[46];
+  tmp[7] = tmp[15] = product[47];
+  tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] =
+      tmp[23] = 0;
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
+                                         num_words_secp192r1) != 1) {
+    carry -=
+        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
+  }
+}
+#elif MG_UECC_WORD_SIZE == 4
+static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t *product) {
+  uint32_t tmp[num_words_secp192r1];
+  int carry;
 
-  mg_iotest(mgr, ms);
-  now = mg_millis();
-  mg_timer_poll(&mgr->timers, now);
+  mg_uecc_vli_set(result, product, num_words_secp192r1);
 
-  for (c = mgr->conns; c != NULL; c = tmp) {
-    bool is_resp = c->is_resp;
-    tmp = c->next;
-    mg_call(c, MG_EV_POLL, &now);
-    if (is_resp && !c->is_resp) {
-      long n = 0;
-      mg_call(c, MG_EV_READ, &n);
-    }
-    MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
-                c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
-                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
-                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
-    if (c->is_resolving || c->is_closing) {
-      // Do nothing
-    } else if (c->is_listening && c->is_udp == 0) {
-      if (c->is_readable) accept_conn(mgr, c);
-    } else if (c->is_connecting) {
-      if (c->is_readable || c->is_writable) connect_conn(c);
-    } else if (c->is_tls_hs) {
-      if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
-    } else {
-      if (c->is_readable) read_conn(c);
-      if (c->is_writable) write_conn(c);
-    }
+  mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1);
+  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
 
-    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
-    if (c->is_closing) close_conn(c);
+  tmp[0] = tmp[1] = 0;
+  tmp[2] = product[6];
+  tmp[3] = product[7];
+  tmp[4] = product[8];
+  tmp[5] = product[9];
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  tmp[0] = tmp[2] = product[10];
+  tmp[1] = tmp[3] = product[11];
+  tmp[4] = tmp[5] = 0;
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
+                                         num_words_secp192r1) != 1) {
+    carry -=
+        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
+  }
+}
+#else
+static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t *product) {
+  uint64_t tmp[num_words_secp192r1];
+  int carry;
+
+  mg_uecc_vli_set(result, product, num_words_secp192r1);
+
+  mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1);
+  carry = (int) mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  tmp[0] = 0;
+  tmp[1] = product[3];
+  tmp[2] = product[4];
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  tmp[0] = tmp[1] = product[5];
+  tmp[2] = 0;
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1);
+
+  while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result,
+                                         num_words_secp192r1) != 1) {
+    carry -=
+        mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1);
   }
 }
+#endif /* MG_UECC_WORD_SIZE */
+#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
+
+#endif /* MG_UECC_SUPPORTS_secp192r1 */
+
+#if MG_UECC_SUPPORTS_secp224r1
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve);
+#endif
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product);
 #endif
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/ssi.c"
+static const struct MG_UECC_Curve_t curve_secp224r1 = {
+    num_words_secp224r1,
+    num_bytes_secp224r1,
+    224, /* num_n_bits */
+    {BYTES_TO_WORDS_8(01, 00, 00, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(3D, 2A, 5C, 5C, 45, 29, DD, 13),
+     BYTES_TO_WORDS_8(3E, F0, B8, E0, A2, 16, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_4(FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(21, 1D, 5C, 11, D6, 80, 32, 34),
+     BYTES_TO_WORDS_8(22, 11, C2, 56, D3, C1, 03, 4A),
+     BYTES_TO_WORDS_8(B9, 90, 13, 32, 7F, BF, B4, 6B),
+     BYTES_TO_WORDS_4(BD, 0C, 0E, B7),
+
+     BYTES_TO_WORDS_8(34, 7E, 00, 85, 99, 81, D5, 44),
+     BYTES_TO_WORDS_8(64, 47, 07, 5A, A0, 75, 43, CD),
+     BYTES_TO_WORDS_8(E6, DF, 22, 4C, FB, 23, F7, B5),
+     BYTES_TO_WORDS_4(88, 63, 37, BD)},
+    {BYTES_TO_WORDS_8(B4, FF, 55, 23, 43, 39, 0B, 27),
+     BYTES_TO_WORDS_8(BA, D8, BF, D7, B7, B0, 44, 50),
+     BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C),
+     BYTES_TO_WORDS_4(85, 0A, 05, B4)},
+    &double_jacobian_default,
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+    &mod_sqrt_secp224r1,
 #endif
+    &x_side_default,
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+    &vli_mmod_fast_secp224r1
+#endif
+};
+
+MG_UECC_Curve mg_uecc_secp224r1(void) {
+  return &curve_secp224r1;
+}
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+/* Routine 3.2.4 RS;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
+                                  mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
+                                  const mg_uecc_word_t *e0,
+                                  const mg_uecc_word_t *f0) {
+  mg_uecc_word_t t[num_words_secp224r1];
+
+  mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1);    /* t <-- d0 ^ 2 */
+  mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */
+  mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p,
+                     num_words_secp224r1); /* d1 <-- t  + f0 */
+  mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p,
+                     num_words_secp224r1);               /* e1 <-- e1 + e1 */
+  mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t  * f0 */
+  mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
+                     num_words_secp224r1); /* f1 <-- f1 + f1 */
+  mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p,
+                     num_words_secp224r1); /* f1 <-- f1 + f1 */
+}
+
+/* Routine 3.2.5 RSS;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
+                                   mg_uecc_word_t *f1, const mg_uecc_word_t *d0,
+                                   const mg_uecc_word_t *e0,
+                                   const mg_uecc_word_t *f0,
+                                   const bitcount_t j) {
+  bitcount_t i;
+
+  mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */
+  mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */
+  mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */
+  for (i = 1; i <= j; i++) {
+    mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS (d1,e1,f1,d1,e1,f1) */
+  }
+}
+
+/* Routine 3.2.6 RM;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2, mg_uecc_word_t *e2,
+                                  mg_uecc_word_t *f2, const mg_uecc_word_t *c,
+                                  const mg_uecc_word_t *d0,
+                                  const mg_uecc_word_t *e0,
+                                  const mg_uecc_word_t *d1,
+                                  const mg_uecc_word_t *e1) {
+  mg_uecc_word_t t1[num_words_secp224r1];
+  mg_uecc_word_t t2[num_words_secp224r1];
+
+  mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */
+  mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1);  /* t1 <-- t1 * c */
+  /* t1 <-- p  - t1 */
+  mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p,
+                     num_words_secp224r1);
+  mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */
+  mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p,
+                     num_words_secp224r1);                /* t2 <-- t2 + t1 */
+  mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */
+  mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */
+  mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p,
+                     num_words_secp224r1);               /* e2 <-- e2 + t1 */
+  mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1);  /* f2 <-- e2^2 */
+  mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */
+  /* f2 <-- p  - f2 */
+  mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p,
+                     num_words_secp224r1);
+  mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */
+}
+
+/* Routine 3.2.7 RP;  from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1, mg_uecc_word_t *e1,
+                                  mg_uecc_word_t *f1, const mg_uecc_word_t *c,
+                                  const mg_uecc_word_t *r) {
+  wordcount_t i;
+  wordcount_t pow2i = 1;
+  mg_uecc_word_t d0[num_words_secp224r1];
+  mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */
+  mg_uecc_word_t f0[num_words_secp224r1];
+
+  mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */
+  /* f0 <-- p  - c */
+  mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p,
+                     num_words_secp224r1);
+  for (i = 0; i <= 6; i++) {
+    mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0,
+                           pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */
+    mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0,
+                          e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */
+    mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
+    mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
+    mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
+    pow2i *= 2;
+  }
+}
 
+/* Compute a = sqrt(a) (mod curve_p). */
+/* Routine 3.2.8 mp_mod_sqrt_224; from
+ * http://www.nsa.gov/ia/_files/nist-routines.pdf */
+static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve) {
+  (void) curve;
+  bitcount_t i;
+  mg_uecc_word_t e1[num_words_secp224r1];
+  mg_uecc_word_t f1[num_words_secp224r1];
+  mg_uecc_word_t d0[num_words_secp224r1];
+  mg_uecc_word_t e0[num_words_secp224r1];
+  mg_uecc_word_t f0[num_words_secp224r1];
+  mg_uecc_word_t d1[num_words_secp224r1];
+
+  /* s = a; using constant instead of random value */
+  mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */
+  mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
+                        f0); /* RS (d1, e1, f1, d0, e0, f0) */
+  for (i = 1; i <= 95; i++) {
+    mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */
+    mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */
+    mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */
+    mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0,
+                          f0); /* RS (d1, e1, f1, d0, e0, f0) */
+    if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */
+      break;
+    }
+  }
+  mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p,
+                     num_words_secp224r1);               /* f1 <-- 1 / e0 */
+  mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a  <-- d0 / e0 */
+}
+#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+/* Computes result = product % curve_p
+   from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+#if MG_UECC_WORD_SIZE == 1
+static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) {
+  uint8_t tmp[num_words_secp224r1];
+  int8_t carry;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp224r1);
+
+  /* s1 */
+  tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
+  tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
+  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
+  tmp[12] = product[28];
+  tmp[13] = product[29];
+  tmp[14] = product[30];
+  tmp[15] = product[31];
+  tmp[16] = product[32];
+  tmp[17] = product[33];
+  tmp[18] = product[34];
+  tmp[19] = product[35];
+  tmp[20] = product[36];
+  tmp[21] = product[37];
+  tmp[22] = product[38];
+  tmp[23] = product[39];
+  tmp[24] = product[40];
+  tmp[25] = product[41];
+  tmp[26] = product[42];
+  tmp[27] = product[43];
+  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* s2 */
+  tmp[12] = product[44];
+  tmp[13] = product[45];
+  tmp[14] = product[46];
+  tmp[15] = product[47];
+  tmp[16] = product[48];
+  tmp[17] = product[49];
+  tmp[18] = product[50];
+  tmp[19] = product[51];
+  tmp[20] = product[52];
+  tmp[21] = product[53];
+  tmp[22] = product[54];
+  tmp[23] = product[55];
+  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* d1 */
+  tmp[0] = product[28];
+  tmp[1] = product[29];
+  tmp[2] = product[30];
+  tmp[3] = product[31];
+  tmp[4] = product[32];
+  tmp[5] = product[33];
+  tmp[6] = product[34];
+  tmp[7] = product[35];
+  tmp[8] = product[36];
+  tmp[9] = product[37];
+  tmp[10] = product[38];
+  tmp[11] = product[39];
+  tmp[12] = product[40];
+  tmp[13] = product[41];
+  tmp[14] = product[42];
+  tmp[15] = product[43];
+  tmp[16] = product[44];
+  tmp[17] = product[45];
+  tmp[18] = product[46];
+  tmp[19] = product[47];
+  tmp[20] = product[48];
+  tmp[21] = product[49];
+  tmp[22] = product[50];
+  tmp[23] = product[51];
+  tmp[24] = product[52];
+  tmp[25] = product[53];
+  tmp[26] = product[54];
+  tmp[27] = product[55];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  /* d2 */
+  tmp[0] = product[44];
+  tmp[1] = product[45];
+  tmp[2] = product[46];
+  tmp[3] = product[47];
+  tmp[4] = product[48];
+  tmp[5] = product[49];
+  tmp[6] = product[50];
+  tmp[7] = product[51];
+  tmp[8] = product[52];
+  tmp[9] = product[53];
+  tmp[10] = product[54];
+  tmp[11] = product[55];
+  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
+  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
+  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
+  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  if (carry < 0) {
+    do {
+      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
+                               num_words_secp224r1);
+    } while (carry < 0);
+  } else {
+    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
+                                           num_words_secp224r1) != 1) {
+      carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
+                               num_words_secp224r1);
+    }
+  }
+}
+#elif MG_UECC_WORD_SIZE == 4
+static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) {
+  uint32_t tmp[num_words_secp224r1];
+  int carry;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp224r1);
+
+  /* s1 */
+  tmp[0] = tmp[1] = tmp[2] = 0;
+  tmp[3] = product[7];
+  tmp[4] = product[8];
+  tmp[5] = product[9];
+  tmp[6] = product[10];
+  carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* s2 */
+  tmp[3] = product[11];
+  tmp[4] = product[12];
+  tmp[5] = product[13];
+  tmp[6] = 0;
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* d1 */
+  tmp[0] = product[7];
+  tmp[1] = product[8];
+  tmp[2] = product[9];
+  tmp[3] = product[10];
+  tmp[4] = product[11];
+  tmp[5] = product[12];
+  tmp[6] = product[13];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  /* d2 */
+  tmp[0] = product[11];
+  tmp[1] = product[12];
+  tmp[2] = product[13];
+  tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0;
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  if (carry < 0) {
+    do {
+      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
+                               num_words_secp224r1);
+    } while (carry < 0);
+  } else {
+    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
+                                           num_words_secp224r1) != 1) {
+      carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p,
+                               num_words_secp224r1);
+    }
+  }
+}
+#else
+static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) {
+  uint64_t tmp[num_words_secp224r1];
+  int carry = 0;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp224r1);
+  result[num_words_secp224r1 - 1] &= 0xffffffff;
+
+  /* s1 */
+  tmp[0] = 0;
+  tmp[1] = product[3] & 0xffffffff00000000ull;
+  tmp[2] = product[4];
+  tmp[3] = product[5] & 0xffffffff;
+  mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* s2 */
+  tmp[1] = product[5] & 0xffffffff00000000ull;
+  tmp[2] = product[6];
+  tmp[3] = 0;
+  mg_uecc_vli_add(result, result, tmp, num_words_secp224r1);
+
+  /* d1 */
+  tmp[0] = (product[3] >> 32) | (product[4] << 32);
+  tmp[1] = (product[4] >> 32) | (product[5] << 32);
+  tmp[2] = (product[5] >> 32) | (product[6] << 32);
+  tmp[3] = product[6] >> 32;
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  /* d2 */
+  tmp[0] = (product[5] >> 32) | (product[6] << 32);
+  tmp[1] = product[6] >> 32;
+  tmp[2] = tmp[3] = 0;
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1);
+
+  if (carry < 0) {
+    do {
+      carry += mg_uecc_vli_add(result, result, curve_secp224r1.p,
+                               num_words_secp224r1);
+    } while (carry < 0);
+  } else {
+    while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result,
+                                  num_words_secp224r1) != 1) {
+      mg_uecc_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1);
+    }
+  }
+}
+#endif /* MG_UECC_WORD_SIZE */
+#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */
 
+#endif /* MG_UECC_SUPPORTS_secp224r1 */
 
+#if MG_UECC_SUPPORTS_secp256r1
 
-#ifndef MG_MAX_SSI_DEPTH
-#define MG_MAX_SSI_DEPTH 5
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product);
 #endif
 
-#ifndef MG_SSI_BUFSIZ
-#define MG_SSI_BUFSIZ 1024
+static const struct MG_UECC_Curve_t curve_secp256r1 = {
+    num_words_secp256r1,
+    num_bytes_secp256r1,
+    256, /* num_n_bits */
+    {BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(01, 00, 00, 00, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(51, 25, 63, FC, C2, CA, B9, F3),
+     BYTES_TO_WORDS_8(84, 9E, 17, A7, AD, FA, E6, BC),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(96, C2, 98, D8, 45, 39, A1, F4),
+     BYTES_TO_WORDS_8(A0, 33, EB, 2D, 81, 7D, 03, 77),
+     BYTES_TO_WORDS_8(F2, 40, A4, 63, E5, E6, BC, F8),
+     BYTES_TO_WORDS_8(47, 42, 2C, E1, F2, D1, 17, 6B),
+
+     BYTES_TO_WORDS_8(F5, 51, BF, 37, 68, 40, B6, CB),
+     BYTES_TO_WORDS_8(CE, 5E, 31, 6B, 57, 33, CE, 2B),
+     BYTES_TO_WORDS_8(16, 9E, 0F, 7C, 4A, EB, E7, 8E),
+     BYTES_TO_WORDS_8(9B, 7F, 1A, FE, E2, 42, E3, 4F)},
+    {BYTES_TO_WORDS_8(4B, 60, D2, 27, 3E, 3C, CE, 3B),
+     BYTES_TO_WORDS_8(F6, B0, 53, CC, B0, 06, 1D, 65),
+     BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3),
+     BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)},
+    &double_jacobian_default,
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+    &mod_sqrt_default,
+#endif
+    &x_side_default,
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+    &vli_mmod_fast_secp256r1
 #endif
+};
 
-#if MG_ENABLE_SSI
-static char *mg_ssi(const char *path, const char *root, int depth) {
-  struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE};
-  FILE *fp = fopen(path, "rb");
-  if (fp != NULL) {
-    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
-    int ch, intag = 0;
-    size_t len = 0;
-    buf[0] = arg[0] = '\0';
-    while ((ch = fgetc(fp)) != EOF) {
-      if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
-        buf[len++] = (char) (ch & 0xff);
-        buf[len] = '\0';
-        if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
-          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
-              *p = (char *) path + strlen(path), *data;
-          while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
-          mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
-          if (depth < MG_MAX_SSI_DEPTH &&
-              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_add(&b, b.len, data, strlen(data));
-            free(data);
-          } else {
-            MG_ERROR(("%s: file=%s error or too deep", path, arg));
-          }
-        } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
-          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
-          mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
-          if (depth < MG_MAX_SSI_DEPTH &&
-              (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
-            mg_iobuf_add(&b, b.len, data, strlen(data));
-            free(data);
-          } else {
-            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
-          }
-        } else {
-          // Unknown SSI tag
-          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
-          mg_iobuf_add(&b, b.len, buf, len);
-        }
-        intag = 0;
-        len = 0;
-      } else if (ch == '<') {
-        intag = 1;
-        if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
-        len = 0;
-        buf[len++] = (char) (ch & 0xff);
-      } else if (intag) {
-        if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
-          intag = 0;
-        } else if (len >= sizeof(buf) - 2) {
-          MG_ERROR(("%s: SSI tag is too large", path));
-          len = 0;
-        }
-        buf[len++] = (char) (ch & 0xff);
-      } else {
-        buf[len++] = (char) (ch & 0xff);
-        if (len >= sizeof(buf)) {
-          mg_iobuf_add(&b, b.len, buf, len);
-          len = 0;
-        }
-      }
+MG_UECC_Curve mg_uecc_secp256r1(void) {
+  return &curve_secp256r1;
+}
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1)
+/* Computes result = product % curve_p
+   from http://www.nsa.gov/ia/_files/nist-routines.pdf */
+#if MG_UECC_WORD_SIZE == 1
+static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) {
+  uint8_t tmp[num_words_secp256r1];
+  int8_t carry;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp256r1);
+
+  /* s1 */
+  tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
+  tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0;
+  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
+  tmp[12] = product[44];
+  tmp[13] = product[45];
+  tmp[14] = product[46];
+  tmp[15] = product[47];
+  tmp[16] = product[48];
+  tmp[17] = product[49];
+  tmp[18] = product[50];
+  tmp[19] = product[51];
+  tmp[20] = product[52];
+  tmp[21] = product[53];
+  tmp[22] = product[54];
+  tmp[23] = product[55];
+  tmp[24] = product[56];
+  tmp[25] = product[57];
+  tmp[26] = product[58];
+  tmp[27] = product[59];
+  tmp[28] = product[60];
+  tmp[29] = product[61];
+  tmp[30] = product[62];
+  tmp[31] = product[63];
+  carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s2 */
+  tmp[12] = product[48];
+  tmp[13] = product[49];
+  tmp[14] = product[50];
+  tmp[15] = product[51];
+  tmp[16] = product[52];
+  tmp[17] = product[53];
+  tmp[18] = product[54];
+  tmp[19] = product[55];
+  tmp[20] = product[56];
+  tmp[21] = product[57];
+  tmp[22] = product[58];
+  tmp[23] = product[59];
+  tmp[24] = product[60];
+  tmp[25] = product[61];
+  tmp[26] = product[62];
+  tmp[27] = product[63];
+  tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0;
+  carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s3 */
+  tmp[0] = product[32];
+  tmp[1] = product[33];
+  tmp[2] = product[34];
+  tmp[3] = product[35];
+  tmp[4] = product[36];
+  tmp[5] = product[37];
+  tmp[6] = product[38];
+  tmp[7] = product[39];
+  tmp[8] = product[40];
+  tmp[9] = product[41];
+  tmp[10] = product[42];
+  tmp[11] = product[43];
+  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
+  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
+  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
+  tmp[24] = product[56];
+  tmp[25] = product[57];
+  tmp[26] = product[58];
+  tmp[27] = product[59];
+  tmp[28] = product[60];
+  tmp[29] = product[61];
+  tmp[30] = product[62];
+  tmp[31] = product[63];
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s4 */
+  tmp[0] = product[36];
+  tmp[1] = product[37];
+  tmp[2] = product[38];
+  tmp[3] = product[39];
+  tmp[4] = product[40];
+  tmp[5] = product[41];
+  tmp[6] = product[42];
+  tmp[7] = product[43];
+  tmp[8] = product[44];
+  tmp[9] = product[45];
+  tmp[10] = product[46];
+  tmp[11] = product[47];
+  tmp[12] = product[52];
+  tmp[13] = product[53];
+  tmp[14] = product[54];
+  tmp[15] = product[55];
+  tmp[16] = product[56];
+  tmp[17] = product[57];
+  tmp[18] = product[58];
+  tmp[19] = product[59];
+  tmp[20] = product[60];
+  tmp[21] = product[61];
+  tmp[22] = product[62];
+  tmp[23] = product[63];
+  tmp[24] = product[52];
+  tmp[25] = product[53];
+  tmp[26] = product[54];
+  tmp[27] = product[55];
+  tmp[28] = product[32];
+  tmp[29] = product[33];
+  tmp[30] = product[34];
+  tmp[31] = product[35];
+  carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* d1 */
+  tmp[0] = product[44];
+  tmp[1] = product[45];
+  tmp[2] = product[46];
+  tmp[3] = product[47];
+  tmp[4] = product[48];
+  tmp[5] = product[49];
+  tmp[6] = product[50];
+  tmp[7] = product[51];
+  tmp[8] = product[52];
+  tmp[9] = product[53];
+  tmp[10] = product[54];
+  tmp[11] = product[55];
+  tmp[12] = tmp[13] = tmp[14] = tmp[15] = 0;
+  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
+  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
+  tmp[24] = product[32];
+  tmp[25] = product[33];
+  tmp[26] = product[34];
+  tmp[27] = product[35];
+  tmp[28] = product[40];
+  tmp[29] = product[41];
+  tmp[30] = product[42];
+  tmp[31] = product[43];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d2 */
+  tmp[0] = product[48];
+  tmp[1] = product[49];
+  tmp[2] = product[50];
+  tmp[3] = product[51];
+  tmp[4] = product[52];
+  tmp[5] = product[53];
+  tmp[6] = product[54];
+  tmp[7] = product[55];
+  tmp[8] = product[56];
+  tmp[9] = product[57];
+  tmp[10] = product[58];
+  tmp[11] = product[59];
+  tmp[12] = product[60];
+  tmp[13] = product[61];
+  tmp[14] = product[62];
+  tmp[15] = product[63];
+  tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0;
+  tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0;
+  tmp[24] = product[36];
+  tmp[25] = product[37];
+  tmp[26] = product[38];
+  tmp[27] = product[39];
+  tmp[28] = product[44];
+  tmp[29] = product[45];
+  tmp[30] = product[46];
+  tmp[31] = product[47];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d3 */
+  tmp[0] = product[52];
+  tmp[1] = product[53];
+  tmp[2] = product[54];
+  tmp[3] = product[55];
+  tmp[4] = product[56];
+  tmp[5] = product[57];
+  tmp[6] = product[58];
+  tmp[7] = product[59];
+  tmp[8] = product[60];
+  tmp[9] = product[61];
+  tmp[10] = product[62];
+  tmp[11] = product[63];
+  tmp[12] = product[32];
+  tmp[13] = product[33];
+  tmp[14] = product[34];
+  tmp[15] = product[35];
+  tmp[16] = product[36];
+  tmp[17] = product[37];
+  tmp[18] = product[38];
+  tmp[19] = product[39];
+  tmp[20] = product[40];
+  tmp[21] = product[41];
+  tmp[22] = product[42];
+  tmp[23] = product[43];
+  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
+  tmp[28] = product[48];
+  tmp[29] = product[49];
+  tmp[30] = product[50];
+  tmp[31] = product[51];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d4 */
+  tmp[0] = product[56];
+  tmp[1] = product[57];
+  tmp[2] = product[58];
+  tmp[3] = product[59];
+  tmp[4] = product[60];
+  tmp[5] = product[61];
+  tmp[6] = product[62];
+  tmp[7] = product[63];
+  tmp[8] = tmp[9] = tmp[10] = tmp[11] = 0;
+  tmp[12] = product[36];
+  tmp[13] = product[37];
+  tmp[14] = product[38];
+  tmp[15] = product[39];
+  tmp[16] = product[40];
+  tmp[17] = product[41];
+  tmp[18] = product[42];
+  tmp[19] = product[43];
+  tmp[20] = product[44];
+  tmp[21] = product[45];
+  tmp[22] = product[46];
+  tmp[23] = product[47];
+  tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0;
+  tmp[28] = product[52];
+  tmp[29] = product[53];
+  tmp[30] = product[54];
+  tmp[31] = product[55];
+  carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  if (carry < 0) {
+    do {
+      carry += mg_uecc_vli_add(result, result, curve_secp256r1.p,
+                               num_words_secp256r1);
+    } while (carry < 0);
+  } else {
+    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
+                                           num_words_secp256r1) != 1) {
+      carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p,
+                               num_words_secp256r1);
     }
-    if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
-    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1);  // nul-terminate
-    fclose(fp);
   }
-  (void) depth;
-  (void) root;
-  return (char *) b.buf;
 }
-
-void mg_http_serve_ssi(struct mg_connection *c, const char *root,
-                       const char *fullpath) {
-  const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
-  char *data = mg_ssi(fullpath, root, 0);
-  mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
-  free(data);
+#elif MG_UECC_WORD_SIZE == 4
+static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) {
+  uint32_t tmp[num_words_secp256r1];
+  int carry;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp256r1);
+
+  /* s1 */
+  tmp[0] = tmp[1] = tmp[2] = 0;
+  tmp[3] = product[11];
+  tmp[4] = product[12];
+  tmp[5] = product[13];
+  tmp[6] = product[14];
+  tmp[7] = product[15];
+  carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s2 */
+  tmp[3] = product[12];
+  tmp[4] = product[13];
+  tmp[5] = product[14];
+  tmp[6] = product[15];
+  tmp[7] = 0;
+  carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s3 */
+  tmp[0] = product[8];
+  tmp[1] = product[9];
+  tmp[2] = product[10];
+  tmp[3] = tmp[4] = tmp[5] = 0;
+  tmp[6] = product[14];
+  tmp[7] = product[15];
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s4 */
+  tmp[0] = product[9];
+  tmp[1] = product[10];
+  tmp[2] = product[11];
+  tmp[3] = product[13];
+  tmp[4] = product[14];
+  tmp[5] = product[15];
+  tmp[6] = product[13];
+  tmp[7] = product[8];
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* d1 */
+  tmp[0] = product[11];
+  tmp[1] = product[12];
+  tmp[2] = product[13];
+  tmp[3] = tmp[4] = tmp[5] = 0;
+  tmp[6] = product[8];
+  tmp[7] = product[10];
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d2 */
+  tmp[0] = product[12];
+  tmp[1] = product[13];
+  tmp[2] = product[14];
+  tmp[3] = product[15];
+  tmp[4] = tmp[5] = 0;
+  tmp[6] = product[9];
+  tmp[7] = product[11];
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d3 */
+  tmp[0] = product[13];
+  tmp[1] = product[14];
+  tmp[2] = product[15];
+  tmp[3] = product[8];
+  tmp[4] = product[9];
+  tmp[5] = product[10];
+  tmp[6] = 0;
+  tmp[7] = product[12];
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d4 */
+  tmp[0] = product[14];
+  tmp[1] = product[15];
+  tmp[2] = 0;
+  tmp[3] = product[9];
+  tmp[4] = product[10];
+  tmp[5] = product[11];
+  tmp[6] = 0;
+  tmp[7] = product[13];
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  if (carry < 0) {
+    do {
+      carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
+                                     num_words_secp256r1);
+    } while (carry < 0);
+  } else {
+    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
+                                           num_words_secp256r1) != 1) {
+      carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
+                                     num_words_secp256r1);
+    }
+  }
 }
 #else
-void mg_http_serve_ssi(struct mg_connection *c, const char *root,
-                       const char *fullpath) {
-  mg_http_reply(c, 501, NULL, "SSI not enabled");
-  (void) root, (void) fullpath;
+static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t *product) {
+  uint64_t tmp[num_words_secp256r1];
+  int carry;
+
+  /* t */
+  mg_uecc_vli_set(result, product, num_words_secp256r1);
+
+  /* s1 */
+  tmp[0] = 0;
+  tmp[1] = product[5] & 0xffffffff00000000U;
+  tmp[2] = product[6];
+  tmp[3] = product[7];
+  carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s2 */
+  tmp[1] = product[6] << 32;
+  tmp[2] = (product[6] >> 32) | (product[7] << 32);
+  tmp[3] = product[7] >> 32;
+  carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1);
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s3 */
+  tmp[0] = product[4];
+  tmp[1] = product[5] & 0xffffffff;
+  tmp[2] = 0;
+  tmp[3] = product[7];
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* s4 */
+  tmp[0] = (product[4] >> 32) | (product[5] << 32);
+  tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U);
+  tmp[2] = product[7];
+  tmp[3] = (product[6] >> 32) | (product[4] << 32);
+  carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1);
+
+  /* d1 */
+  tmp[0] = (product[5] >> 32) | (product[6] << 32);
+  tmp[1] = (product[6] >> 32);
+  tmp[2] = 0;
+  tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32);
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d2 */
+  tmp[0] = product[6];
+  tmp[1] = product[7];
+  tmp[2] = 0;
+  tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000);
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d3 */
+  tmp[0] = (product[6] >> 32) | (product[7] << 32);
+  tmp[1] = (product[7] >> 32) | (product[4] << 32);
+  tmp[2] = (product[4] >> 32) | (product[5] << 32);
+  tmp[3] = (product[6] << 32);
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  /* d4 */
+  tmp[0] = product[7];
+  tmp[1] = product[4] & 0xffffffff00000000U;
+  tmp[2] = product[5];
+  tmp[3] = product[6] & 0xffffffff00000000U;
+  carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1);
+
+  if (carry < 0) {
+    do {
+      carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p,
+                                     num_words_secp256r1);
+    } while (carry < 0);
+  } else {
+    while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result,
+                                           num_words_secp256r1) != 1) {
+      carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p,
+                                     num_words_secp256r1);
+    }
+  }
 }
-#endif
+#endif /* MG_UECC_WORD_SIZE */
+#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/str.c"
+#endif /* MG_UECC_SUPPORTS_secp256r1 */
+
+#if MG_UECC_SUPPORTS_secp256k1
+
+static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                                      mg_uecc_word_t *Z1, MG_UECC_Curve curve);
+static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x,
+                             MG_UECC_Curve curve);
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product);
 #endif
 
+static const struct MG_UECC_Curve_t curve_secp256k1 = {
+    num_words_secp256k1,
+    num_bytes_secp256k1,
+    256, /* num_n_bits */
+    {BYTES_TO_WORDS_8(2F, FC, FF, FF, FE, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(41, 41, 36, D0, 8C, 5E, D2, BF),
+     BYTES_TO_WORDS_8(3B, A0, 48, AF, E6, DC, AE, BA),
+     BYTES_TO_WORDS_8(FE, FF, FF, FF, FF, FF, FF, FF),
+     BYTES_TO_WORDS_8(FF, FF, FF, FF, FF, FF, FF, FF)},
+    {BYTES_TO_WORDS_8(98, 17, F8, 16, 5B, 81, F2, 59),
+     BYTES_TO_WORDS_8(D9, 28, CE, 2D, DB, FC, 9B, 02),
+     BYTES_TO_WORDS_8(07, 0B, 87, CE, 95, 62, A0, 55),
+     BYTES_TO_WORDS_8(AC, BB, DC, F9, 7E, 66, BE, 79),
+
+     BYTES_TO_WORDS_8(B8, D4, 10, FB, 8F, D0, 47, 9C),
+     BYTES_TO_WORDS_8(19, 54, 85, A6, 48, B4, 17, FD),
+     BYTES_TO_WORDS_8(A8, 08, 11, 0E, FC, FB, A4, 5D),
+     BYTES_TO_WORDS_8(65, C4, A3, 26, 77, DA, 3A, 48)},
+    {BYTES_TO_WORDS_8(07, 00, 00, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00),
+     BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)},
+    &double_jacobian_secp256k1,
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+    &mod_sqrt_default,
+#endif
+    &x_side_secp256k1,
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+    &vli_mmod_fast_secp256k1
+#endif
+};
 
-struct mg_str mg_str_s(const char *s) {
-  struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
-  return str;
+MG_UECC_Curve mg_uecc_secp256k1(void) {
+  return &curve_secp256k1;
 }
 
-struct mg_str mg_str_n(const char *s, size_t n) {
-  struct mg_str str = {s, n};
-  return str;
-}
+/* Double in place */
+static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                                      mg_uecc_word_t *Z1, MG_UECC_Curve curve) {
+  /* t1 = X, t2 = Y, t3 = Z */
+  mg_uecc_word_t t4[num_words_secp256k1];
+  mg_uecc_word_t t5[num_words_secp256k1];
 
-int mg_lower(const char *s) {
-  int c = *s;
-  if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
-  return c;
-}
+  if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) {
+    return;
+  }
 
-int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
-  int diff = 0;
-  if (len > 0) do {
-      diff = mg_lower(s1++) - mg_lower(s2++);
-    } while (diff == 0 && s1[-1] != '\0' && --len > 0);
-  return diff;
+  mg_uecc_vli_modSquare_fast(t5, Y1, curve);   /* t5 = y1^2 */
+  mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */
+  mg_uecc_vli_modSquare_fast(X1, X1, curve);   /* t1 = x1^2 */
+  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = y1^4 */
+  mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */
+
+  mg_uecc_vli_modAdd(Y1, X1, X1, curve->p,
+                     num_words_secp256k1); /* t2 = 2*x1^2 */
+  mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p,
+                     num_words_secp256k1); /* t2 = 3*x1^2 */
+  if (mg_uecc_vli_testBit(Y1, 0)) {
+    mg_uecc_word_t carry =
+        mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1);
+    mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
+    Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1);
+  } else {
+    mg_uecc_vli_rshift1(Y1, num_words_secp256k1);
+  }
+  /* t2 = 3/2*(x1^2) = B */
+
+  mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */
+  mg_uecc_vli_modSub(X1, X1, t4, curve->p,
+                     num_words_secp256k1); /* t1 = B^2 - A */
+  mg_uecc_vli_modSub(X1, X1, t4, curve->p,
+                     num_words_secp256k1); /* t1 = B^2 - 2A = x3 */
+
+  mg_uecc_vli_modSub(t4, t4, X1, curve->p,
+                     num_words_secp256k1);     /* t4 = A - x3 */
+  mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */
+  mg_uecc_vli_modSub(Y1, Y1, t5, curve->p,
+                     num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */
+}
+
+/* Computes result = x^3 + b. result must not overlap x. */
+static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x,
+                             MG_UECC_Curve curve) {
+  mg_uecc_vli_modSquare_fast(result, x, curve);       /* r = x^2 */
+  mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */
+  mg_uecc_vli_modAdd(result, result, curve->b, curve->p,
+                     num_words_secp256k1); /* r = x^3 + b */
+}
+
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1)
+static void omega_mult_secp256k1(mg_uecc_word_t *result,
+                                 const mg_uecc_word_t *right);
+static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result,
+                                    mg_uecc_word_t *product) {
+  mg_uecc_word_t tmp[2 * num_words_secp256k1];
+  mg_uecc_word_t carry;
+
+  mg_uecc_vli_clear(tmp, num_words_secp256k1);
+  mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1);
+
+  omega_mult_secp256k1(tmp,
+                       product + num_words_secp256k1); /* (Rq, q) = q * c */
+
+  carry = mg_uecc_vli_add(result, product, tmp,
+                          num_words_secp256k1); /* (C, r) = r + q       */
+  mg_uecc_vli_clear(product, num_words_secp256k1);
+  omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */
+  carry += mg_uecc_vli_add(result, result, product,
+                           num_words_secp256k1); /* (C1, r) = r + Rq*c */
+
+  while (carry > 0) {
+    --carry;
+    mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1);
+  }
+  if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) >
+      0) {
+    mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1);
+  }
 }
 
-int mg_casecmp(const char *s1, const char *s2) {
-  return mg_ncasecmp(s1, s2, (size_t) ~0);
+#if MG_UECC_WORD_SIZE == 1
+static void omega_mult_secp256k1(uint8_t *result, const uint8_t *right) {
+  /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
+  mg_uecc_word_t r0 = 0;
+  mg_uecc_word_t r1 = 0;
+  mg_uecc_word_t r2 = 0;
+  wordcount_t k;
+
+  /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
+  muladd(0xD1, right[0], &r0, &r1, &r2);
+  result[0] = r0;
+  r0 = r1;
+  r1 = r2;
+  /* r2 is still 0 */
+
+  for (k = 1; k < num_words_secp256k1; ++k) {
+    muladd(0x03, right[k - 1], &r0, &r1, &r2);
+    muladd(0xD1, right[k], &r0, &r1, &r2);
+    result[k] = r0;
+    r0 = r1;
+    r1 = r2;
+    r2 = 0;
+  }
+  muladd(0x03, right[num_words_secp256k1 - 1], &r0, &r1, &r2);
+  result[num_words_secp256k1] = r0;
+  result[num_words_secp256k1 + 1] = r1;
+  /* add the 2^32 multiple */
+  result[4 + num_words_secp256k1] =
+      mg_uecc_vli_add(result + 4, result + 4, right, num_words_secp256k1);
+}
+#elif MG_UECC_WORD_SIZE == 4
+static void omega_mult_secp256k1(uint32_t *result, const uint32_t *right) {
+  /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
+  uint32_t carry = 0;
+  wordcount_t k;
+
+  for (k = 0; k < num_words_secp256k1; ++k) {
+    uint64_t p = (uint64_t) 0x3D1 * right[k] + carry;
+    result[k] = (uint32_t) p;
+    carry = p >> 32;
+  }
+  result[num_words_secp256k1] = carry;
+  /* add the 2^32 multiple */
+  result[1 + num_words_secp256k1] =
+      mg_uecc_vli_add(result + 1, result + 1, right, num_words_secp256k1);
 }
-
-int mg_vcmp(const struct mg_str *s1, const char *s2) {
-  size_t n2 = strlen(s2), n1 = s1->len;
-  int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
-  if (r == 0) return (int) (n1 - n2);
-  return r;
+#else
+static void omega_mult_secp256k1(uint64_t *result, const uint64_t *right) {
+  mg_uecc_word_t r0 = 0;
+  mg_uecc_word_t r1 = 0;
+  mg_uecc_word_t r2 = 0;
+  wordcount_t k;
+
+  /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */
+  for (k = 0; k < num_words_secp256k1; ++k) {
+    muladd(0x1000003D1ull, right[k], &r0, &r1, &r2);
+    result[k] = r0;
+    r0 = r1;
+    r1 = r2;
+    r2 = 0;
+  }
+  result[num_words_secp256k1] = r0;
 }
+#endif /* MG_UECC_WORD_SIZE */
+#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 &&  && !asm_mmod_fast_secp256k1) */
 
-int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
-  size_t n2 = strlen(str2), n1 = str1->len;
-  int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
-  if (r == 0) return (int) (n1 - n2);
-  return r;
+#endif /* MG_UECC_SUPPORTS_secp256k1 */
+
+#endif /* _UECC_CURVE_SPECIFIC_H_ */
+
+/* Returns 1 if 'point' is the point at infinity, 0 otherwise. */
+#define EccPoint_isZero(point, curve) \
+  mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words * 2))
+
+/* Point multiplication algorithm using Montgomery's ladder with co-Z
+coordinates. From http://eprint.iacr.org/2011/338.pdf
+*/
+
+/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */
+static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                    const mg_uecc_word_t *const Z, MG_UECC_Curve curve) {
+  mg_uecc_word_t t1[MG_UECC_MAX_WORDS];
+
+  mg_uecc_vli_modSquare_fast(t1, Z, curve);    /* z^2 */
+  mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */
+  mg_uecc_vli_modMult_fast(t1, t1, Z, curve);  /* z^3 */
+  mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
 }
 
-struct mg_str mg_strdup(const struct mg_str s) {
-  struct mg_str r = {NULL, 0};
-  if (s.len > 0 && s.ptr != NULL) {
-    char *sc = (char *) calloc(1, s.len + 1);
-    if (sc != NULL) {
-      memcpy(sc, s.ptr, s.len);
-      sc[s.len] = '\0';
-      r.ptr = sc;
-      r.len = s.len;
-    }
+/* P = (x1, y1) => 2P, (x2, y2) => P' */
+static void XYcZ_initial_double(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                                mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
+                                const mg_uecc_word_t *const initial_Z,
+                                MG_UECC_Curve curve) {
+  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
+  wordcount_t num_words = curve->num_words;
+  if (initial_Z) {
+    mg_uecc_vli_set(z, initial_Z, num_words);
+  } else {
+    mg_uecc_vli_clear(z, num_words);
+    z[0] = 1;
   }
-  return r;
-}
 
-int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
-  size_t i = 0;
-  while (i < str1.len && i < str2.len) {
-    int c1 = str1.ptr[i];
-    int c2 = str2.ptr[i];
-    if (c1 < c2) return -1;
-    if (c1 > c2) return 1;
-    i++;
+  mg_uecc_vli_set(X2, X1, num_words);
+  mg_uecc_vli_set(Y2, Y1, num_words);
+
+  apply_z(X1, Y1, z, curve);
+  curve->double_jacobian(X1, Y1, z, curve);
+  apply_z(X2, Y2, z, curve);
+}
+
+/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
+   Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3)
+   or P => P', Q => P + Q
+*/
+static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, mg_uecc_word_t *X2,
+                     mg_uecc_word_t *Y2, MG_UECC_Curve curve) {
+  /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
+  mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
+  wordcount_t num_words = curve->num_words;
+
+  mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
+  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = (x2 - x1)^2 = A */
+  mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
+  mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
+  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
+  mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */
+
+  mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */
+  mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */
+  mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */
+  mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve);         /* t2 = y1*(C - B) */
+  mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */
+  mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */
+  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */
+
+  mg_uecc_vli_set(X2, t5, num_words);
+}
+
+/* Input P = (x1, y1, Z), Q = (x2, y2, Z)
+   Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3)
+   or P => P - Q, Q => P + Q
+*/
+static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1,
+                      mg_uecc_word_t *X2, mg_uecc_word_t *Y2,
+                      MG_UECC_Curve curve) {
+  /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
+  mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0};
+  mg_uecc_word_t t6[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t t7[MG_UECC_MAX_WORDS];
+  wordcount_t num_words = curve->num_words;
+
+  mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
+  mg_uecc_vli_modSquare_fast(t5, t5, curve);   /* t5 = (x2 - x1)^2 = A */
+  mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */
+  mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */
+  mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */
+  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */
+
+  mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */
+  mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */
+  mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */
+  mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */
+  mg_uecc_vli_modSub(X2, X2, t6, curve->p,
+                     num_words); /* t3 = D - (B + C) = x3 */
+
+  mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */
+  mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */
+  mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p,
+                     num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */
+
+  mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */
+  mg_uecc_vli_modSub(t7, t7, t6, curve->p,
+                     num_words); /* t7 = F - (B + C) = x3' */
+  mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */
+  mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */
+  mg_uecc_vli_modSub(Y1, t6, Y1, curve->p,
+                     num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */
+
+  mg_uecc_vli_set(X1, t7, num_words);
+}
+
+/* result may overlap point. */
+static void EccPoint_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
+                          const mg_uecc_word_t *scalar,
+                          const mg_uecc_word_t *initial_Z, bitcount_t num_bits,
+                          MG_UECC_Curve curve) {
+  /* R0 and R1 */
+  mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS];
+  mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS];
+  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
+  bitcount_t i;
+  mg_uecc_word_t nb;
+  wordcount_t num_words = curve->num_words;
+
+  mg_uecc_vli_set(Rx[1], point, num_words);
+  mg_uecc_vli_set(Ry[1], point + num_words, num_words);
+
+  XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve);
+
+  for (i = num_bits - 2; i > 0; --i) {
+    nb = !mg_uecc_vli_testBit(scalar, i);
+    XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
+    XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
+  }
+
+  nb = !mg_uecc_vli_testBit(scalar, 0);
+  XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);
+
+  /* Find final 1/Z value. */
+  mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */
+  mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve);        /* Yb * (X1 - X0) */
+  mg_uecc_vli_modMult_fast(z, z, point, curve);  /* xP * Yb * (X1 - X0) */
+  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */
+  /* yP / (xP * Yb * (X1 - X0)) */
+  mg_uecc_vli_modMult_fast(z, z, point + num_words, curve);
+  mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb],
+                           curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */
+  /* End 1/Z calculation */
+
+  XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);
+  apply_z(Rx[0], Ry[0], z, curve);
+
+  mg_uecc_vli_set(result, Rx[0], num_words);
+  mg_uecc_vli_set(result + num_words, Ry[0], num_words);
+}
+
+static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k,
+                                   mg_uecc_word_t *k0, mg_uecc_word_t *k1,
+                                   MG_UECC_Curve curve) {
+  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
+  bitcount_t num_n_bits = curve->num_n_bits;
+  mg_uecc_word_t carry =
+      mg_uecc_vli_add(k0, k, curve->n, num_n_words) ||
+      (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8) &&
+       mg_uecc_vli_testBit(k0, num_n_bits));
+  mg_uecc_vli_add(k1, k0, curve->n, num_n_words);
+  return carry;
+}
+
+/* Generates a random integer in the range 0 < random < top.
+   Both random and top have num_words words. */
+MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random,
+                                                const mg_uecc_word_t *top,
+                                                wordcount_t num_words) {
+  mg_uecc_word_t mask = (mg_uecc_word_t) -1;
+  mg_uecc_word_t tries;
+  bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words);
+
+  if (!g_rng_function) {
+    return 0;
+  }
+
+  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
+    if (!g_rng_function((uint8_t *) random,
+                        (unsigned int) (num_words * MG_UECC_WORD_SIZE))) {
+      return 0;
+    }
+    random[num_words - 1] &=
+        mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits));
+    if (!mg_uecc_vli_isZero(random, num_words) &&
+        mg_uecc_vli_cmp(top, random, num_words) == 1) {
+      return 1;
+    }
   }
-  if (i < str1.len) return 1;
-  if (i < str2.len) return -1;
   return 0;
 }
 
-const char *mg_strstr(const struct mg_str haystack,
-                      const struct mg_str needle) {
-  size_t i;
-  if (needle.len > haystack.len) return NULL;
-  for (i = 0; i <= haystack.len - needle.len; i++) {
-    if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
-      return haystack.ptr + i;
+static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t *result,
+                                                  mg_uecc_word_t *private_key,
+                                                  MG_UECC_Curve curve) {
+  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t *p2[2] = {tmp1, tmp2};
+  mg_uecc_word_t *initial_Z = 0;
+  mg_uecc_word_t carry;
+
+  /* Regularize the bitcount for the private key so that attackers cannot use a
+     side channel attack to learn the number of leading zeros. */
+  carry = regularize_k(private_key, tmp1, tmp2, curve);
+
+  /* If an RNG function was specified, try to get a random initial Z value to
+     improve protection against side-channel attacks. */
+  if (g_rng_function) {
+    if (!mg_uecc_generate_random_int(p2[carry], curve->p, curve->num_words)) {
+      return 0;
     }
+    initial_Z = p2[carry];
   }
-  return NULL;
+  EccPoint_mult(result, curve->G, p2[!carry], initial_Z,
+                (bitcount_t) (curve->num_n_bits + 1), curve);
+
+  if (EccPoint_isZero(result, curve)) {
+    return 0;
+  }
+  return 1;
 }
 
-static bool is_space(int c) {
-  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+#if MG_UECC_WORD_SIZE == 1
+
+MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
+                                               const uint8_t *native) {
+  wordcount_t i;
+  for (i = 0; i < num_bytes; ++i) {
+    bytes[i] = native[(num_bytes - 1) - i];
+  }
 }
 
-struct mg_str mg_strstrip(struct mg_str s) {
-  while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
-  while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
-  return s;
+MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native,
+                                               const uint8_t *bytes,
+                                               int num_bytes) {
+  mg_uecc_vli_nativeToBytes(native, num_bytes, bytes);
 }
 
-bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
-  size_t i = 0, j = 0, ni = 0, nj = 0;
-  if (caps) caps->ptr = NULL, caps->len = 0;
-  while (i < p.len || j < s.len) {
-    if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) {
-      if (caps == NULL) {
-      } else if (p.ptr[i] == '?') {
-        caps->ptr = &s.ptr[j], caps->len = 1;     // Finalize `?` cap
-        caps++, caps->ptr = NULL, caps->len = 0;  // Init next cap
-      } else if (caps->ptr != NULL && caps->len == 0) {
-        caps->len = (size_t) (&s.ptr[j] - caps->ptr);  // Finalize current cap
-        caps++, caps->len = 0, caps->ptr = NULL;       // Init next cap
-      }
-      i++, j++;
-    } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
-      if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];  // Init cap
-      ni = i++, nj = j + 1;
-    } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) {
-      i = ni, j = nj;
-      if (caps && caps->ptr == NULL && caps->len == 0) {
-        caps--, caps->len = 0;  // Restart previous cap
-      }
-    } else {
-      return false;
-    }
-  }
-  if (caps && caps->ptr && caps->len == 0) {
-    caps->len = (size_t) (&s.ptr[j] - caps->ptr);
+#else
+
+MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes,
+                                               const mg_uecc_word_t *native) {
+  int i;
+  for (i = 0; i < num_bytes; ++i) {
+    unsigned b = (unsigned) (num_bytes - 1 - i);
+    bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >>
+                          (8 * (b % MG_UECC_WORD_SIZE)));
   }
-  return true;
 }
 
-bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
-  return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
+MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native,
+                                               const uint8_t *bytes,
+                                               int num_bytes) {
+  int i;
+  mg_uecc_vli_clear(native,
+                    (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) /
+                                   MG_UECC_WORD_SIZE));
+  for (i = 0; i < num_bytes; ++i) {
+    unsigned b = (unsigned) (num_bytes - 1 - i);
+    native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i]
+                                     << (8 * (b % MG_UECC_WORD_SIZE));
+  }
 }
 
-static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff,
-                     size_t *klen, size_t *voff, size_t *vlen, char delim) {
-  size_t kvlen, kl;
-  for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;) kvlen++;
-  for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
-  if (koff != NULL) *koff = ofs;
-  if (klen != NULL) *klen = kl;
-  if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
-  if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
-  ofs += kvlen + 1;
-  return ofs > n ? n : ofs;
+#endif /* MG_UECC_WORD_SIZE */
+
+int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key,
+                     MG_UECC_Curve curve) {
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
+  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
+#else
+  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
+#endif
+  mg_uecc_word_t tries;
+
+  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
+    if (!mg_uecc_generate_random_int(_private, curve->n,
+                                     BITS_TO_WORDS(curve->num_n_bits))) {
+      return 0;
+    }
+
+    if (EccPoint_compute_public_key(_public, _private, curve)) {
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+      mg_uecc_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits),
+                                _private);
+      mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
+      mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes,
+                                _public + curve->num_words);
+#endif
+      return 1;
+    }
+  }
+  return 0;
 }
 
-bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char sep) {
-  size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
-  if (s->ptr == NULL || s->len == 0) return 0;
-  off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
-  if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
-  if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
-  *s = mg_str_n(s->ptr + off, s->len - off);
-  return off > 0;
+int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
+                          uint8_t *secret, MG_UECC_Curve curve) {
+  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
+  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
+
+  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t *p2[2] = {_private, tmp};
+  mg_uecc_word_t *initial_Z = 0;
+  mg_uecc_word_t carry;
+  wordcount_t num_words = curve->num_words;
+  wordcount_t num_bytes = curve->num_bytes;
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) _private, private_key, num_bytes);
+  bcopy((uint8_t *) _public, public_key, num_bytes * 2);
+#else
+  mg_uecc_vli_bytesToNative(_private, private_key,
+                            BITS_TO_BYTES(curve->num_n_bits));
+  mg_uecc_vli_bytesToNative(_public, public_key, num_bytes);
+  mg_uecc_vli_bytesToNative(_public + num_words, public_key + num_bytes,
+                            num_bytes);
+#endif
+
+  /* Regularize the bitcount for the private key so that attackers cannot use a
+     side channel attack to learn the number of leading zeros. */
+  carry = regularize_k(_private, _private, tmp, curve);
+
+  /* If an RNG function was specified, try to get a random initial Z value to
+     improve protection against side-channel attacks. */
+  if (g_rng_function) {
+    if (!mg_uecc_generate_random_int(p2[carry], curve->p, num_words)) {
+      return 0;
+    }
+    initial_Z = p2[carry];
+  }
+
+  EccPoint_mult(_public, _public, p2[!carry], initial_Z,
+                (bitcount_t) (curve->num_n_bits + 1), curve);
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes);
+#else
+  mg_uecc_vli_nativeToBytes(secret, num_bytes, _public);
+#endif
+  return !EccPoint_isZero(_public, curve);
 }
 
-bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) {
-  return mg_split(s, k, v, ',');
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed,
+                      MG_UECC_Curve curve) {
+  wordcount_t i;
+  for (i = 0; i < curve->num_bytes; ++i) {
+    compressed[i + 1] = public_key[i];
+  }
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01);
+#else
+  compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01);
+#endif
 }
 
-char *mg_hex(const void *buf, size_t len, char *to) {
-  const unsigned char *p = (const unsigned char *) buf;
-  const char *hex = "0123456789abcdef";
-  size_t i = 0;
-  for (; len--; p++) {
-    to[i++] = hex[p[0] >> 4];
-    to[i++] = hex[p[0] & 0x0f];
+void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key,
+                        MG_UECC_Curve curve) {
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *point = (mg_uecc_word_t *) public_key;
+#else
+  mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2];
+#endif
+  mg_uecc_word_t *y = point + curve->num_words;
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy(public_key, compressed + 1, curve->num_bytes);
+#else
+  mg_uecc_vli_bytesToNative(point, compressed + 1, curve->num_bytes);
+#endif
+  curve->x_side(y, point, curve);
+  curve->mod_sqrt(y, curve);
+
+  if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) {
+    mg_uecc_vli_sub(y, curve->p, y, curve->num_words);
   }
-  to[i] = '\0';
-  return to;
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+  mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point);
+  mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y);
+#endif
 }
+#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */
+
+MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t *point,
+                                        MG_UECC_Curve curve) {
+  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
+  wordcount_t num_words = curve->num_words;
+
+  /* The point at infinity is invalid. */
+  if (EccPoint_isZero(point, curve)) {
+    return 0;
+  }
 
-static unsigned char mg_unhex_nimble(unsigned char c) {
-  return (c >= '0' && c <= '9')   ? (unsigned char) (c - '0')
-         : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
-                                  : (unsigned char) (c - 'W');
+  /* x and y must be smaller than p. */
+  if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 ||
+      mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) {
+    return 0;
+  }
+
+  mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve);
+  curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */
+
+  /* Make sure that y^2 == x^3 + ax + b */
+  return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words));
 }
 
-unsigned long mg_unhexn(const char *s, size_t len) {
-  unsigned long i = 0, v = 0;
-  for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]);
-  return v;
+int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve) {
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
+#else
+  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
+#endif
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+  mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
+  mg_uecc_vli_bytesToNative(_public + curve->num_words,
+                            public_key + curve->num_bytes, curve->num_bytes);
+#endif
+  return mg_uecc_valid_point(_public, curve);
 }
 
-void mg_unhex(const char *buf, size_t len, unsigned char *to) {
-  size_t i;
-  for (i = 0; i < len; i += 2) {
-    to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
+int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key,
+                               MG_UECC_Curve curve) {
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key;
+  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
+#else
+  mg_uecc_word_t _private[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
+#endif
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+  mg_uecc_vli_bytesToNative(_private, private_key,
+                            BITS_TO_BYTES(curve->num_n_bits));
+#endif
+
+  /* Make sure the private key is in the range [1, n-1]. */
+  if (mg_uecc_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) {
+    return 0;
   }
-}
 
-uint64_t mg_tou64(struct mg_str str) {
-  uint64_t result = 0;
-  size_t i = 0;
-  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
-  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-    result *= 10;
-    result += (unsigned) (str.ptr[i] - '0');
-    i++;
+  if (mg_uecc_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) !=
+      1) {
+    return 0;
   }
-  return result;
+
+  /* Compute public key. */
+  if (!EccPoint_compute_public_key(_public, _private, curve)) {
+    return 0;
+  }
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+  mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public);
+  mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes,
+                            _public + curve->num_words);
+#endif
+  return 1;
 }
 
-int64_t mg_to64(struct mg_str str) {
-  int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
-  size_t i = 0;
-  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
-  if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
-  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-    if (result > max) return 0;
-    result *= 10;
-    result += (str.ptr[i] - '0');
-    i++;
+/* -------- ECDSA code -------- */
+
+static void bits2int(mg_uecc_word_t *native, const uint8_t *bits,
+                     unsigned bits_size, MG_UECC_Curve curve) {
+  unsigned num_n_bytes = (unsigned) BITS_TO_BYTES(curve->num_n_bits);
+  unsigned num_n_words = (unsigned) BITS_TO_WORDS(curve->num_n_bits);
+  int shift;
+  mg_uecc_word_t carry;
+  mg_uecc_word_t *ptr;
+
+  if (bits_size > num_n_bytes) {
+    bits_size = num_n_bytes;
+  }
+
+  mg_uecc_vli_clear(native, (wordcount_t) num_n_words);
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) native, bits, bits_size);
+#else
+  mg_uecc_vli_bytesToNative(native, bits, (int) bits_size);
+#endif
+  if (bits_size * 8 <= (unsigned) curve->num_n_bits) {
+    return;
+  }
+  shift = (int) bits_size * 8 - curve->num_n_bits;
+  carry = 0;
+  ptr = native + num_n_words;
+  while (ptr-- > native) {
+    mg_uecc_word_t temp = *ptr;
+    *ptr = (temp >> shift) | carry;
+    carry = temp << (MG_UECC_WORD_BITS - shift);
+  }
+
+  /* Reduce mod curve_n */
+  if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t) num_n_words) !=
+      1) {
+    mg_uecc_vli_sub(native, native, curve->n, (wordcount_t) num_n_words);
   }
-  return result * neg;
 }
 
-char *mg_remove_double_dots(char *s) {
-  char *saved = s, *p = s;
-  while (*s != '\0') {
-    *p++ = *s++;
-    if (s[-1] == '/' || s[-1] == '\\') {
-      while (s[0] != '\0') {
-        if (s[0] == '/' || s[0] == '\\') {
-          s++;
-        } else if (s[0] == '.' && s[1] == '.' &&
-                   (s[2] == '/' || s[2] == '\\')) {
-          s += 2;
-        } else {
-          break;
-        }
-      }
+static int mg_uecc_sign_with_k_internal(const uint8_t *private_key,
+                                        const uint8_t *message_hash,
+                                        unsigned hash_size, mg_uecc_word_t *k,
+                                        uint8_t *signature,
+                                        MG_UECC_Curve curve) {
+  mg_uecc_word_t tmp[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t s[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t *k2[2] = {tmp, s};
+  mg_uecc_word_t *initial_Z = 0;
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *p = (mg_uecc_word_t *) signature;
+#else
+  mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2];
+#endif
+  mg_uecc_word_t carry;
+  wordcount_t num_words = curve->num_words;
+  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
+  bitcount_t num_n_bits = curve->num_n_bits;
+
+  /* Make sure 0 < k < curve_n */
+  if (mg_uecc_vli_isZero(k, num_words) ||
+      mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) {
+    return 0;
+  }
+
+  carry = regularize_k(k, tmp, s, curve);
+  /* If an RNG function was specified, try to get a random initial Z value to
+     improve protection against side-channel attacks. */
+  if (g_rng_function) {
+    if (!mg_uecc_generate_random_int(k2[carry], curve->p, num_words)) {
+      return 0;
     }
+    initial_Z = k2[carry];
+  }
+  EccPoint_mult(p, curve->G, k2[!carry], initial_Z,
+                (bitcount_t) (num_n_bits + 1), curve);
+  if (mg_uecc_vli_isZero(p, num_words)) {
+    return 0;
   }
-  *p = '\0';
-  return saved;
-}
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/timer.c"
+  /* If an RNG function was specified, get a random number
+     to prevent side channel analysis of k. */
+  if (!g_rng_function) {
+    mg_uecc_vli_clear(tmp, num_n_words);
+    tmp[0] = 1;
+  } else if (!mg_uecc_generate_random_int(tmp, curve->n, num_n_words)) {
+    return 0;
+  }
+
+  /* Prevent side channel analysis of mg_uecc_vli_modInv() to determine
+     bits of k / the private key by premultiplying by a random number */
+  mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */
+  mg_uecc_vli_modInv(k, k, curve->n, num_n_words);       /* k = 1 / k' */
+  mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */
+
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0
+  mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */
 #endif
 
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits));
+#else
+  mg_uecc_vli_bytesToNative(tmp, private_key,
+                            BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */
+#endif
 
+  s[num_n_words - 1] = 0;
+  mg_uecc_vli_set(s, p, num_words);
+  mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */
 
-#define MG_TIMER_CALLED 4
+  bits2int(tmp, message_hash, hash_size, curve);
+  mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */
+  mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words);  /* s = (e + r*d) / k */
+  if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t) curve->num_bytes * 8) {
+    return 0;
+  }
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s,
+        curve->num_bytes);
+#else
+  mg_uecc_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s);
+#endif
+  return 1;
+}
 
-void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
-                   unsigned flags, void (*fn)(void *), void *arg) {
-  t->id = 0, t->period_ms = ms, t->expire = 0;
-  t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
-  *head = t;
+#if 0
+/* For testing - sign with an explicitly specified k value */
+int mg_uecc_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash,
+                     unsigned hash_size, const uint8_t *k, uint8_t *signature,
+                     MG_UECC_Curve curve) {
+  mg_uecc_word_t k2[MG_UECC_MAX_WORDS];
+  bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits), curve);
+  return mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k2,
+                                   signature, curve);
 }
+#endif
 
-void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
-  while (*head && *head != t) head = &(*head)->next;
-  if (*head) *head = t->next;
+int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash,
+                 unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) {
+  mg_uecc_word_t k[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tries;
+
+  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
+    if (!mg_uecc_generate_random_int(k, curve->n,
+                                     BITS_TO_WORDS(curve->num_n_bits))) {
+      return 0;
+    }
+
+    if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k,
+                                     signature, curve)) {
+      return 1;
+    }
+  }
+  return 0;
 }
 
-// t: expiration time, prd: period, now: current time. Return true if expired
-bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
-  if (now + prd < *t) *t = 0;                    // Time wrapped? Reset timer
-  if (*t == 0) *t = now + prd;                   // Firt poll? Set expiration
-  if (*t > now) return false;                    // Not expired yet, return
-  *t = (now - *t) > prd ? now + prd : *t + prd;  // Next expiration time
-  return true;                                   // Expired, return true
+/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always
+   the same size as the hash result size. */
+static void HMAC_init(const MG_UECC_HashContext *hash_context,
+                      const uint8_t *K) {
+  uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
+  unsigned i;
+  for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x36;
+  for (; i < hash_context->block_size; ++i) pad[i] = 0x36;
+
+  hash_context->init_hash(hash_context);
+  hash_context->update_hash(hash_context, pad, hash_context->block_size);
 }
 
-void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
-  struct mg_timer *t, *tmp;
-  for (t = *head; t != NULL; t = tmp) {
-    bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
-                !(t->flags & MG_TIMER_CALLED);  // Handle MG_TIMER_NOW only once
-    bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
-    tmp = t->next;
-    if (!once && !expired) continue;
-    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
-      t->fn(t->arg);
+static void HMAC_update(const MG_UECC_HashContext *hash_context,
+                        const uint8_t *message, unsigned message_size) {
+  hash_context->update_hash(hash_context, message, message_size);
+}
+
+static void HMAC_finish(const MG_UECC_HashContext *hash_context,
+                        const uint8_t *K, uint8_t *result) {
+  uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size;
+  unsigned i;
+  for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x5c;
+  for (; i < hash_context->block_size; ++i) pad[i] = 0x5c;
+
+  hash_context->finish_hash(hash_context, result);
+
+  hash_context->init_hash(hash_context);
+  hash_context->update_hash(hash_context, pad, hash_context->block_size);
+  hash_context->update_hash(hash_context, result, hash_context->result_size);
+  hash_context->finish_hash(hash_context, result);
+}
+
+/* V = HMAC_K(V) */
+static void update_V(const MG_UECC_HashContext *hash_context, uint8_t *K,
+                     uint8_t *V) {
+  HMAC_init(hash_context, K);
+  HMAC_update(hash_context, V, hash_context->result_size);
+  HMAC_finish(hash_context, K, V);
+}
+
+/* Deterministic signing, similar to RFC 6979. Differences are:
+    * We just use H(m) directly rather than bits2octets(H(m))
+      (it is not reduced modulo curve_n).
+    * We generate a value for k (aka T) directly rather than converting
+   endianness.
+
+   Layout of hash_context->tmp: <K> | <V> | (1 byte overlapped 0x00 or 0x01) /
+   <HMAC pad> */
+int mg_uecc_sign_deterministic(const uint8_t *private_key,
+                               const uint8_t *message_hash, unsigned hash_size,
+                               const MG_UECC_HashContext *hash_context,
+                               uint8_t *signature, MG_UECC_Curve curve) {
+  uint8_t *K = hash_context->tmp;
+  uint8_t *V = K + hash_context->result_size;
+  wordcount_t num_bytes = curve->num_bytes;
+  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
+  bitcount_t num_n_bits = curve->num_n_bits;
+  mg_uecc_word_t tries;
+  unsigned i;
+  for (i = 0; i < hash_context->result_size; ++i) {
+    V[i] = 0x01;
+    K[i] = 0;
+  }
+
+  /* K = HMAC_K(V || 0x00 || int2octets(x) || h(m)) */
+  HMAC_init(hash_context, K);
+  V[hash_context->result_size] = 0x00;
+  HMAC_update(hash_context, V, hash_context->result_size + 1);
+  HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
+  HMAC_update(hash_context, message_hash, hash_size);
+  HMAC_finish(hash_context, K, K);
+
+  update_V(hash_context, K, V);
+
+  /* K = HMAC_K(V || 0x01 || int2octets(x) || h(m)) */
+  HMAC_init(hash_context, K);
+  V[hash_context->result_size] = 0x01;
+  HMAC_update(hash_context, V, hash_context->result_size + 1);
+  HMAC_update(hash_context, private_key, (unsigned int) num_bytes);
+  HMAC_update(hash_context, message_hash, hash_size);
+  HMAC_finish(hash_context, K, K);
+
+  update_V(hash_context, K, V);
+
+  for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) {
+    mg_uecc_word_t T[MG_UECC_MAX_WORDS];
+    uint8_t *T_ptr = (uint8_t *) T;
+    wordcount_t T_bytes = 0;
+    for (;;) {
+      update_V(hash_context, K, V);
+      for (i = 0; i < hash_context->result_size; ++i) {
+        T_ptr[T_bytes++] = V[i];
+        if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) {
+          goto filled;
+        }
+      }
+    }
+  filled:
+    if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 > num_n_bits) {
+      mg_uecc_word_t mask = (mg_uecc_word_t) -1;
+      T[num_n_words - 1] &=
+          mask >>
+          ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits));
+    }
+
+    if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, T,
+                                     signature, curve)) {
+      return 1;
     }
-    t->flags |= MG_TIMER_CALLED;
+
+    /* K = HMAC_K(V || 0x00) */
+    HMAC_init(hash_context, K);
+    V[hash_context->result_size] = 0x00;
+    HMAC_update(hash_context, V, hash_context->result_size + 1);
+    HMAC_finish(hash_context, K, K);
+
+    update_V(hash_context, K, V);
   }
+  return 0;
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_dummy.c"
+static bitcount_t smax(bitcount_t a, bitcount_t b) {
+  return (a > b ? a : b);
+}
+
+int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash,
+                   unsigned hash_size, const uint8_t *signature,
+                   MG_UECC_Curve curve) {
+  mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t z[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2];
+  mg_uecc_word_t rx[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t ry[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tx[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t ty[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tz[MG_UECC_MAX_WORDS];
+  const mg_uecc_word_t *points[4];
+  const mg_uecc_word_t *point;
+  bitcount_t num_bits;
+  bitcount_t i;
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key;
+#else
+  mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2];
 #endif
+  mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS];
+  wordcount_t num_words = curve->num_words;
+  wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
 
+  rx[num_n_words - 1] = 0;
+  r[num_n_words - 1] = 0;
+  s[num_n_words - 1] = 0;
 
-#if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL && !MG_ENABLE_CUSTOM_TLS
-void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
-  (void) opts;
-  mg_error(c, "TLS is not enabled");
-}
-void mg_tls_handshake(struct mg_connection *c) {
-  (void) c;
-}
-void mg_tls_free(struct mg_connection *c) {
-  (void) c;
-}
-long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
-  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
-}
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
-}
-size_t mg_tls_pending(struct mg_connection *c) {
-  (void) c;
-  return 0;
-}
+#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN
+  bcopy((uint8_t *) r, signature, curve->num_bytes);
+  bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes);
+#else
+  mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes);
+  mg_uecc_vli_bytesToNative(_public + num_words, public_key + curve->num_bytes,
+                            curve->num_bytes);
+  mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes);
+  mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes);
 #endif
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_mbed.c"
-#endif
+  /* r, s must not be 0. */
+  if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s, num_words)) {
+    return 0;
+  }
 
+  /* r, s must be < n. */
+  if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 ||
+      mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) {
+    return 0;
+  }
 
+  /* Calculate u1 and u2. */
+  mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */
+  u1[num_n_words - 1] = 0;
+  bits2int(u1, message_hash, hash_size, curve);
+  mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */
+  mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words);  /* u2 = r/s */
+
+  /* Calculate sum = G + Q. */
+  mg_uecc_vli_set(sum, _public, num_words);
+  mg_uecc_vli_set(sum + num_words, _public + num_words, num_words);
+  mg_uecc_vli_set(tx, curve->G, num_words);
+  mg_uecc_vli_set(ty, curve->G + num_words, num_words);
+  mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */
+  XYcZ_add(tx, ty, sum, sum + num_words, curve);
+  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */
+  apply_z(sum, sum + num_words, z, curve);
+
+  /* Use Shamir's trick to calculate u1*G + u2*Q */
+  points[0] = 0;
+  points[1] = curve->G;
+  points[2] = _public;
+  points[3] = sum;
+  num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words),
+                  mg_uecc_vli_numBits(u2, num_n_words));
+  point =
+      points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) |
+             ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)];
+  mg_uecc_vli_set(rx, point, num_words);
+  mg_uecc_vli_set(ry, point + num_words, num_words);
+  mg_uecc_vli_clear(z, num_words);
+  z[0] = 1;
+
+  for (i = num_bits - 2; i >= 0; --i) {
+    mg_uecc_word_t index;
+    curve->double_jacobian(rx, ry, z, curve);
+
+    index = (!!mg_uecc_vli_testBit(u1, i)) |
+            (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1);
+    point = points[index];
+    if (point) {
+      mg_uecc_vli_set(tx, point, num_words);
+      mg_uecc_vli_set(ty, point + num_words, num_words);
+      apply_z(tx, ty, z, curve);
+      mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */
+      XYcZ_add(tx, ty, rx, ry, curve);
+      mg_uecc_vli_modMult_fast(z, z, tz, curve);
+    }
+  }
 
+  mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */
+  apply_z(rx, ry, z, curve);
 
-#if MG_ENABLE_MBEDTLS
+  /* v = x1 (mod n) */
+  if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) {
+    mg_uecc_vli_sub(rx, rx, curve->n, num_n_words);
+  }
 
-#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
-#define MGRNG , rng_get, NULL
-#else
-#define MGRNG
-#endif
+  /* Accept only if v == r. */
+  return (int) (mg_uecc_vli_equal(rx, r, num_words));
+}
 
-void mg_tls_free(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  if (tls != NULL) {
-    free(tls->cafile);
-    mbedtls_ssl_free(&tls->ssl);
-    mbedtls_pk_free(&tls->pk);
-    mbedtls_x509_crt_free(&tls->ca);
-    mbedtls_x509_crt_free(&tls->cert);
-    mbedtls_ssl_config_free(&tls->conf);
-    free(tls);
-    c->tls = NULL;
-  }
+#if MG_UECC_ENABLE_VLI_API
+
+unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) {
+  return curve->num_words;
 }
 
-static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
-  long n = mg_io_send((struct mg_connection *) ctx, buf, len);
-  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
-  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
-  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
-  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
-  return (int) n;
+unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) {
+  return curve->num_bytes;
 }
 
-static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
-  long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
-  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
-  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
-  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
-  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
-  return (int) n;
+unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) {
+  return curve->num_bytes * 8;
 }
 
-void mg_tls_handshake(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int rc = mbedtls_ssl_handshake(&tls->ssl);
-  if (rc == 0) {  // Success
-    MG_DEBUG(("%lu success", c->id));
-    c->is_tls_hs = 0;
-    mg_call(c, MG_EV_TLS_HS, NULL);
-  } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
-             rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
-    MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
-                c->is_tls_hs, rc, -rc));
-  } else {
-    mg_error(c, "TLS handshake: -%#x", -rc);  // Error
-  }
+unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) {
+  return BITS_TO_WORDS(curve->num_n_bits);
 }
 
-static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
-  mg_random(buf, len);
-  (void) ctx;
-  return 0;
+unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) {
+  return BITS_TO_BYTES(curve->num_n_bits);
 }
 
-static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
-  n = (int) strlen(s2) - 1;
-  MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
-  (void) s;
+unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) {
+  return curve->num_n_bits;
 }
 
-#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
-static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
-  (void) p_rng;
-  mg_random(buf, len);
-  return 0;
+const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) {
+  return curve->p;
 }
-#endif
 
-static struct mg_str mg_loadfile(struct mg_fs *fs, const char *path) {
-  size_t n = 0;
-  if (path[0] == '-') return mg_str(path);
-  char *p = mg_file_read(fs, path, &n);
-  return mg_str_n(p, n);
+const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) {
+  return curve->n;
 }
 
-void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
-  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
-  int rc = 0;
-  c->tls = tls;
-  if (c->tls == NULL) {
-    mg_error(c, "TLS OOM");
-    goto fail;
-  }
-  MG_DEBUG(("%lu Setting TLS", c->id));
-  mbedtls_ssl_init(&tls->ssl);
-  mbedtls_ssl_config_init(&tls->conf);
-  mbedtls_x509_crt_init(&tls->ca);
-  mbedtls_x509_crt_init(&tls->cert);
-  mbedtls_pk_init(&tls->pk);
-  mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
-#if defined(MG_MBEDTLS_DEBUG_LEVEL)
-  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
+const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) {
+  return curve->G;
+}
+
+const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) {
+  return curve->b;
+}
+
+#if MG_UECC_SUPPORT_COMPRESSED_POINT
+void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) {
+  curve->mod_sqrt(a, curve);
+}
 #endif
-  if ((rc = mbedtls_ssl_config_defaults(
-           &tls->conf,
-           c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
-           MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
-    mg_error(c, "tls defaults %#x", -rc);
-    goto fail;
-  }
-  mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
-  if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
-    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
-  } else if (opts->ca != NULL && opts->ca[0] != '\0') {
-#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
-    tls->cafile = strdup(opts->ca);
-    rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL);
-    if (rc != 0) {
-      mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
-      goto fail;
-    }
+
+void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product,
+                           MG_UECC_Curve curve) {
+#if (MG_UECC_OPTIMIZATION_LEVEL > 0)
+  curve->mmod_fast(result, product);
 #else
-    struct mg_str s = mg_loadfile(fs, opts->ca);
-    rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len + 1);
-    if (opts->ca[0] != '-') free((char *) s.ptr);
-    if (rc != 0) {
-      mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
-      goto fail;
-    }
-    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
+  mg_uecc_vli_mmod(result, product, curve->p, curve->num_words);
 #endif
-    if (opts->srvname.len > 0) {
-      char *x = mg_mprintf("%.*s", (int) opts->srvname.len, opts->srvname.ptr);
-      mbedtls_ssl_set_hostname(&tls->ssl, x);
-      free(x);
-    }
-    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
-  }
-  if (opts->cert != NULL && opts->cert[0] != '\0') {
-    struct mg_str s = mg_loadfile(fs, opts->cert);
-    const char *key = opts->certkey == NULL ? opts->cert : opts->certkey;
-    rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr, s.len + 1);
-    if (opts->cert[0] != '-') free((char *) s.ptr);
-    if (rc != 0) {
-      mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
-      goto fail;
-    }
-    s = mg_loadfile(fs, key);
-    rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len + 1, NULL,
-                              0 MGRNG);
-    if (key[0] != '-') free((char *) s.ptr);
-    if (rc != 0) {
-      mg_error(c, "tls key(%s) %#x", key, -rc);
-      goto fail;
-    }
-    rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
-    if (rc != 0) {
-      mg_error(c, "own cert %#x", -rc);
-      goto fail;
-    }
-  }
-  if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
-    mg_error(c, "setup err %#x", -rc);
-    goto fail;
-  }
-  c->tls = tls;
-  c->is_tls = 1;
-  c->is_tls_hs = 1;
-  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
-  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
-    mg_tls_handshake(c);
-  }
-  return;
-fail:
-  mg_tls_free(c);
 }
 
-size_t mg_tls_pending(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
-}
+void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point,
+                        const mg_uecc_word_t *scalar, MG_UECC_Curve curve) {
+  mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS];
+  mg_uecc_word_t *p2[2] = {tmp1, tmp2};
+  mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve);
 
-long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
-  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
-    return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
+  EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, curve);
 }
 
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
-  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
-    return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-#endif
+#endif  /* MG_UECC_ENABLE_VLI_API */
+#endif  // MG_TLS_BUILTIN
+// End of uecc BSD-2
 
 #ifdef MG_ENABLE_LINES
-#line 1 "src/tls_openssl.c"
+#line 1 "src/tls_x25519.c"
 #endif
+/**
+ * Adapted from STROBE: https://strobe.sourceforge.io/
+ * Copyright (c) 2015-2016 Cryptography Research, Inc.
+ * Author: Mike Hamburg
+ * License: MIT License
+ */
 
 
 
-#if MG_ENABLE_OPENSSL
-static int mg_tls_err(struct mg_tls *tls, int res) {
-  int err = SSL_get_error(tls->ssl, res);
-  // We've just fetched the last error from the queue.
-  // Now we need to clear the error queue. If we do not, then the following
-  // can happen (actually reported):
-  //  - A new connection is accept()-ed with cert error (e.g. self-signed cert)
-  //  - Since all accept()-ed connections share listener's context,
-  //  - *ALL* SSL accepted connection report read error on the next poll cycle.
-  //    Thus a single errored connection can close all the rest, unrelated ones.
-  // Clearing the error keeps the shared SSL_CTX in an OK state.
+const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9};
 
-  if (err != 0) ERR_print_errors_fp(stderr);
-  ERR_clear_error();
-  if (err == SSL_ERROR_WANT_READ) return 0;
-  if (err == SSL_ERROR_WANT_WRITE) return 0;
-  return err;
-}
+#define X25519_WBITS 32
 
-void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
-  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
-  const char *id = "mongoose";
-  static unsigned char s_initialised = 0;
-  int rc;
+typedef uint32_t limb_t;
+typedef uint64_t dlimb_t;
+typedef int64_t sdlimb_t;
 
-  if (tls == NULL) {
-    mg_error(c, "TLS OOM");
-    goto fail;
-  }
+#define NLIMBS (256 / X25519_WBITS)
+typedef limb_t mg_fe[NLIMBS];
 
-  if (!s_initialised) {
-    SSL_library_init();
-    s_initialised++;
-  }
-  MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
-            opts->ca == NULL ? "null" : opts->ca,
-            opts->cert == NULL ? "null" : opts->cert,
-            opts->certkey == NULL ? "null" : opts->certkey));
-  tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
-                          : SSL_CTX_new(SSLv23_server_method());
-  if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
-    mg_error(c, "SSL_new");
-    goto fail;
-  }
-  SSL_set_session_id_context(tls->ssl, (const uint8_t *) id,
-                             (unsigned) strlen(id));
-  // Disable deprecated protocols
-  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
-  SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
-  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
-  SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1);
-#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
-  SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
-#endif
-#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
-  SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
-#endif
+static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) {
+  dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry;
+  *carry = (limb_t) (tmp >> X25519_WBITS);
+  return (limb_t) tmp;
+}
 
-  if (opts->ca != NULL && opts->ca[0] != '\0') {
-    SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
-                   NULL);
-    if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 1) {
-      mg_error(c, "load('%s') %d err %d", opts->ca, rc, mg_tls_err(tls, rc));
-      goto fail;
-    }
-  }
-  if (opts->cert != NULL && opts->cert[0] != '\0') {
-    const char *key = opts->certkey;
-    if (key == NULL) key = opts->cert;
-    if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) != 1) {
-      mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
-      goto fail;
-    } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) != 1) {
-      mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
-      goto fail;
-#if OPENSSL_VERSION_NUMBER > 0x10100000L
-    } else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) !=
-               1) {
-      mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
-      goto fail;
-#endif
-    } else {
-      SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-#if OPENSSL_VERSION_NUMBER > 0x10002000L
-      SSL_set_ecdh_auto(tls->ssl, 1);
-#endif
-    }
-  }
-  if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, opts->ciphers);
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-  if (opts->srvname.len > 0) {
-    char *s = mg_mprintf("%.*s", (int) opts->srvname.len, opts->srvname.ptr);
-    SSL_set1_host(tls->ssl, s);
-    free(s);
-  }
-#endif
-  c->tls = tls;
-  c->is_tls = 1;
-  c->is_tls_hs = 1;
-  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
-    mg_tls_handshake(c);
-  }
-  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
-  return;
-fail:
-  c->is_closing = 1;
-  free(tls);
+// These functions are implemented in terms of umaal on ARM
+static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) {
+  dlimb_t total = (dlimb_t) *carry + acc + mand;
+  *carry = (limb_t) (total >> X25519_WBITS);
+  return (limb_t) total;
+}
+
+static limb_t adc0(limb_t *carry, limb_t acc) {
+  dlimb_t total = (dlimb_t) *carry + acc;
+  *carry = (limb_t) (total >> X25519_WBITS);
+  return (limb_t) total;
+}
+
+// - Precondition: carry is small.
+// - Invariant: result of propagate is < 2^255 + 1 word
+// - In particular, always less than 2p.
+// - Also, output x >= min(x,19)
+static void propagate(mg_fe x, limb_t over) {
+  unsigned i;
+  limb_t carry;
+  over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1;
+  x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1));
+
+  carry = over * 19;
+  for (i = 0; i < NLIMBS; i++) {
+    x[i] = adc0(&carry, x[i]);
+  }
 }
 
-void mg_tls_handshake(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int rc;
-  SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
-  rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
-  if (rc == 1) {
-    MG_DEBUG(("%lu success", c->id));
-    c->is_tls_hs = 0;
-    mg_call(c, MG_EV_TLS_HS, NULL);
-  } else {
-    int code = mg_tls_err(tls, rc);
-    if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
+static void add(mg_fe out, const mg_fe a, const mg_fe b) {
+  unsigned i;
+  limb_t carry = 0;
+  for (i = 0; i < NLIMBS; i++) {
+    out[i] = adc(&carry, a[i], b[i]);
   }
+  propagate(out, carry);
 }
 
-void mg_tls_free(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  if (tls == NULL) return;
-  SSL_free(tls->ssl);
-  SSL_CTX_free(tls->ctx);
-  free(tls);
-  c->tls = NULL;
+static void sub(mg_fe out, const mg_fe a, const mg_fe b) {
+  unsigned i;
+  sdlimb_t carry = -38;
+  for (i = 0; i < NLIMBS; i++) {
+    carry = carry + a[i] - b[i];
+    out[i] = (limb_t) carry;
+    carry >>= X25519_WBITS;
+  }
+  propagate(out, (limb_t) (1 + carry));
+}
+
+// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of `mg_fe`
+// to avoid build warnings
+static void mul(mg_fe out, const mg_fe a, const limb_t *b, unsigned nb) {
+  limb_t accum[2 * NLIMBS] = {0};
+  unsigned i, j;
+
+  limb_t carry2;
+  for (i = 0; i < nb; i++) {
+    limb_t mand = b[i];
+    carry2 = 0;
+    for (j = 0; j < NLIMBS; j++) {
+      limb_t tmp;                        // "a" may be misaligned
+      memcpy(&tmp, &a[j], sizeof(tmp));  // So make an aligned copy
+      accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp);
+    }
+    accum[i + j] = carry2;
+  }
+
+  carry2 = 0;
+  for (j = 0; j < NLIMBS; j++) {
+    out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]);
+  }
+  propagate(out, carry2);
 }
 
-size_t mg_tls_pending(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
+static void sqr(mg_fe out, const mg_fe a) {
+  mul(out, a, a, NLIMBS);
+}
+static void mul1(mg_fe out, const mg_fe a) {
+  mul(out, a, out, NLIMBS);
+}
+static void sqr1(mg_fe a) {
+  mul1(a, a);
 }
 
-long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int n = SSL_read(tls->ssl, buf, (int) len);
-  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
+static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS],
+                     limb_t doswap) {
+  unsigned i;
+  for (i = 0; i < 2 * NLIMBS; i++) {
+    limb_t xor_ab = (a[i] ^ b[i]) & doswap;
+    a[i] ^= xor_ab;
+    b[i] ^= xor_ab;
+  }
 }
 
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int n = SSL_write(tls->ssl, buf, (int) len);
-  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
+// Canonicalize a field element x, reducing it to the least residue which is
+// congruent to it mod 2^255-19
+// - Precondition: x < 2^255 + 1 word
+static limb_t canon(mg_fe x) {
+  // First, add 19.
+  unsigned i;
+  limb_t carry0 = 19;
+  limb_t res;
+  sdlimb_t carry;
+  for (i = 0; i < NLIMBS; i++) {
+    x[i] = adc0(&carry0, x[i]);
+  }
+  propagate(x, carry0);
+
+  // Here, 19 <= x2 < 2^255
+  // - This is because we added 19, so before propagate it can't be less
+  // than 19. After propagate, it still can't be less than 19, because if
+  // propagate does anything it adds 19.
+  // - We know that the high bit must be clear, because either the input was ~
+  // 2^255 + one word + 19 (in which case it propagates to at most 2 words) or
+  // it was < 2^255. So now, if we subtract 19, we will get back to something in
+  // [0,2^255-19).
+  carry = -19;
+  res = 0;
+  for (i = 0; i < NLIMBS; i++) {
+    carry += x[i];
+    res |= x[i] = (limb_t) carry;
+    carry >>= X25519_WBITS;
+  }
+  return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS);
+}
+
+static const limb_t a24[1] = {121665};
+
+static void ladder_part1(mg_fe xs[5]) {
+  limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
+  add(t1, x2, z2);                                 // t1 = A
+  sub(z2, x2, z2);                                 // z2 = B
+  add(x2, x3, z3);                                 // x2 = C
+  sub(z3, x3, z3);                                 // z3 = D
+  mul1(z3, t1);                                    // z3 = DA
+  mul1(x2, z2);                                    // x3 = BC
+  add(x3, z3, x2);                                 // x3 = DA+CB
+  sub(z3, z3, x2);                                 // z3 = DA-CB
+  sqr1(t1);                                        // t1 = AA
+  sqr1(z2);                                        // z2 = BB
+  sub(x2, t1, z2);                                 // x2 = E = AA-BB
+  mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0]));  // z2 = E*a24
+  add(z2, z2, t1);                                 // z2 = E*a24 + AA
+}
+
+static void ladder_part2(mg_fe xs[5], const mg_fe x1) {
+  limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4];
+  sqr1(z3);         // z3 = (DA-CB)^2
+  mul1(z3, x1);     // z3 = x1 * (DA-CB)^2
+  sqr1(x3);         // x3 = (DA+CB)^2
+  mul1(z2, x2);     // z2 = AA*(E*a24+AA)
+  sub(x2, t1, x2);  // x2 = BB again
+  mul1(x2, t1);     // x2 = AA*BB
+}
+
+static void x25519_core(mg_fe xs[5], const uint8_t scalar[X25519_BYTES],
+                        const uint8_t *x1, int clamp) {
+  int i;
+  mg_fe x1_limbs;
+  limb_t swap = 0;
+  limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3];
+  memset(xs, 0, 4 * sizeof(mg_fe));
+  x2[0] = z3[0] = 1;
+  for (i = 0; i < NLIMBS; i++) {
+    x3[i] = x1_limbs[i] =
+        MG_U32(x1[i * 4 + 3], x1[i * 4 + 2], x1[i * 4 + 1], x1[i * 4]);
+  }
+
+  for (i = 255; i >= 0; i--) {
+    uint8_t bytei = scalar[i / 8];
+    limb_t doswap;
+    if (clamp) {
+      if (i / 8 == 0) {
+        bytei &= (uint8_t) ~7U;
+      } else if (i / 8 == X25519_BYTES - 1) {
+        bytei &= 0x7F;
+        bytei |= 0x40;
+      }
+    }
+    doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1);
+    condswap(x2, x3, swap ^ doswap);
+    swap = doswap;
+
+    ladder_part1(xs);
+    ladder_part2(xs, (const limb_t *) x1_limbs);
+  }
+  condswap(x2, x3, swap);
+}
+
+int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES],
+                  const uint8_t x1[X25519_BYTES], int clamp) {
+  int i, ret;
+  mg_fe xs[5], out_limbs;
+  limb_t *x2, *z2, *z3, *prev;
+  static const struct {
+    uint8_t a, c, n;
+  } steps[13] = {{2, 1, 1},  {2, 1, 1},  {4, 2, 3},  {2, 4, 6},  {3, 1, 1},
+                 {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125},
+                 {3, 1, 2},  {3, 1, 2},  {3, 1, 1}};
+  x25519_core(xs, scalar, x1, clamp);
+
+  // Precomputed inversion chain
+  x2 = xs[0];
+  z2 = xs[1];
+  z3 = xs[3];
+
+  prev = z2;
+  for (i = 0; i < 13; i++) {
+    int j;
+    limb_t *a = xs[steps[i].a];
+    for (j = steps[i].n; j > 0; j--) {
+      sqr(a, prev);
+      prev = a;
+    }
+    mul1(a, xs[steps[i].c]);
+  }
+
+  // Here prev = z3
+  // x2 /= z2
+  mul(out_limbs, x2, z3, NLIMBS);
+  ret = (int) canon(out_limbs);
+  if (!clamp) ret = 0;
+  for (i = 0; i < NLIMBS; i++) {
+    uint32_t n = out_limbs[i];
+    out[i * 4] = (uint8_t) (n & 0xff);
+    out[i * 4 + 1] = (uint8_t) ((n >> 8) & 0xff);
+    out[i * 4 + 2] = (uint8_t) ((n >> 16) & 0xff);
+    out[i * 4 + 3] = (uint8_t) ((n >> 24) & 0xff);
+  }
+  return ret;
 }
-#endif
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/url.c"
@@ -5382,7 +14797,7 @@  struct url {
 int mg_url_is_ssl(const char *url) {
   return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 ||
          strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
-         strncmp(url, "tls:", 4) == 0;
+         strncmp(url, "tls:", 4) == 0 || strncmp(url, "tcps:", 5) == 0;
 }
 
 static struct url urlparse(const char *url) {
@@ -5464,6 +14879,14 @@  struct mg_str mg_url_pass(const char *url) {
 #endif
 
 
+// Not using memset for zeroing memory, cause it can be dropped by compiler
+// See https://github.com/cesanta/mongoose/pull/1265
+void mg_bzero(volatile unsigned char *buf, size_t len) {
+  if (buf != NULL) {
+    while (len--) *buf++ = 0;
+  }
+}
+
 #if MG_ENABLE_CUSTOM_RANDOM
 #else
 void mg_random(void *buf, size_t len) {
@@ -5512,11 +14935,16 @@  uint16_t mg_ntohs(uint16_t net) {
 }
 
 uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
-  int i;
+  static const uint32_t crclut[16] = {
+      // table for polynomial 0xEDB88320 (reflected)
+      0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4,
+      0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
+      0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
   crc = ~crc;
   while (len--) {
-    crc ^= *(unsigned char *) buf++;
-    for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1;
+    uint8_t b = *(uint8_t *) buf++;
+    crc = crclut[(crc ^ b) & 0x0F] ^ (crc >> 4);
+    crc = crclut[(crc ^ (b >> 4)) & 0x0F] ^ (crc >> 4);
   }
   return ~crc;
 }
@@ -5539,18 +14967,36 @@  static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
   return len;
 }
 
-int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
-  struct mg_str k, v;
+int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
+  struct mg_str entry;
   int allowed = acl.len == 0 ? '+' : '-';  // If any ACL is set, deny by default
-  while (mg_commalist(&acl, &k, &v)) {
-    uint32_t net, mask;
-    if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
-    if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
-    if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
+  uint32_t remote_ip4;
+  if (remote_ip->is_ip6) {
+    return -1;  // TODO(): handle IPv6 ACL and addresses
+  } else {      // IPv4
+    memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
+    while (mg_span(acl, &entry, &acl, ',')) {
+      uint32_t net, mask;
+      if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1;
+      if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2;
+      if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0];
+    }
   }
   return allowed == '+';
 }
 
+bool mg_path_is_sane(const struct mg_str path) {
+  const char *s = path.buf;
+  size_t n = path.len;
+  if (path.buf[0] == '.' && path.buf[1] == '.') return false;  // Starts with ..
+  for (; s[0] != '\0' && n > 0; s++, n--) {
+    if ((s[0] == '/' || s[0] == '\\') && n >= 2) {   // Subdir?
+      if (s[1] == '.' && s[2] == '.') return false;  // Starts with ..
+    }
+  }
+  return true;
+}
+
 #if MG_ENABLE_CUSTOM_MILLIS
 #else
 uint64_t mg_millis(void) {
@@ -5558,9 +15004,8 @@  uint64_t mg_millis(void) {
   return GetTickCount();
 #elif MG_ARCH == MG_ARCH_RP2040
   return time_us_64() / 1000;
-#elif MG_ARCH == MG_ARCH_ESP32
-  return esp_timer_get_time() / 1000;
-#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_FREERTOS
+#elif MG_ARCH == MG_ARCH_ESP8266 || MG_ARCH == MG_ARCH_ESP32 || \
+    MG_ARCH == MG_ARCH_FREERTOS
   return xTaskGetTickCount() * portTICK_PERIOD_MS;
 #elif MG_ARCH == MG_ARCH_AZURERTOS
   return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
@@ -5568,6 +15013,12 @@  uint64_t mg_millis(void) {
   return (uint64_t) Clock_getTicks();
 #elif MG_ARCH == MG_ARCH_ZEPHYR
   return (uint64_t) k_uptime_get();
+#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
+  return (uint64_t) rt_time_get();
+#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
+  return (uint64_t) ((osKernelGetTickCount() * 1000) / osKernelGetTickFreq());
+#elif MG_ARCH == MG_ARCH_RTTHREAD
+  return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
 #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
   // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on linux
   // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on linux
@@ -5595,7 +15046,6 @@  uint64_t mg_millis(void) {
 }
 #endif
 
-
 #ifdef MG_ENABLE_LINES
 #line 1 "src/ws.c"
 #endif
@@ -5609,6 +15059,7 @@  uint64_t mg_millis(void) {
 
 
 
+
 struct ws_msg {
   uint8_t flags;
   size_t header_len;
@@ -5640,10 +15091,10 @@  static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey,
 
   mg_sha1_ctx sha_ctx;
   mg_sha1_init(&sha_ctx);
-  mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len);
+  mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf, wskey->len);
   mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
   mg_sha1_final(sha, &sha_ctx);
-  mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
+  mg_base64_encode(sha, sizeof(sha), (char *) b64_sha, sizeof(b64_sha));
   mg_xprintf(mg_pfn_iobuf, &c->send,
              "HTTP/1.1 101 Switching Protocols\r\n"
              "Upgrade: websocket\r\n"
@@ -5653,7 +15104,7 @@  static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey,
   if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
   if (wsproto != NULL) {
     mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
-              wsproto->ptr);
+              wsproto->buf);
   }
   mg_send(c, "\r\n", 2);
 }
@@ -5746,12 +15197,15 @@  static bool mg_ws_client_handshake(struct mg_connection *c) {
     mg_error(c, "not http");  // Some just, not an HTTP request
   } else if (n > 0) {
     if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
-      mg_error(c, "handshake error");
+      mg_error(c, "ws handshake error");
     } else {
       struct mg_http_message hm;
-      mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
-      c->is_websocket = 1;
-      mg_call(c, MG_EV_WS_OPEN, &hm);
+      if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
+        c->is_websocket = 1;
+        mg_call(c, MG_EV_WS_OPEN, &hm);
+      } else {
+        mg_error(c, "ws handshake error");
+      }
     }
     mg_iobuf_del(&c->recv, 0, (size_t) n);
   } else {
@@ -5760,8 +15214,7 @@  static bool mg_ws_client_handshake(struct mg_connection *c) {
   return false;  // Continue event handler
 }
 
-static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
-                     void *fn_data) {
+static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) {
   struct ws_msg msg;
   size_t ofs = (size_t) c->pfn_data;
 
@@ -5775,7 +15228,7 @@  static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
       size_t len = msg.header_len + msg.data_len;
       uint8_t final = msg.flags & 128, op = msg.flags & 15;
       // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
-      //                       (int) m.data.len, (int) m.data.len, m.data.ptr));
+      //                       (int) m.data.len, (int) m.data.len, m.data.buf));
       switch (op) {
         case WEBSOCKET_OP_CONTINUE:
           mg_call(c, MG_EV_WS_CTL, &m);
@@ -5796,7 +15249,7 @@  static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
           MG_DEBUG(("%lu WS CLOSE", c->id));
           mg_call(c, MG_EV_WS_CTL, &m);
           // Echo the payload of the received CLOSE message back to the sender
-          mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
+          mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE);
           c->is_draining = 1;
           break;
         default:
@@ -5827,7 +15280,6 @@  static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
       }
     }
   }
-  (void) fn_data;
   (void) ev_data;
 }
 
@@ -5839,7 +15291,7 @@  struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
     char nonce[16], key[30];
     struct mg_str host = mg_url_host(url);
     mg_random(nonce, sizeof(nonce));
-    mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
+    mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key, sizeof(key));
     mg_xprintf(mg_pfn_iobuf, &c->send,
                "GET %s HTTP/1.1\r\n"
                "Upgrade: websocket\r\n"
@@ -5847,7 +15299,7 @@  struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
                "Connection: Upgrade\r\n"
                "Sec-WebSocket-Version: 13\r\n"
                "Sec-WebSocket-Key: %s\r\n",
-               mg_url_uri(url), (int) host.len, host.ptr, key);
+               mg_url_uri(url), (int) host.len, host.buf, key);
     if (fmt != NULL) {
       va_list ap;
       va_start(ap, fmt);
@@ -5896,1591 +15348,2247 @@  size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
 }
 
 #ifdef MG_ENABLE_LINES
-#line 1 "mip/driver_stm32.c"
+#line 1 "src/drivers/cmsis.c"
 #endif
+// https://arm-software.github.io/CMSIS_5/Driver/html/index.html
 
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CMSIS) && MG_ENABLE_DRIVER_CMSIS
 
-#if MG_ENABLE_MIP && \
-    (!defined(MG_ENABLE_DRIVER_TM4C) || MG_ENABLE_DRIVER_TM4C == 0)
-struct stm32_eth {
-  volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
-      MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
-      MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
-      MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
-      RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
-      RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
-      RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
-      PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
-      DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
-      DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
-      DMACHRBAR;
-};
-#undef ETH
-#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
 
-#undef BIT
-#define BIT(x) ((uint32_t) 1 << (x))
-#define ETH_PKT_SIZE 1540  // Max frame size
-#define ETH_DESC_CNT 4     // Descriptors count
-#define ETH_DS 4           // Descriptor size (words)
 
-static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
-static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
-static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
-static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
-static struct mip_if *s_ifp;                         // MIP interface
-enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 };     // PHY constants
 
-static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
-  ETH->MACMIIAR &= (7 << 2);
-  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
-  ETH->MACMIIAR |= BIT(0);
-  while (ETH->MACMIIAR & BIT(0)) (void) 0;
-  return ETH->MACMIIDR;
-}
 
-static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
-  ETH->MACMIIDR = val;
-  ETH->MACMIIAR &= (7 << 2);
-  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
-  ETH->MACMIIAR |= BIT(0);
-  while (ETH->MACMIIAR & BIT(0)) (void) 0;
-}
+extern ARM_DRIVER_ETH_MAC Driver_ETH_MAC0;
+extern ARM_DRIVER_ETH_PHY Driver_ETH_PHY0;
 
-static uint32_t get_hclk(void) {
-  struct rcc {
-    volatile uint32_t CR, PLLCFGR, CFGR;
-  } *rcc = (struct rcc *) 0x40023800;
-  uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */;
+static struct mg_tcpip_if *s_ifp;
 
-  if (rcc->CFGR & (1 << 2)) {
-    clk = hse;
-  } else if (rcc->CFGR & (1 << 3)) {
-    uint32_t vco, m, n, p;
-    m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
-    n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
-    p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
-    clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
-    vco = (uint32_t) ((uint64_t) clk * n / m);
-    clk = vco / p;
-  } else {
-    clk = hsi;
+static void mac_cb(uint32_t);
+static bool cmsis_init(struct mg_tcpip_if *);
+static bool cmsis_up(struct mg_tcpip_if *);
+static size_t cmsis_tx(const void *, size_t, struct mg_tcpip_if *);
+static size_t cmsis_rx(void *, size_t, struct mg_tcpip_if *);
+
+struct mg_tcpip_driver mg_tcpip_driver_cmsis = {cmsis_init, cmsis_tx, NULL,
+                                                cmsis_up};
+
+static bool cmsis_init(struct mg_tcpip_if *ifp) {
+  ARM_ETH_MAC_ADDR addr;
+  s_ifp = ifp;
+
+  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
+  ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
+  ARM_ETH_MAC_CAPABILITIES cap = mac->GetCapabilities();
+  if (mac->Initialize(mac_cb) != ARM_DRIVER_OK) return false;
+  if (phy->Initialize(mac->PHY_Read, mac->PHY_Write) != ARM_DRIVER_OK)
+    return false;
+  if (cap.event_rx_frame == 0)  // polled mode driver
+    mg_tcpip_driver_cmsis.rx = cmsis_rx;
+  mac->PowerControl(ARM_POWER_FULL);
+  if (cap.mac_address) {  // driver provides MAC address
+    mac->GetMacAddress(&addr);
+    memcpy(ifp->mac, &addr, sizeof(ifp->mac));
+  } else {  // we provide MAC address
+    memcpy(&addr, ifp->mac, sizeof(addr));
+    mac->SetMacAddress(&addr);
   }
-  uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
-  if (hpre < 8) return clk;
+  phy->PowerControl(ARM_POWER_FULL);
+  phy->SetInterface(cap.media_interface);
+  phy->SetMode(ARM_ETH_PHY_AUTO_NEGOTIATE);
+  return true;
+}
 
-  uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
-  return ((uint32_t) clk) >> ahbptab[hpre - 8];
+static size_t cmsis_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
+  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
+  if (mac->SendFrame(buf, (uint32_t) len, 0) != ARM_DRIVER_OK) {
+    ifp->nerr++;
+    return 0;
+  }
+  ifp->nsent++;
+  return len;
 }
 
-//  Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3,
-//  it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived
-//  from the HSI (internal RC), and it can go above specs, the datasheets
-//  specify a range of frequencies and activate one of a series of dividers to
-//  keep the MDC clock safely below 2.5MHz. We guess a divider setting based on
-//  HCLK with a +5% drift. If the user uses a different clock from our
-//  defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
-//  (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
-static int guess_mdc_cr(void) {
-  uint8_t crs[] = {2, 3, 0, 1, 4, 5};          // ETH->MACMIIAR::CR values
-  uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
-  uint32_t hclk = get_hclk();                  // Guess system HCLK
-  int result = -1;                             // Invalid CR value
-  if (hclk < 25000000) {
-    MG_ERROR(("HCLK too low"));
-  } else {
-    for (int i = 0; i < 6; i++) {
-      if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
-        result = crs[i];
-        break;
+static bool cmsis_up(struct mg_tcpip_if *ifp) {
+  ARM_DRIVER_ETH_PHY *phy = &Driver_ETH_PHY0;
+  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
+  bool up = (phy->GetLinkState() == ARM_ETH_LINK_UP) ? 1 : 0;  // link state
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {             // just went up
+    ARM_ETH_LINK_INFO st = phy->GetLinkInfo();
+    mac->Control(ARM_ETH_MAC_CONFIGURE,
+                 (st.speed << ARM_ETH_MAC_SPEED_Pos) |
+                     (st.duplex << ARM_ETH_MAC_DUPLEX_Pos) |
+                     ARM_ETH_MAC_ADDRESS_BROADCAST);
+    MG_DEBUG(("Link is %uM %s-duplex",
+              (st.speed == 2) ? 1000
+              : st.speed      ? 100
+                              : 10,
+              st.duplex ? "full" : "half"));
+    mac->Control(ARM_ETH_MAC_CONTROL_TX, 1);
+    mac->Control(ARM_ETH_MAC_CONTROL_RX, 1);
+  } else if ((ifp->state != MG_TCPIP_STATE_DOWN) && !up) {  // just went down
+    mac->Control(ARM_ETH_MAC_FLUSH,
+                 ARM_ETH_MAC_FLUSH_TX | ARM_ETH_MAC_FLUSH_RX);
+    mac->Control(ARM_ETH_MAC_CONTROL_TX, 0);
+    mac->Control(ARM_ETH_MAC_CONTROL_RX, 0);
+  }
+  return up;
+}
+
+static void mac_cb(uint32_t ev) {
+  if ((ev & ARM_ETH_MAC_EVENT_RX_FRAME) == 0) return;
+  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
+  uint32_t len = mac->GetRxFrameSize();  // CRC already stripped
+  if (len >= 60 && len <= 1518) {        // proper frame
+    char *p;
+    if (mg_queue_book(&s_ifp->recv_queue, &p, len) >= len) {  // have room
+      if ((len = mac->ReadFrame((uint8_t *) p, len)) > 0) {   // copy succeeds
+        mg_queue_add(&s_ifp->recv_queue, len);
+        s_ifp->nrecv++;
       }
+      return;
     }
-    if (result < 0) MG_ERROR(("HCLK too high"));
+    s_ifp->ndrop++;
   }
-  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
-  return result;
+  mac->ReadFrame(NULL, 0);  // otherwise, discard
+}
+
+static size_t cmsis_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
+  ARM_DRIVER_ETH_MAC *mac = &Driver_ETH_MAC0;
+  uint32_t len = mac->GetRxFrameSize();  // CRC already stripped
+  if (len >= 60 && len <= 1518 &&
+      ((len = mac->ReadFrame(buf, (uint32_t) buflen)) > 0))
+    return len;
+  if (len > 0) mac->ReadFrame(NULL, 0);  // discard bad frames
+  (void) ifp;
+  return 0;
 }
 
-static bool mip_driver_stm32_init(struct mip_if *ifp) {
-  struct mip_driver_stm32_data *d = (struct mip_driver_stm32_data *) ifp->driver_data;
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/imxrt.c"
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT
+struct imxrt_enet {
+  volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR, RESERVED2[3],
+      ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC, RESERVED5[7], RCR,
+      RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0, TXIC1, TXIC2,
+      RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR, GALR,
+      RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL, RSEM,
+      RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC, RACC,
+      RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT,
+      RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG,
+      RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127, RMON_T_P128TO255,
+      RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048, RMON_T_GTE2048,
+      RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL, IEEE_T_MCOL,
+      IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR, IEEE_T_CSERR,
+      IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3], RMON_R_PACKETS,
+      RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE,
+      RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64,
+      RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511, RMON_R_P512TO1023,
+      RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP,
+      IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR, IEEE_R_FDXFC,
+      IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER, ATCOR, ATINC,
+      ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1, TCSR2, TCCR2,
+      TCSR3;
+};
+
+#undef ENET
+#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11
+#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U)
+#define ETH_DESC_CNT 5     // Descriptors count
+#else
+#define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U)
+#define ETH_DESC_CNT 4     // Descriptors count
+#endif
+
+#define ETH_PKT_SIZE 1536  // Max frame size, 64-bit aligned
+
+struct enet_desc {
+  uint16_t length;   // Data length
+  uint16_t control;  // Control and status
+  uint32_t *buffer;  // Data ptr
+};
+
+// TODO(): handle these in a portable compiler-independent CMSIS-friendly way
+#define MG_64BYTE_ALIGNED __attribute__((aligned((64U))))
+
+// Descriptors: in non-cached area (TODO(scaprile)), (37.5.1.22.2 37.5.1.23.2)
+// Buffers: 64-byte aligned (37.3.14)
+static volatile struct enet_desc s_rxdesc[ETH_DESC_CNT] MG_64BYTE_ALIGNED;
+static volatile struct enet_desc s_txdesc[ETH_DESC_CNT] MG_64BYTE_ALIGNED;
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED;
+static struct mg_tcpip_if *s_ifp;  // MIP interface
+
+static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
+  ENET->EIR |= MG_BIT(23);  // MII interrupt clear
+  ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18) | (2 << 16);
+  while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
+  return ENET->MMFR & 0xffff;
+}
+
+static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  ENET->EIR |= MG_BIT(23);  // MII interrupt clear
+  ENET->MMFR =
+      (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16) | val;
+  while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0;
+}
+
+//  MDC clock is generated from IPS Bus clock (ipg_clk); as per 802.3,
+//  it must not exceed 2.5MHz
+// The PHY receives the PLL6-generated 50MHz clock
+static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_imxrt_data *d =
+      (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
   s_ifp = ifp;
 
   // Init RX descriptors
   for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_rxdesc[i][0] = BIT(31);                            // Own
-    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14);       // 2nd address chained
-    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
-    s_rxdesc[i][3] =
-        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+    s_rxdesc[i].control = MG_BIT(15);              // Own (E)
+    s_rxdesc[i].buffer = (uint32_t *) s_rxbuf[i];  // Point to data buffer
   }
+  s_rxdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13);  // Wrap last descriptor
 
   // Init TX descriptors
   for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
-    s_txdesc[i][3] =
-        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+    // s_txdesc[i].control = MG_BIT(10);  // Own (TC)
+    s_txdesc[i].buffer = (uint32_t *) s_txbuf[i];
   }
-
-  ETH->DMABMR |= BIT(0);                         // Software reset
-  while ((ETH->DMABMR & BIT(0)) != 0) (void) 0;  // Wait until done
-
-  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
-  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
-  ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
-
-  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
-  // hardware checksum. Therefore, descriptor size is 4, not 8
-  // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
-  ETH->MACIMR = BIT(3) | BIT(9);  // Mask timestamp & PMT IT
-  ETH->MACFCR = BIT(7);           // Disable zero quarta pause
-  // ETH->MACFFR = BIT(31);                            // Receive all
-  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15));           // Reset PHY
-  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12));           // Set autonegotiation
-  ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc;     // RX descriptors
-  ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc;     // RX descriptors
-  ETH->DMAIER = BIT(6) | BIT(16);                      // RIE, NISE
-  ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14);    // RE, TE, Duplex, Fast
-  ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
-
-  // MAC address filtering
-  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
-  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
-                 ((uint32_t) ifp->mac[2] << 16) |
-                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
-  if (ifp->queue.len == 0) ifp->queue.len = 8192;
+  s_txdesc[ETH_DESC_CNT - 1].control |= MG_BIT(13);  // Wrap last descriptor
+
+  ENET->ECR = MG_BIT(0);                     // Software reset, disable
+  while ((ENET->ECR & MG_BIT(0))) (void) 0;  // Wait until done
+
+  // Set MDC clock divider. If user told us the value, use it.
+  // TODO(): Otherwise, guess (currently assuming max freq)
+  int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr;
+  ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1);  // HOLDTIME 2 clks
+  struct mg_phy phy = {enet_read_phy, enet_write_phy};
+  mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC clocks PHY  
+  // Select RMII mode, 100M, keep CRC, set max rx length, disable loop
+  ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2);
+  // ENET->RCR |= MG_BIT(3);     // Receive all
+  ENET->TCR = MG_BIT(2);  // Full-duplex
+  ENET->RDSR = (uint32_t) (uintptr_t) s_rxdesc;
+  ENET->TDSR = (uint32_t) (uintptr_t) s_txdesc;
+  ENET->MRBR[0] = ETH_PKT_SIZE;  // Same size for RX/TX buffers
+  // MAC address filtering (bytes in reversed order)
+  ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t) ifp->mac[5] << 16U;
+  ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) |
+               ((uint32_t) ifp->mac[1] << 16U) |
+               ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
+  ENET->ECR = MG_BIT(8) | MG_BIT(1);  // Little-endian CPU, Enable
+  ENET->EIMR = MG_BIT(25);            // Set interrupt mask
+  ENET->RDAR = MG_BIT(24);            // Receive Descriptors have changed
+  ENET->TDAR = MG_BIT(24);            // Transmit Descriptors have changed
+  // ENET->OPD = 0x10014;
   return true;
 }
 
-static uint32_t s_txno;
-static size_t mip_driver_stm32_tx(const void *buf, size_t len, struct mip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
+// Transmit frame
+static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len,
+                                       struct mg_tcpip_if *ifp) {
+  static int s_txno;  // Current descriptor index
+  if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
     MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // Frame is too big
-  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
-    MG_ERROR(("No free descriptors"));
-    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
-    len = 0;  // All descriptors are busy, fail
+    len = (size_t) -1;  // fail
+  } else if ((s_txdesc[s_txno].control & MG_BIT(15))) {
+    ifp->nerr++;
+    MG_ERROR(("No descriptors available"));
+    len = 0;  // retry later
   } else {
-    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
-    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
-    s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30);  // Chain,FS,LS
-    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
+    memcpy(s_txbuf[s_txno], buf, len);         // Copy data
+    s_txdesc[s_txno].length = (uint16_t) len;  // Set data len
+    // Table 37-34, R, L, TC (Ready, last, transmit CRC after frame
+    s_txdesc[s_txno].control |=
+        (uint16_t) (MG_BIT(15) | MG_BIT(11) | MG_BIT(10));
+    ENET->TDAR = MG_BIT(24);  // Descriptor ring updated
     if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
   }
-  ETH->DMASR = BIT(2) | BIT(5);  // Clear any prior TBUS/TUS
-  ETH->DMATPDR = 0;              // and resume
-  return len;
   (void) ifp;
+  return len;
 }
 
-static bool mip_driver_stm32_up(struct mip_if *ifp) {
-  uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
-  (void) ifp;
-  return bsr & BIT(2) ? 1 : 0;
+static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_imxrt_data *d =
+      (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {enet_read_phy, enet_write_phy};
+  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    // tmp = reg with flags set to the most likely situation: 100M full-duplex
+    // if(link is slow or half) set flags otherwise
+    // reg = tmp
+    uint32_t tcr = ENET->TCR | MG_BIT(2);             // Full-duplex
+    uint32_t rcr = ENET->RCR & ~MG_BIT(9);            // 100M
+    if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9);  // 10M
+    if (full_duplex == false) tcr &= ~MG_BIT(2);      // Half-duplex
+    ENET->TCR = tcr;  // IRQ handler does not fiddle with these registers
+    ENET->RCR = rcr;
+    MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100,
+              tcr & MG_BIT(2) ? "full" : "half"));
+  }
+  return up;
 }
 
-void ETH_IRQHandler(void);
+void ENET_IRQHandler(void);
 static uint32_t s_rxno;
-void ETH_IRQHandler(void) {
-  qp_mark(QP_IRQTRIGGERED, 0);
-  if (ETH->DMASR & BIT(6)) {             // Frame received, loop
-    ETH->DMASR = BIT(16) | BIT(6);       // Clear flag
-    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
-      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
-      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
-          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
-        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
-        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
-        //  ETH->DMASR);
-        mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
-      }
-      s_rxdesc[s_rxno][0] = BIT(31);
-      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+void ENET_IRQHandler(void) {
+  ENET->EIR = MG_BIT(25);  // Ack IRQ
+  // Frame received, loop
+  for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
+    uint32_t r = s_rxdesc[s_rxno].control;
+    if (r & MG_BIT(15)) break;  // exit when done
+    // skip partial/errored frames (Table 37-32)
+    if ((r & MG_BIT(11)) &&
+        !(r & (MG_BIT(5) | MG_BIT(4) | MG_BIT(2) | MG_BIT(1) | MG_BIT(0)))) {
+      size_t len = s_rxdesc[s_rxno].length;
+      mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
     }
+    s_rxdesc[s_rxno].control |= MG_BIT(15);
+    if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
   }
-  ETH->DMASR = BIT(7);  // Clear possible RBUS while processing
-  ETH->DMARPDR = 0;     // and resume RX
+  ENET->RDAR = MG_BIT(24);  // Receive Descriptors have changed
+  // If b24 == 0, descriptors were exhausted and probably frames were dropped
 }
 
-struct mip_driver mip_driver_stm32 = {
-    mip_driver_stm32_init, mip_driver_stm32_tx, mip_driver_rx, mip_driver_stm32_up};
+struct mg_tcpip_driver mg_tcpip_driver_imxrt = {mg_tcpip_driver_imxrt_init,
+                                                mg_tcpip_driver_imxrt_tx, NULL,
+                                                mg_tcpip_driver_imxrt_up};
+
 #endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "mip/driver_tm4c.c"
+#line 1 "src/drivers/phy.c"
 #endif
 
 
-#if MG_ENABLE_MIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
-struct tm4c_emac {
-  volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
-      EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS,
-      EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
-      EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
-      EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
-      EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
-      EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
-      EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR,
-      EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
-      EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC,
-      EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
-      EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
-      RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
-      EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
-      EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
-      RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
-      RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
-      EMACEPHYIM, EMACEPHYIMSC;
+enum {                      // ID1  ID2
+  MG_PHY_KSZ8x = 0x22,      // 0022 1561 - KSZ8081RNB
+  MG_PHY_DP83x = 0x2000,    // 2000 a140 - TI DP83825I
+  MG_PHY_DP83867 = 0xa231,  // 2000 a231 - TI DP83867I
+  MG_PHY_LAN87x = 0x7,      // 0007 c0fx - LAN8720
+  MG_PHY_RTL8201 = 0x1C     // 001c c816 - RTL8201
+};
+
+enum {
+  MG_PHY_REG_BCR = 0,
+  MG_PHY_REG_BSR = 1,
+  MG_PHY_REG_ID1 = 2,
+  MG_PHY_REG_ID2 = 3,
+  MG_PHY_DP83x_REG_PHYSTS = 16,
+  MG_PHY_DP83867_REG_PHYSTS = 17,
+  MG_PHY_DP83x_REG_RCSR = 23,
+  MG_PHY_DP83x_REG_LEDCR = 24,
+  MG_PHY_KSZ8x_REG_PC1R = 30,
+  MG_PHY_KSZ8x_REG_PC2R = 31,
+  MG_PHY_LAN87x_REG_SCSR = 31,
+  MG_PHY_RTL8201_REG_RMSR = 16,  // in page 7
+  MG_PHY_RTL8201_REG_PAGESEL = 31
+};
+
+static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
+  switch (id1) {
+    case MG_PHY_DP83x:
+      switch (id2) {
+        case MG_PHY_DP83867:
+          return "DP83867";
+        default:
+          return "DP83x";
+      }
+    case MG_PHY_KSZ8x:
+      return "KSZ8x";
+    case MG_PHY_LAN87x:
+      return "LAN87x";
+    case MG_PHY_RTL8201:
+      return "RTL8201";
+    default:
+      return "unknown";
+  }
+  (void) id2;
+}
+
+void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) {
+  uint16_t id1, id2;
+  phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15));  // Reset PHY
+  while (phy->read_reg(phy_addr, MG_PHY_REG_BCR) & MG_BIT(15)) (void) 0;
+  // MG_PHY_REG_BCR[12]: Autonegotiation is default unless hw says otherwise
+
+  id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
+  id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
+  MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2)));
+
+  if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
+    phy->write_reg(phy_addr, 0x0d, 0x1f);    // write 0x10d to IO_MUX_CFG (0x0170)
+    phy->write_reg(phy_addr, 0x0e, 0x170);
+    phy->write_reg(phy_addr, 0x0d, 0x401f);
+    phy->write_reg(phy_addr, 0x0e, 0x10d);
+  }
+
+  if (config & MG_PHY_CLOCKS_MAC) {
+    // Use PHY crystal oscillator (preserve defaults)
+    // nothing to do
+  } else {  // MAC clocks PHY, PHY has no xtal
+    // Enable 50 MHz external ref clock at XI (preserve defaults)
+    if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) {
+      phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) | MG_BIT(0));
+    } else if (id1 == MG_PHY_KSZ8x) {
+      phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R,
+                     MG_BIT(15) | MG_BIT(8) | MG_BIT(7));
+    } else if (id1 == MG_PHY_LAN87x) {
+      // nothing to do
+    } else if (id1 == MG_PHY_RTL8201) {
+      // assume PHY has been hardware strapped properly
+#if 0
+      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7);  // Select page 7
+      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa);
+      phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0);  // Select page 0
+#endif
+    }
+  }
+
+  if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) {
+    phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR,
+                   MG_BIT(9) | MG_BIT(7));  // LED status, active high
+  }  // Other PHYs do not support this feature
+}
+
+bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex,
+               uint8_t *speed) {
+  bool up = false;
+  uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);
+  if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2)))  // some PHYs latch down events
+    bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR);  // read again
+  up = bsr & MG_BIT(2);
+  if (up && full_duplex != NULL && speed != NULL) {
+    uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1);
+    if (id1 == MG_PHY_DP83x) {
+      uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
+      if (id2 == MG_PHY_DP83867) {
+        uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83867_REG_PHYSTS);
+        *full_duplex = physts & MG_BIT(13);
+        *speed = (physts & MG_BIT(15))   ? MG_PHY_SPEED_1000M
+                 : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
+                                         : MG_PHY_SPEED_10M;
+      } else {
+        uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83x_REG_PHYSTS);
+        *full_duplex = physts & MG_BIT(2);
+        *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
+      }
+    } else if (id1 == MG_PHY_KSZ8x) {
+      uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R);
+      *full_duplex = pc1r & MG_BIT(2);
+      *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M;
+    } else if (id1 == MG_PHY_LAN87x) {
+      uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR);
+      *full_duplex = scsr & MG_BIT(4);
+      *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
+    } else if (id1 == MG_PHY_RTL8201) {
+      uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR);
+      *full_duplex = bcr & MG_BIT(8);
+      *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
+    }
+  }
+  return up;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/ra.c"
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA
+struct ra_etherc {
+  volatile uint32_t ECMR, RESERVED, RFLR, RESERVED1, ECSR, RESERVED2, ECSIPR,
+      RESERVED3, PIR, RESERVED4, PSR, RESERVED5[5], RDMLR, RESERVED6[3], IPGR,
+      APR, MPR, RESERVED7, RFCF, TPAUSER, TPAUSECR, BCFRR, RESERVED8[20], MAHR,
+      RESERVED9, MALR, RESERVED10, TROCR, CDCR, LCCR, CNDCR, RESERVED11, CEFCR,
+      FRECR, TSFRCR, TLFRCR, RFCR, MAFCR;
 };
-#undef EMAC
-#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
 
-#undef BIT
-#define BIT(x) ((uint32_t) 1 << (x))
-#define ETH_PKT_SIZE 1540  // Max frame size
+struct ra_edmac {
+  volatile uint32_t EDMR, RESERVED, EDTRR, RESERVED1, EDRRR, RESERVED2, TDLAR,
+      RESERVED3, RDLAR, RESERVED4, EESR, RESERVED5, EESIPR, RESERVED6, TRSCER,
+      RESERVED7, RMFCR, RESERVED8, TFTR, RESERVED9, FDR, RESERVED10, RMCR,
+      RESERVED11[2], TFUCR, RFOCR, IOSR, FCFTR, RESERVED12, RPADIR, TRIMD,
+      RESERVED13[18], RBWAR, RDFAR, RESERVED14, TBRAR, TDFAR;
+};
+
+#undef ETHERC
+#define ETHERC ((struct ra_etherc *) (uintptr_t) 0x40114100U)
+#undef EDMAC
+#define EDMAC ((struct ra_edmac *) (uintptr_t) 0x40114000U)
+#undef RASYSC
+#define RASYSC ((uint32_t *) (uintptr_t) 0x4001E000U)
+#undef ICU_IELSR
+#define ICU_IELSR ((uint32_t *) (uintptr_t) 0x40006300U)
+
+#define ETH_PKT_SIZE 1536  // Max frame size, multiple of 32
 #define ETH_DESC_CNT 4     // Descriptors count
-#define ETH_DS 4           // Descriptor size (words)
 
-static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
-static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
-static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
-static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
-static struct mip_if *s_ifp;                         // MIP interface
-enum { EPHY_ADDR = 0, EPHYBMCR = 0, EPHYBMSR = 1 };  // PHY constants
+// TODO(): handle these in a portable compiler-independent CMSIS-friendly way
+#define MG_16BYTE_ALIGNED __attribute__((aligned((16U))))
+#define MG_32BYTE_ALIGNED __attribute__((aligned((32U))))
 
-static inline void tm4cspin(volatile uint32_t count) {
+// Descriptors: 16-byte aligned
+// Buffers: 32-byte aligned (27.3.1)
+static volatile uint32_t s_rxdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
+static volatile uint32_t s_txdesc[ETH_DESC_CNT][4] MG_16BYTE_ALIGNED;
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED;
+static struct mg_tcpip_if *s_ifp;  // MIP interface
+
+// fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or equivalent
+static inline void raspin(volatile uint32_t count) {
   while (count--) (void) 0;
 }
-
-static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
-  EMAC->EMACMIIADDR &= (0xf << 2);
-  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
-  EMAC->EMACMIIADDR |= BIT(0);
-  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
-  return EMAC->EMACMIIDATA;
+// count to get the 200ns SMC semi-cycle period (2.5MHz) calling raspin():
+// SYS_FREQUENCY * 200ns / 3 = SYS_FREQUENCY / 15000000
+static uint32_t s_smispin;
+
+// Bit-banged SMI
+static void smi_preamble(void) {
+  unsigned int i = 32;
+  uint32_t pir = MG_BIT(1) | MG_BIT(2);  // write, mdio = 1, mdc = 0
+  ETHERC->PIR = pir;
+  while (i--) {
+    pir &= ~MG_BIT(0);  // mdc = 0
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+    pir |= MG_BIT(0);  // mdc = 1
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+  }
 }
-
-static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
-  EMAC->EMACMIIDATA = val;
-  EMAC->EMACMIIADDR &= (0xf << 2);
-  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
-  EMAC->EMACMIIADDR |= BIT(0);
-  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
+static void smi_wr(uint16_t header, uint16_t data) {
+  uint32_t word = (header << 16) | data;
+  smi_preamble();
+  unsigned int i = 32;
+  while (i--) {
+    uint32_t pir = MG_BIT(1) |
+                   (word & 0x80000000 ? MG_BIT(2) : 0);  // write, mdc = 0, data
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+    pir |= MG_BIT(0);  // mdc = 1
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+    word <<= 1;
+  }
 }
-
-static uint32_t get_sysclk(void) {
-  struct sysctl {
-    volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
-        PLLFREQ1;
-  } *sysctl = (struct sysctl *) 0x400FE000;
-  uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */;
-  if (sysctl->RSCLKCFG & (1 << 28)) {  // USEPLL
-    uint32_t fin, vco, mdiv, n, q, psysdiv;
-    uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
-    if (pllsrc == 0) {
-      clk = piosc;
-    } else if (pllsrc == 3) {
-      clk = mosc;
-    } else {
-      MG_ERROR(("Unsupported clock source"));
-    }
-    q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
-    n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
-    fin = clk / ((q + 1) * (n + 1));
-    mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
-           0;  // mint + (mfrac / 1024); MFRAC not supported
-    psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
-    vco = (uint32_t) ((uint64_t) fin * mdiv);
-    return vco / (psysdiv + 1);
+static uint16_t smi_rd(uint16_t header) {
+  smi_preamble();
+  unsigned int i = 16;  // 2 LSb as turnaround
+  uint32_t pir;
+  while (i--) {
+    pir = (i > 1 ? MG_BIT(1) : 0) |
+          (header & 0x8000
+               ? MG_BIT(2)
+               : 0);  // mdc = 0, header, set read direction at turnaround
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+    pir |= MG_BIT(0);  // mdc = 1
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
+    header <<= 1;
   }
-  uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
-  if (oscsrc == 0) {
-    clk = piosc;
-  } else if (oscsrc == 3) {
-    clk = mosc;
-  } else {
-    MG_ERROR(("Unsupported clock source"));
+  i = 16;
+  uint16_t data = 0;
+  while (i--) {
+    data <<= 1;
+    pir = 0;  // read, mdc = 0
+    ETHERC->PIR = pir;
+    raspin(s_smispin / 2);  // 1/4 clock period, 300ns max access time
+    data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0);  // read mdio
+    raspin(s_smispin / 2);                    // 1/4 clock period
+    pir |= MG_BIT(0);                         // mdc = 1
+    ETHERC->PIR = pir;
+    raspin(s_smispin);
   }
-  uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
-  return clk / (osysdiv + 1);
+  return data;
 }
 
-//  Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per
-//  802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be
-//  derived from the PIOSC (internal RC), and it can go above  specs, the
-//  datasheets specify a range of frequencies and activate one of a series of
-//  dividers to keep the MDC clock safely below 2.5MHz. We guess a divider
-//  setting based on SYSCLK with a +5% drift. If the user uses a different clock
-//  from our defaults, needs to set the macros on top Valid for TM4C129x (20.7)
-//  (4.5% worst case drift)
-// The PHY receives the main oscillator (MOSC) (20.3.1)
-static int guess_mdc_cr(void) {
-  uint8_t crs[] = {2, 3, 0, 1};      // EMAC->MACMIIAR::CR values
-  uint8_t div[] = {16, 26, 42, 62};  // Respective HCLK dividers
-  uint32_t sysclk = get_sysclk();    // Guess system SYSCLK
-  int result = -1;                   // Invalid CR value
-  if (sysclk < 25000000) {
-    MG_ERROR(("SYSCLK too low"));
-  } else {
-    for (int i = 0; i < 4; i++) {
-      if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
-        result = crs[i];
-        break;
-      }
-    }
-    if (result < 0) MG_ERROR(("SYSCLK too high"));
-  }
-  MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
-  return result;
+static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) {
+  return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) | (reg << 2) | (2 << 0)));
 }
 
-static bool mip_driver_tm4c_init(struct mip_if *ifp) {
-  struct mip_driver_tm4c_data *d = (struct mip_driver_tm4c_data *) ifp->driver_data;
+static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg << 2) | (2 << 0)), val);
+}
+
+// MDC clock is generated manually; as per 802.3, it must not exceed 2.5MHz
+static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_ra_data *d =
+      (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
   s_ifp = ifp;
 
+  // Init SMI clock timing. If user told us the clock value, use it.
+  // TODO(): Otherwise, guess
+  s_smispin = d->clock / 15000000;
+
   // Init RX descriptors
   for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_rxdesc[i][0] = BIT(31);                            // Own
-    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14);       // 2nd address chained
-    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
-    s_rxdesc[i][3] =
-        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
-    // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
+    s_rxdesc[i][0] = MG_BIT(31);             // RACT
+    s_rxdesc[i][1] = ETH_PKT_SIZE << 16;     // RBL
+    s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];  // Point to data buffer
   }
+  s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30);  // Wrap last descriptor
 
   // Init TX descriptors
   for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
-    s_txdesc[i][3] =
-        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+    // TACT = 0
+    s_txdesc[i][2] = (uint32_t) s_txbuf[i];
   }
-
-  EMAC->EMACDMABUSMOD |= BIT(0);                            // Software reset
-  while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1);  // Wait until done
-
-  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
-  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
-  EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
-
-  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
-  // hardware checksum. Therefore, descriptor size is 4, not 8
-  // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
-  EMAC->EMACIM = BIT(3) | BIT(9);  // Mask timestamp & PMT IT
-  EMAC->EMACFLOWCTL = BIT(7);      // Disable zero-quanta pause
-  // EMAC->EMACFRAMEFLTR = BIT(31);   // Receive all
-  // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
-  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15));  // Reset internal PHY (EPHY)
-  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12));  // Set autonegotiation
-  EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
-  EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors
-  EMAC->EMACDMAIM = BIT(6) | BIT(16);                    // RIE, NIE
-  EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14);   // RE, TE, Duplex, Fast
-  EMAC->EMACDMAOPMODE =
-      BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
-  EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
-  EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
-                     ((uint32_t) ifp->mac[2] << 16) |
-                     ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
-  // NOTE(scaprile) There are 3 additional slots for filtering, disabled by
-  // default. This also applies to the STM32 driver (at least for F7)
-
-  if (ifp->queue.len == 0) ifp->queue.len = 8192;
+  s_txdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(30);  // Wrap last descriptor
+
+  EDMAC->EDMR = MG_BIT(0);  // Software reset, wait 64 PCLKA clocks (27.2.1)
+  uint32_t sckdivcr = RASYSC[8];  // get divisors from SCKDIVCR (8.2.2)
+  uint32_t ick = 1 << ((sckdivcr >> 24) & 7);   // sys_clock div
+  uint32_t pcka = 1 << ((sckdivcr >> 12) & 7);  // pclka div
+  raspin((64U * pcka) / (3U * ick));
+  EDMAC->EDMR = MG_BIT(6);  // Initialize, little-endian (27.2.1)
+
+  MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin));
+  struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
+  mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY
+
+  // Select RMII mode,
+  ETHERC->ECMR = MG_BIT(2) | MG_BIT(1);  // 100M, Full-duplex, CRC
+  // ETHERC->ECMR |= MG_BIT(0);             // Receive all
+  ETHERC->RFLR = 1518;  // Set max rx length
+
+  EDMAC->RDLAR = (uint32_t) (uintptr_t) s_rxdesc;
+  EDMAC->TDLAR = (uint32_t) (uintptr_t) s_txdesc;
+  // MAC address filtering (bytes in reversed order)
+  ETHERC->MAHR = (uint32_t) (ifp->mac[0] << 24U) |
+                 ((uint32_t) ifp->mac[1] << 16U) |
+                 ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
+  ETHERC->MALR = ((uint32_t) ifp->mac[4] << 8U) | ifp->mac[5];
+
+  EDMAC->TFTR = 0;                        // Store and forward (27.2.10)
+  EDMAC->FDR = 0x070f;                    // (27.2.11)
+  EDMAC->RMCR = MG_BIT(0);                // (27.2.12)
+  ETHERC->ECMR |= MG_BIT(6) | MG_BIT(5);  // TE RE
+  EDMAC->EESIPR = MG_BIT(18);             // Enable Rx IRQ
+  EDMAC->EDRRR = MG_BIT(0);               // Receive Descriptors have changed
+  EDMAC->EDTRR = MG_BIT(0);               // Transmit Descriptors have changed
   return true;
 }
 
-static uint32_t s_txno;
-static size_t mip_driver_tm4c_tx(const void *buf, size_t len, struct mip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
+// Transmit frame
+static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len,
+                                    struct mg_tcpip_if *ifp) {
+  static int s_txno;  // Current descriptor index
+  if (len > sizeof(s_txbuf[ETH_DESC_CNT])) {
     MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // fail
-  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
+    len = (size_t) -1;  // fail
+  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
+    ifp->nerr++;
     MG_ERROR(("No descriptors available"));
-    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
-    // EMAC->EMACDMARIS);
-    len = 0;  // fail
+    len = 0;  // retry later
   } else {
-    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
-    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
-    s_txdesc[s_txno][0] =
-        BIT(20) | BIT(28) | BIT(29) | BIT(30);  // Chain,FS,LS,IC
-    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
+    memcpy(s_txbuf[s_txno], buf, len);            // Copy data
+    s_txdesc[s_txno][1] = len << 16;              // Set data len
+    s_txdesc[s_txno][0] |= MG_BIT(31) | 3 << 28;  // (27.3.1.1) mark valid
+    EDMAC->EDTRR = MG_BIT(0);                     // Transmit request
     if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
   }
-  EMAC->EMACDMARIS = BIT(2) | BIT(5);  // Clear any prior TU/UNF
-  EMAC->EMACTXPOLLD = 0;               // and resume
   return len;
-  (void) ifp;
 }
 
-static bool mip_driver_tm4c_up(struct mip_if *ifp) {
-  uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
-  (void) ifp;
-  return (bmsr & BIT(2)) ? 1 : 0;
+static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_ra_data *d =
+      (struct mg_tcpip_driver_ra_data *) ifp->driver_data;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {raeth_read_phy, raeth_write_phy};
+  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    // tmp = reg with flags set to the most likely situation: 100M full-duplex
+    // if(link is slow or half) set flags otherwise
+    // reg = tmp
+    uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1);  // 100M Full-duplex
+    if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2);     // 10M
+    if (full_duplex == false) ecmr &= ~MG_BIT(1);          // Half-duplex
+    ETHERC->ECMR = ecmr;  // IRQ handler does not fiddle with these registers
+    MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10,
+              ecmr & MG_BIT(1) ? "full" : "half"));
+  }
+  return up;
 }
 
-void EMAC0_IRQHandler(void);
+void EDMAC_IRQHandler(void);
 static uint32_t s_rxno;
-void EMAC0_IRQHandler(void) {
-  qp_mark(QP_IRQTRIGGERED, 0);
-  if (EMAC->EMACDMARIS & BIT(6)) {        // Frame received, loop
-    EMAC->EMACDMARIS = BIT(16) | BIT(6);  // Clear flag
-    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
-      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
-      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
-          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
-        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
-        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
-        //  EMAC->EMACDMARIS);
-        mip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
-      }
-      s_rxdesc[s_rxno][0] = BIT(31);
-      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+void EDMAC_IRQHandler(void) {
+  struct mg_tcpip_driver_ra_data *d =
+      (struct mg_tcpip_driver_ra_data *) s_ifp->driver_data;
+  EDMAC->EESR = MG_BIT(18);            // Ack IRQ in EDMAC 1st
+  ICU_IELSR[d->irqno] &= ~MG_BIT(16);  // Ack IRQ in ICU last
+  // Frame received, loop
+  for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
+    uint32_t r = s_rxdesc[s_rxno][0];
+    if (r & MG_BIT(31)) break;  // exit when done
+    // skip partial/errored frames (27.3.1.2)
+    if ((r & (MG_BIT(29) | MG_BIT(28)) && !(r & MG_BIT(27)))) {
+      size_t len = s_rxdesc[s_rxno][1] & 0xffff;
+      mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);  // CRC already stripped
     }
+    s_rxdesc[s_rxno][0] |= MG_BIT(31);
+    if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
   }
-  EMAC->EMACDMARIS = BIT(7);  // Clear possible RU while processing
-  EMAC->EMACRXPOLLD = 0;      // and resume RX
+  EDMAC->EDRRR = MG_BIT(0);  // Receive Descriptors have changed
+  // If b0 == 0, descriptors were exhausted and probably frames were dropped,
+  // (27.2.9 RMFCR counts them)
 }
 
-struct mip_driver mip_driver_tm4c = {mip_driver_tm4c_init, mip_driver_tm4c_tx,
-                                     mip_driver_rx, mip_driver_tm4c_up};
+struct mg_tcpip_driver mg_tcpip_driver_ra = {mg_tcpip_driver_ra_init,
+                                             mg_tcpip_driver_ra_tx, NULL,
+                                             mg_tcpip_driver_ra_up};
+
 #endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "mip/driver_w5500.c"
+#line 1 "src/drivers/same54.c"
 #endif
 
 
-#if MG_ENABLE_MIP
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54
 
-enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
+#include <sam.h>
 
-static void w5500_txn(struct mip_spi *s, uint8_t block, uint16_t addr, bool wr,
-                      void *buf, size_t len) {
-  uint8_t *p = (uint8_t *) buf;
-  uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
-                   (uint8_t) ((block << 3) | (wr ? 4 : 0))};
-  s->begin(s->spi);
-  for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
-  for (size_t i = 0; i < len; i++) {
-    uint8_t r = s->txn(s->spi, p[i]);
-    if (!wr) p[i] = r;
-  }
-  s->end(s->spi);
+#define ETH_PKT_SIZE 1536  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 2           // Descriptor size (words)
+
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
+static uint8_t s_txno;                           // Current TX descriptor
+static uint8_t s_rxno;                           // Current RX descriptor
+
+static struct mg_tcpip_if *s_ifp;  // MIP interface
+enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
+
+#define MG_PHYREGBIT_BCR_DUPLEX_MODE MG_BIT(8)
+#define MG_PHYREGBIT_BCR_SPEED MG_BIT(13)
+#define MG_PHYREGBIT_BSR_LINK_STATUS MG_BIT(2)
+
+static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
+                        GMAC_MAN_OP(2) |  // Setting the read operation
+                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) |  // PHY address
+                        GMAC_MAN_REGA(reg);  // Setting the register
+  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
+  return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk;  // Getting the read value
 }
 
-// clang-format off
-static  void w5500_wn(struct mip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
-static  void w5500_w1(struct mip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
-static  void w5500_w2(struct mip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
-static  void w5500_rn(struct mip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
-static  uint8_t w5500_r1(struct mip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
-static  uint16_t w5500_r2(struct mip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
-// clang-format on
+#if 0
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) |   // Setting the write operation
+                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
+                        GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val);  // Setting the register
+  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting until the write op is complete
+}
+#endif
 
-static size_t w5500_rx(void *buf, size_t buflen, struct mip_if *ifp) {
-  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
-  uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2;     // Read recv len
-  while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2;  // Until it is stable
-  // printf("RSR: %d\n", (int) n);
-  if (n > 0) {
-    uint16_t ptr = w5500_r2(s, W5500_S0, 0x28);  // Get read pointer
-    n = w5500_r2(s, W5500_RX0, ptr);             // Read frame length
-    if (n <= len + 2 && n > 1) {
-      r = (uint16_t) (n - 2);
-      w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
+int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
+  if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
+    return d->mdc_cr;
+  } else {
+    // get MCLK from GCLK_GENERATOR 0
+    uint32_t div = 512;
+    uint32_t mclk;
+    if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
+      div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
+      if (div == 0) div = 1;
     }
-    w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n));  // Advance read pointer
-    w5500_w1(s, W5500_S0, 1, 0x40);                     // Sock0 CR -> RECV
-    // printf("  RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
+    switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
+      case GCLK_GENCTRL_SRC_XOSC0_Val:
+        mclk = 32000000UL; /* 32MHz */
+        break;
+      case GCLK_GENCTRL_SRC_XOSC1_Val:
+        mclk = 32000000UL; /* 32MHz */
+        break;
+      case GCLK_GENCTRL_SRC_OSCULP32K_Val:
+        mclk = 32000UL;
+        break;
+      case GCLK_GENCTRL_SRC_XOSC32K_Val:
+        mclk = 32000UL;
+        break;
+      case GCLK_GENCTRL_SRC_DFLL_Val:
+        mclk = 48000000UL; /* 48MHz */
+        break;
+      case GCLK_GENCTRL_SRC_DPLL0_Val:
+        mclk = 200000000UL; /* 200MHz */
+        break;
+      case GCLK_GENCTRL_SRC_DPLL1_Val:
+        mclk = 200000000UL; /* 200MHz */
+        break;
+      default:
+        mclk = 200000000UL; /* 200MHz */
+    }
+
+    mclk /= div;
+    uint8_t crs[] = {0, 1, 2, 3, 4, 5};            // GMAC->NCFGR::CLK values
+    uint8_t dividers[] = {8, 16, 32, 48, 64, 96};  // Respective CLK dividers
+    for (int i = 0; i < 6; i++) {
+      if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
+        return crs[i];
+      }
+    }
+
+    return 5;
   }
-  return r;
 }
 
-static size_t w5500_tx(const void *buf, size_t buflen, struct mip_if *ifp) {
-  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
-  uint16_t n = 0, len = (uint16_t) buflen;
-  while (n < len) n = w5500_r2(s, W5500_S0, 0x20);      // Wait for space
-  uint16_t ptr = w5500_r2(s, W5500_S0, 0x24);           // Get write pointer
-  w5500_wn(s, W5500_TX0, ptr, (void *) buf, len);       // Write data
-  w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len));  // Advance write pointer
-  w5500_w1(s, W5500_S0, 1, 0x20);                       // Sock0 CR -> SEND
-  for (int i = 0; i < 40; i++) {
-    uint8_t ir = w5500_r1(s, W5500_S0, 2);  // Read S0 IR
-    if (ir == 0) continue;
-    // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
-    w5500_w1(s, W5500_S0, 2, ir);  // Write S0 IR: clear it!
-    if (ir & 8) len = 0;           // Timeout. Report error
-    if (ir & (16 | 8)) break;      // Stop on SEND_OK or timeout
+static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_same54_data *d =
+      (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
+  s_ifp = ifp;
+
+  MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
+  MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
+  GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d));  // Set MDC divider
+  GMAC_REGS->GMAC_NCR = 0;                                    // Disable RX & TX
+  GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk;  // Enable MDC & MDIO
+
+  for (int i = 0; i < ETH_DESC_CNT; i++) {   // Init TX descriptors
+    s_txdesc[i][0] = (uint32_t) s_txbuf[i];  // Point to data buffer
+    s_txdesc[i][1] = MG_BIT(31);             // OWN bit
   }
-  return len;
+  s_txdesc[ETH_DESC_CNT - 1][1] |= MG_BIT(30);  // Last tx descriptor - wrap
+
+  GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18)  // DMA recv buf 1536
+                          | GMAC_DCFGR_RXBMS(GMAC_DCFGR_RXBMS_FULL_Val) |
+                          GMAC_DCFGR_TXPBMS(1);  // See #2487
+  for (int i = 0; i < ETH_DESC_CNT; i++) {       // Init RX descriptors
+    s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];      // Address of the data buffer
+    s_rxdesc[i][1] = 0;                          // Clear status
+  }
+  s_rxdesc[ETH_DESC_CNT - 1][0] |= MG_BIT(1);  // Last rx descriptor - wrap
+
+  GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc;  // about the descriptor addresses
+  GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc;  // Let the controller know
+
+  GMAC_REGS->SA[0].GMAC_SAB =
+      MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
+  GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
+
+  GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk;  // Disable MII, use RMII
+  GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk | GMAC_NCFGR_MTIHEN_Msk |
+                           GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
+  GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
+                        GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
+                        GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
+                        GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
+  GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
+                        GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
+  GMAC_REGS->GMAC_IDR = ~0U;  // Disable interrupts, then enable required
+  GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
+                        GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
+                        GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
+                        GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
+  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
+  NVIC_EnableIRQ(GMAC_IRQn);
+
+  return true;
 }
 
-static bool w5500_init(struct mip_if *ifp) {
-  struct mip_spi *s = (struct mip_spi *) ifp->driver_data;
-  s->end(s->spi);
-  w5500_w1(s, W5500_CR, 0, 0x80);     // Reset chip: CR -> 0x80
-  w5500_w1(s, W5500_CR, 0x2e, 0);     // CR PHYCFGR -> reset
-  w5500_w1(s, W5500_CR, 0x2e, 0xf8);  // CR PHYCFGR -> set
-  // w5500_wn(s, W5500_CR, 9, s->mac, 6);      // Set source MAC
-  w5500_w1(s, W5500_S0, 0x1e, 16);          // Sock0 RX buf size
-  w5500_w1(s, W5500_S0, 0x1f, 16);          // Sock0 TX buf size
-  w5500_w1(s, W5500_S0, 0, 4);              // Sock0 MR -> MACRAW
-  w5500_w1(s, W5500_S0, 1, 1);              // Sock0 CR -> OPEN
-  return w5500_r1(s, W5500_S0, 3) == 0x42;  // Sock0 SR == MACRAW
+static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t len,
+                                        struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // Frame is too big
+  } else if ((s_txdesc[s_txno][1] & MG_BIT(31)) == 0) {
+    ifp->nerr++;
+    MG_ERROR(("No free descriptors"));
+    len = 0;  // All descriptors are busy, fail
+  } else {
+    uint32_t status = len | MG_BIT(15);  // Frame length, last chunk
+    if (s_txno == ETH_DESC_CNT - 1) status |= MG_BIT(30);  // wrap
+    memcpy(s_txbuf[s_txno], buf, len);                     // Copy data
+    s_txdesc[s_txno][1] = status;
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
+  }
+  __DSB();  // Ensure descriptors have been written
+  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk;  // Enable transmission
+  return len;
 }
 
-static bool w5500_up(struct mip_if *ifp) {
-  struct mip_spi *spi = (struct mip_spi *) ifp->driver_data;
-  uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
-  return phycfgr & 1;  // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
+static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
+  uint16_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR);
+  bool up = bsr & MG_PHYREGBIT_BSR_LINK_STATUS ? 1 : 0;
+
+  // If PHY is ready, update NCFGR accordingly
+  if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
+    uint16_t bcr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BCR);
+    bool fd = bcr & MG_PHYREGBIT_BCR_DUPLEX_MODE ? 1 : 0;
+    bool spd = bcr & MG_PHYREGBIT_BCR_SPEED ? 1 : 0;
+    GMAC_REGS->GMAC_NCFGR = (GMAC_REGS->GMAC_NCFGR &
+                             ~(GMAC_NCFGR_SPD_Msk | MG_PHYREGBIT_BCR_SPEED)) |
+                            GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
+  }
+
+  return up;
+}
+
+void GMAC_Handler(void);
+void GMAC_Handler(void) {
+  uint32_t isr = GMAC_REGS->GMAC_ISR;
+  uint32_t rsr = GMAC_REGS->GMAC_RSR;
+  uint32_t tsr = GMAC_REGS->GMAC_TSR;
+  if (isr & GMAC_ISR_RCOMP_Msk) {
+    if (rsr & GMAC_ISR_RCOMP_Msk) {
+      for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
+        if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break;
+        size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
+        s_rxdesc[s_rxno][0] &= ~MG_BIT(0);  // Disown
+        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+      }
+    }
+  }
+
+  if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk | GMAC_TSR_TXCOMP_Msk |
+              GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
+              GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
+    // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
+    if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= MG_BIT(31);
+  }
+
+  GMAC_REGS->GMAC_RSR = rsr;
+  GMAC_REGS->GMAC_TSR = tsr;
 }
 
-struct mip_driver mip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, w5500_up};
+struct mg_tcpip_driver mg_tcpip_driver_same54 = {
+    mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
+    mg_tcpip_driver_same54_up};
 #endif
 
 #ifdef MG_ENABLE_LINES
-#line 1 "mip/mip.c"
+#line 1 "src/drivers/stm32f.c"
 #endif
 
 
-#if MG_ENABLE_MIP
-
-#define MIP_ETHEMERAL_PORT 49152
-#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
-#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
-
-#ifndef MIP_QSIZE
-#define MIP_QSIZE (16 * 1024)  // Queue size
-#endif
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \
+    MG_ENABLE_DRIVER_STM32F
+struct stm32f_eth {
+  volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
+      MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
+      MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
+      MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
+      RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
+      RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
+      RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
+      PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
+      DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
+      DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
+      DMACHRBAR;
+};
+#undef ETH
+#define ETH ((struct stm32f_eth *) (uintptr_t) 0x40028000)
 
-#ifndef MIP_TCP_KEEPALIVE_MS
-#define MIP_TCP_KEEPALIVE_MS 45000  // TCP keep-alive period, ms
-#endif
+#define ETH_PKT_SIZE 1540  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 4           // Descriptor size (words)
 
-#define MIP_TCP_ACK_MS 150  // Timeout for ACKing
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
+static uint8_t s_txno;                               // Current TX descriptor
+static uint8_t s_rxno;                               // Current RX descriptor
 
-struct connstate {
-  uint32_t seq, ack;           // TCP seq/ack counters
-  uint64_t timer;              // TCP keep-alive / ACK timer
-  uint8_t mac[6];              // Peer MAC address
-  uint8_t ttype;               // Timer type. 0: ack, 1: keep-alive
-#define MIP_TTYPE_KEEPALIVE 0  // Connection is idle for long, send keepalive
-#define MIP_TTYPE_ACK 1        // Peer sent us data, we have to ack it soon
-  uint8_t tmiss;               // Number of keep-alive misses
-  struct mg_iobuf raw;         // For TLS only. Incoming raw data
-};
+static struct mg_tcpip_if *s_ifp;  // MIP interface
 
-#pragma pack(push, 1)
+static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  ETH->MACMIIAR &= (7 << 2);
+  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
+  ETH->MACMIIAR |= MG_BIT(0);
+  while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
+  return ETH->MACMIIDR & 0xffff;
+}
 
-struct lcp {
-  uint8_t addr, ctrl, proto[2], code, id, len[2];
-};
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  ETH->MACMIIDR = val;
+  ETH->MACMIIAR &= (7 << 2);
+  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1);
+  ETH->MACMIIAR |= MG_BIT(0);
+  while (ETH->MACMIIAR & MG_BIT(0)) (void) 0;
+}
 
-struct eth {
-  uint8_t dst[6];  // Destination MAC address
-  uint8_t src[6];  // Source MAC address
-  uint16_t type;   // Ethernet type
-};
+static uint32_t get_hclk(void) {
+  struct rcc {
+    volatile uint32_t CR, PLLCFGR, CFGR;
+  } *rcc = (struct rcc *) 0x40023800;
+  uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */;
 
-struct ip {
-  uint8_t ver;    // Version
-  uint8_t tos;    // Unused
-  uint16_t len;   // Length
-  uint16_t id;    // Unused
-  uint16_t frag;  // Fragmentation
-  uint8_t ttl;    // Time to live
-  uint8_t proto;  // Upper level protocol
-  uint16_t csum;  // Checksum
-  uint32_t src;   // Source IP
-  uint32_t dst;   // Destination IP
-};
+  if (rcc->CFGR & (1 << 2)) {
+    clk = hse;
+  } else if (rcc->CFGR & (1 << 3)) {
+    uint32_t vco, m, n, p;
+    m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
+    n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
+    p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
+    clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
+    vco = (uint32_t) ((uint64_t) clk * n / m);
+    clk = vco / p;
+  } else {
+    clk = hsi;
+  }
+  uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
+  if (hpre < 8) return clk;
 
-struct ip6 {
-  uint8_t ver;      // Version
-  uint8_t opts[3];  // Options
-  uint16_t len;     // Length
-  uint8_t proto;    // Upper level protocol
-  uint8_t ttl;      // Time to live
-  uint8_t src[16];  // Source IP
-  uint8_t dst[16];  // Destination IP
-};
+  uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
+  return ((uint32_t) clk) >> ahbptab[hpre - 8];
+}
 
-struct icmp {
-  uint8_t type;
-  uint8_t code;
-  uint16_t csum;
-};
+//  Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3,
+//  it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived
+//  from the HSI (internal RC), and it can go above specs, the datasheets
+//  specify a range of frequencies and activate one of a series of dividers to
+//  keep the MDC clock safely below 2.5MHz. We guess a divider setting based on
+//  HCLK with a +5% drift. If the user uses a different clock from our
+//  defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
+//  (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
+static int guess_mdc_cr(void) {
+  uint8_t crs[] = {2, 3, 0, 1, 4, 5};          // ETH->MACMIIAR::CR values
+  uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
+  uint32_t hclk = get_hclk();                  // Guess system HCLK
+  int result = -1;                             // Invalid CR value
+  if (hclk < 25000000) {
+    MG_ERROR(("HCLK too low"));
+  } else {
+    for (int i = 0; i < 6; i++) {
+      if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
+        result = crs[i];
+        break;
+      }
+    }
+    if (result < 0) MG_ERROR(("HCLK too high"));
+  }
+  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
+  return result;
+}
 
-struct arp {
-  uint16_t fmt;    // Format of hardware address
-  uint16_t pro;    // Format of protocol address
-  uint8_t hlen;    // Length of hardware address
-  uint8_t plen;    // Length of protocol address
-  uint16_t op;     // Operation
-  uint8_t sha[6];  // Sender hardware address
-  uint32_t spa;    // Sender protocol address
-  uint8_t tha[6];  // Target hardware address
-  uint32_t tpa;    // Target protocol address
-};
+static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_stm32f_data *d =
+      (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
+  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
+  s_ifp = ifp;
 
-struct tcp {
-  uint16_t sport;  // Source port
-  uint16_t dport;  // Destination port
-  uint32_t seq;    // Sequence number
-  uint32_t ack;    // Acknowledgement number
-  uint8_t off;     // Data offset
-  uint8_t flags;   // TCP flags
-#define TH_FIN 0x01
-#define TH_SYN 0x02
-#define TH_RST 0x04
-#define TH_PUSH 0x08
-#define TH_ACK 0x10
-#define TH_URG 0x20
-#define TH_ECE 0x40
-#define TH_CWR 0x80
-  uint16_t win;   // Window
-  uint16_t csum;  // Checksum
-  uint16_t urp;   // Urgent pointer
-};
+  // Init RX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = MG_BIT(31);                         // Own
+    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14);    // 2nd address chained
+    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
+    s_rxdesc[i][3] =
+        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+  }
 
-struct udp {
-  uint16_t sport;  // Source port
-  uint16_t dport;  // Destination port
-  uint16_t len;    // UDP length
-  uint16_t csum;   // UDP checksum
-};
+  // Init TX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
+    s_txdesc[i][3] =
+        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+  }
 
-struct dhcp {
-  uint8_t op, htype, hlen, hops;
-  uint32_t xid;
-  uint16_t secs, flags;
-  uint32_t ciaddr, yiaddr, siaddr, giaddr;
-  uint8_t hwaddr[208];
-  uint32_t magic;
-  uint8_t options[32];
-};
+  ETH->DMABMR |= MG_BIT(0);                         // Software reset
+  while ((ETH->DMABMR & MG_BIT(0)) != 0) (void) 0;  // Wait until done
 
-#pragma pack(pop)
+  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
+  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
+  ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
 
-struct pkt {
-  struct mg_str raw;  // Raw packet data
-  struct mg_str pay;  // Payload data
-  struct eth *eth;
-  struct llc *llc;
-  struct arp *arp;
-  struct ip *ip;
-  struct ip6 *ip6;
-  struct icmp *icmp;
-  struct tcp *tcp;
-  struct udp *udp;
-  struct dhcp *dhcp;
-};
+  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
+  // hardware checksum. Therefore, descriptor size is 4, not 8
+  // ETH->DMABMR = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) |
+  // MG_BIT(25);
+  ETH->MACIMR = MG_BIT(3) | MG_BIT(9);  // Mask timestamp & PMT IT
+  ETH->MACFCR = MG_BIT(7);              // Disable zero quarta pause
+  // ETH->MACFFR = MG_BIT(31);                            // Receive all
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC);
+  ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
+  ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc;  // RX descriptors
+  ETH->DMAIER = MG_BIT(6) | MG_BIT(16);             // RIE, NISE
+  ETH->MACCR =
+      MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14);  // RE, TE, Duplex, Fast
+  ETH->DMAOMR =
+      MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25);  // SR, ST, TSF, RSF
 
-static void q_copyin(struct queue *q, const uint8_t *buf, size_t len,
-                     size_t head) {
-  size_t left = q->len - head;
-  memcpy(&q->buf[head], buf, left < len ? left : len);
-  if (left < len) memcpy(q->buf, &buf[left], len - left);
+  // MAC address filtering
+  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
+  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
+                 ((uint32_t) ifp->mac[2] << 16) |
+                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
+  return true;
 }
 
-static void q_copyout(struct queue *q, uint8_t *buf, size_t len, size_t tail) {
-  size_t left = q->len - tail;
-  memcpy(buf, &q->buf[tail], left < len ? left : len);
-  if (left < len) memcpy(&buf[left], q->buf, len - left);
+static size_t mg_tcpip_driver_stm32f_tx(const void *buf, size_t len,
+                                        struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // Frame is too big
+  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
+    ifp->nerr++;
+    MG_ERROR(("No free descriptors"));
+    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
+    len = 0;  // All descriptors are busy, fail
+  } else {
+    memcpy(s_txbuf[s_txno], buf, len);                           // Copy data
+    s_txdesc[s_txno][1] = (uint32_t) len;                        // Set data len
+    s_txdesc[s_txno][0] = MG_BIT(20) | MG_BIT(28) | MG_BIT(29);  // Chain,FS,LS
+    s_txdesc[s_txno][0] |= MG_BIT(31);  // Set OWN bit - let DMA take over
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
+  }
+  MG_DSB();                            // ensure descriptors have been written
+  ETH->DMASR = MG_BIT(2) | MG_BIT(5);  // Clear any prior TBUS/TUS
+  ETH->DMATPDR = 0;                    // and resume
+  return len;
 }
 
-static bool q_write(struct queue *q, const void *buf, size_t len) {
-  bool success = false;
-  size_t left = (q->len - q->head + q->tail - 1) % q->len;
-  if (len + sizeof(size_t) <= left) {
-    q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
-    q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) % q->len);
-    q->head = (q->head + sizeof(len) + len) % q->len;
-    success = true;
+static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_stm32f_data *d =
+      (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data;
+  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    // tmp = reg with flags set to the most likely situation: 100M full-duplex
+    // if(link is slow or half) set flags otherwise
+    // reg = tmp
+    uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11);  // 100M, Full-duplex
+    if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14);    // 10M
+    if (full_duplex == false) maccr &= ~MG_BIT(11);         // Half-duplex
+    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
+    MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
+              maccr & MG_BIT(11) ? "full" : "half"));
   }
-  return success;
+  return up;
 }
 
-#ifdef MIP_QPROFILE
-static inline size_t q_space(struct queue *q) {
-  return q->tail > q->head ? q->tail - q->head : q->tail + (q->len - q->head);
+#ifdef __riscv
+__attribute__((interrupt()))  // For RISCV CH32V307, which share the same MAC
+#endif
+void ETH_IRQHandler(void);
+void ETH_IRQHandler(void) {
+  if (ETH->DMASR & MG_BIT(6)) {           // Frame received, loop
+    ETH->DMASR = MG_BIT(16) | MG_BIT(6);  // Clear flag
+    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
+      if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break;  // exit when done
+      if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) ==
+           (MG_BIT(8) | MG_BIT(9))) &&
+          !(s_rxdesc[s_rxno][0] & MG_BIT(15))) {  // skip partial/errored frames
+        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
+        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
+        //  ETH->DMASR);
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
+      }
+      s_rxdesc[s_rxno][0] = MG_BIT(31);
+      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+    }
+  }
+  // Cleanup flags
+  ETH->DMASR = MG_BIT(16)    // NIS, normal interrupt summary
+               | MG_BIT(7);  // Clear possible RBUS while processing
+  ETH->DMARPDR = 0;          // and resume RX
 }
+
+struct mg_tcpip_driver mg_tcpip_driver_stm32f = {
+    mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL,
+    mg_tcpip_driver_stm32f_up};
 #endif
 
-static inline size_t q_avail(struct queue *q) {
-  size_t n = 0;
-  if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n), q->tail);
-  return n;
-}
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/stm32h.c"
+#endif
 
-static size_t q_read(struct queue *q, void *buf) {
-  size_t n = q_avail(q);
-  if (n > 0) {
-    q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
-    q->tail = (q->tail + sizeof(n) + n) % q->len;
-  }
-  return n;
-}
 
-static struct mg_str mkstr(void *buf, size_t len) {
-  struct mg_str str = {(char *) buf, len};
-  return str;
-}
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
+    MG_ENABLE_DRIVER_STM32H
+struct stm32h_eth {
+  volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
+      RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR,
+      RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR,
+      MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], MACLCSR,
+      MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10,
+      MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR,
+      RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR,
+      MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR,
+      MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR,
+      RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
+      RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
+      MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
+      RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
+      RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
+      MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR,
+      MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR,
+      RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28,
+      MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
+      RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, MACPPSIR,
+      MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, MACLMIR,
+      RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], MTLTQOMR,
+      MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, MTLRQDR,
+      RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], DMACCR,
+      DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
+      DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
+      DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR,
+      RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2],
+      DMACMFCR;
+};
+#undef ETH
+#define ETH \
+  ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 0x8000UL))
 
-static void mkpay(struct pkt *pkt, void *p) {
-  pkt->pay = mkstr(p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) p));
+#define ETH_PKT_SIZE 1540  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 4           // Descriptor size (words)
+
+static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
+static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];       // RX ethernet buffers
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];       // TX ethernet buffers
+static struct mg_tcpip_if *s_ifp;                         // MIP interface
+
+static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  ETH->MACMDIOAR &= (0xF << 8);
+  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2;
+  ETH->MACMDIOAR |= MG_BIT(0);
+  while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
+  return (uint16_t) ETH->MACMDIODR;
 }
 
-static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
-  const uint8_t *p = (const uint8_t *) buf;
-  for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
-  return sum;
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  ETH->MACMDIODR = val;
+  ETH->MACMDIOAR &= (0xF << 8);
+  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2;
+  ETH->MACMDIOAR |= MG_BIT(0);
+  while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0;
 }
 
-static uint16_t csumfin(uint32_t sum) {
-  while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
-  return mg_htons(~sum & 0xffff);
+static uint32_t get_hclk(void) {
+  struct rcc {
+    volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR,
+        D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR,
+        PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R,
+        D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR,
+        RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR,
+        APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9],
+        RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR,
+        APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
+        AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR,
+        RESERVED13[4];
+  } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
+  uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz */,
+           csi = 4000000 /* 4MHz */;
+  unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
+
+  if (sel == 1) {
+    clk = csi;
+  } else if (sel == 2) {
+    clk = hse;
+  } else if (sel == 3) {
+    uint32_t vco, m, n, p;
+    unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
+    m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
+    n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
+        ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0);  // round-up in fractional mode
+    p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
+    if (src == 1) {
+      clk = csi;
+    } else if (src == 2) {
+      clk = hse;
+    } else {
+      clk = hsi;
+      clk >>= ((rcc->CR & 3) >> 3);
+    }
+    vco = (uint32_t) ((uint64_t) clk * n / m);
+    clk = vco / p;
+  } else {
+    clk = hsi;
+    clk >>= ((rcc->CR & 3) >> 3);
+  }
+  const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
+  uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
+  if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
+  MG_DEBUG(("D1 CLK: %u", clk));
+  uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
+  if (hpre < 8) return clk;
+  return ((uint32_t) clk) >> cptab[hpre - 8];
+}
+
+//  Guess CR from AHB1 clock. MDC clock is generated from the ETH peripheral
+//  clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock can
+//  be derived from HSI or CSI (internal RC) clocks, and those can go above
+//  specs, the datasheets specify a range of frequencies and activate one of a
+//  series of dividers to keep the MDC clock safely below 2.5MHz. We guess a
+//  divider setting based on HCLK with some drift. If the user uses a different
+//  clock from our defaults, needs to set the macros on top. Valid for
+//  STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 7.5 %
+//  worst case drift @ max temp)
+static int guess_mdc_cr(void) {
+  const uint8_t crs[] = {2, 3, 0, 1, 4, 5};  // ETH->MACMDIOAR::CR values
+  const uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
+  uint32_t hclk = get_hclk();                        // Guess system HCLK
+  int result = -1;                                   // Invalid CR value
+  for (int i = 0; i < 6; i++) {
+    if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
+      result = crs[i];
+      break;
+    }
+  }
+  if (result < 0) MG_ERROR(("HCLK too high"));
+  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
+  return result;
 }
 
-static uint16_t ipcsum(const void *buf, size_t len) {
-  uint32_t sum = csumup(0, buf, len);
-  return csumfin(sum);
-}
+static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_stm32h_data *d =
+      (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
+  s_ifp = ifp;
+  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
+  uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf;
 
-// ARP cache is organised as a doubly linked list. A successful cache lookup
-// moves an entry to the head of the list. New entries are added by replacing
-// the last entry in the list with a new IP/MAC.
-// ARP cache format: | prev | next | Entry0 | Entry1 | .... | EntryN |
-// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
-// prev and next are 1-byte offsets in the cache, so cache size is max 256 bytes
-// ARP entry size is 12 bytes
-static void arp_cache_init(uint8_t *p, int n, int size) {
-  for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i - 1) * size);
-  for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i + 1) * size);
-  p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
-  p[1] = p[3 + (n - 1) * size] = 2;
-}
+  // Init RX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
+    s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24);  // OWN, IOC, BUF1V
+  }
 
-#if 0
-static inline void arp_cache_dump(const uint8_t *p) {
-  MG_INFO(("ARP cache:"));
-  for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
-    MG_INFO(("  %I -> %A", 4, &p[j + 2], &p[j + 6]));
+  // Init TX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
   }
-}
-#endif
 
-static const uint8_t bcastmac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+  ETH->DMAMR |= MG_BIT(0);                         // Software reset
+  while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0;  // Wait until done
 
-static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
-  uint8_t *p = ifp->arp_cache;
-  if (ip == 0) return NULL;
-  // use broadcast MAC for local and global broadcast IP
-  if (ip == 0xffffffffU || ip == (ifp->ip | ~ifp->mask))
-    return (uint8_t *) bcastmac;
-  for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
-    if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
-      p[1] = j, p[0] = p[j];  // Found entry! Point list head to us
-      // MG_DEBUG(("ARP find: %I @ %A", 4, &ip, &p[j + 6]));
-      return p + j + 6;  // And return MAC address
-    }
-  }
-  return NULL;
-}
+  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
+  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
+  ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
+
+  // NOTE(scaprile): We do not use timing facilities so the DMA engine does not
+  // re-write buffer address
+  ETH->DMAMR = 0 << 16;        // use interrupt mode 0 (58.8.1) (reset value)
+  ETH->DMASBMR |= MG_BIT(12);  // AAL NOTE(scaprile): is this actually needed
+  ETH->MACIER = 0;  // Do not enable additional irq sources (reset value)
+  ETH->MACTFCR = MG_BIT(7);  // Disable zero-quanta pause
+  // ETH->MACPFR = MG_BIT(31);  // Receive all
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  mg_phy_init(&phy, phy_addr, phy_conf);
+  ETH->DMACRDLAR =
+      (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors start address
+  ETH->DMACRDRLR = ETH_DESC_CNT - 1;    // ring length
+  ETH->DMACRDTPR =
+      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
+                                       1];  // last valid descriptor address
+  ETH->DMACTDLAR =
+      (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors start address
+  ETH->DMACTDRLR = ETH_DESC_CNT - 1;    // ring length
+  ETH->DMACTDTPR =
+      (uint32_t) (uintptr_t) s_txdesc;  // first available descriptor address
+  ETH->DMACCR = 0;  // DSL = 0 (contiguous descriptor table) (reset value)
+  ETH->DMACIER = MG_BIT(6) | MG_BIT(15);  // RIE, NIE
+  ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) |
+               MG_BIT(15);     // RE, TE, Duplex, Fast, Reserved
+  ETH->MTLTQOMR |= MG_BIT(1);  // TSF
+  ETH->MTLRQOMR |= MG_BIT(5);  // RSF
+  ETH->DMACTCR |= MG_BIT(0);   // ST
+  ETH->DMACRCR |= MG_BIT(0);   // SR
 
-static void arp_cache_add(struct mip_if *ifp, uint32_t ip, uint8_t mac[6]) {
-  uint8_t *p = ifp->arp_cache;
-  if (ip == 0 || ip == ~0U) return;             // Bad IP
-  if (arp_cache_find(ifp, ip) != NULL) return;  // Already exists, do nothing
-  memcpy(p + p[0] + 2, &ip, sizeof(ip));  // Replace last entry: IP address
-  memcpy(p + p[0] + 6, mac, 6);           // And MAC address
-  p[1] = p[0], p[0] = p[p[1]];            // Point list head to us
-  MG_DEBUG(("ARP cache: added %I @ %A", 4, &ip, mac));
+  // MAC address filtering
+  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
+  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
+                 ((uint32_t) ifp->mac[2] << 16) |
+                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
+  return true;
 }
 
-static size_t ether_output(struct mip_if *ifp, size_t len) {
-  // size_t min = 64;  // Pad short frames to 64 bytes (minimum Ethernet size)
-  // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len = min;
-  // mg_hexdump(ifp->tx.ptr, len);
-  return ifp->driver->tx(ifp->tx.ptr, len, ifp);
+static uint32_t s_txno;
+static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len,
+                                        struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // Frame is too big
+  } else if ((s_txdesc[s_txno][3] & MG_BIT(31))) {
+    ifp->nerr++;
+    MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
+              s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
+    for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", s_txdesc[i][3]));
+    len = 0;  // All descriptors are busy, fail
+  } else {
+    memcpy(s_txbuf[s_txno], buf, len);              // Copy data
+    s_txdesc[s_txno][2] = (uint32_t) len;           // Set data len
+    s_txdesc[s_txno][3] = MG_BIT(28) | MG_BIT(29);  // FD, LD
+    s_txdesc[s_txno][3] |= MG_BIT(31);  // Set OWN bit - let DMA take over
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
+  }
+  ETH->DMACSR |= MG_BIT(2) | MG_BIT(1);  // Clear any prior TBU, TPS
+  ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno];  // and resume
+  return len;
+  (void) ifp;
 }
 
-static void arp_ask(struct mip_if *ifp, uint32_t ip) {
-  struct eth *eth = (struct eth *) ifp->tx.ptr;
-  struct arp *arp = (struct arp *) (eth + 1);
-  memset(eth->dst, 255, sizeof(eth->dst));
-  memcpy(eth->src, ifp->mac, sizeof(eth->src));
-  eth->type = mg_htons(0x806);
-  memset(arp, 0, sizeof(*arp));
-  arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
-  arp->plen = 4;
-  arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
-  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
-  ether_output(ifp, PDIFF(eth, arp + 1));
+static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_stm32h_data *d =
+      (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
+  uint8_t phy_addr = d == NULL ? 0 : d->phy_addr;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    // tmp = reg with flags set to the most likely situation: 100M full-duplex
+    // if(link is slow or half) set flags otherwise
+    // reg = tmp
+    uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13);  // 100M, Full-duplex
+    if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14);    // 10M
+    if (full_duplex == false) maccr &= ~MG_BIT(13);         // Half-duplex
+    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
+    MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10,
+              maccr & MG_BIT(13) ? "full" : "half"));
+  }
+  return up;
 }
 
-static void onstatechange(struct mip_if *ifp) {
-  if (ifp->state == MIP_STATE_READY) {
-    MG_INFO(("READY, IP: %I", 4, &ifp->ip));
-    MG_INFO(("       GW: %I", 4, &ifp->gw));
-    if (ifp->lease_expire > ifp->now) {
-      MG_INFO(
-          ("       Lease: %lld sec", (ifp->lease_expire - ifp->now) / 1000));
+void ETH_IRQHandler(void);
+static uint32_t s_rxno;
+void ETH_IRQHandler(void) {
+  if (ETH->DMACSR & MG_BIT(6)) {           // Frame received, loop
+    ETH->DMACSR = MG_BIT(15) | MG_BIT(6);  // Clear flag
+    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
+      if (s_rxdesc[s_rxno][3] & MG_BIT(31)) break;  // exit when done
+      if (((s_rxdesc[s_rxno][3] & (MG_BIT(28) | MG_BIT(29))) ==
+           (MG_BIT(28) | MG_BIT(29))) &&
+          !(s_rxdesc[s_rxno][3] & MG_BIT(15))) {  // skip partial/errored frames
+        uint32_t len = s_rxdesc[s_rxno][3] & (MG_BIT(15) - 1);
+        // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3],
+        // ETH->DMACSR));
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
+      }
+      s_rxdesc[s_rxno][3] =
+          MG_BIT(31) | MG_BIT(30) | MG_BIT(24);  // OWN, IOC, BUF1V
+      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
     }
-    arp_ask(ifp, ifp->gw);
-  } else if (ifp->state == MIP_STATE_UP) {
-    MG_ERROR(("Link up"));
-  } else if (ifp->state == MIP_STATE_DOWN) {
-    MG_ERROR(("Link down"));
   }
+  ETH->DMACSR =
+      MG_BIT(7) | MG_BIT(8);  // Clear possible RBU RPS while processing
+  ETH->DMACRDTPR =
+      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1];  // and resume RX
 }
 
-static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto, uint32_t ip_src,
-                        uint32_t ip_dst, size_t plen) {
-  struct eth *eth = (struct eth *) ifp->tx.ptr;
-  struct ip *ip = (struct ip *) (eth + 1);
-  uint8_t *mac = arp_cache_find(ifp, ip_dst);  // Dst IP in ARP cache ?
-  if (!mac && ((ip_dst & ifp->mask) == (ifp->ip & ifp->mask)))
-    arp_ask(ifp, ip_dst);                             // Same net, lookup
-  if (!mac) mac = arp_cache_find(ifp, ifp->gw);       // Use gateway MAC
-  if (!mac) arp_ask(ifp, ifp->gw);                    // Not found? lookup
-  if (mac) memcpy(eth->dst, mac, sizeof(eth->dst));   // Found? Use it
-  if (!mac) memset(eth->dst, 255, sizeof(eth->dst));  // No? Use broadcast
-  memcpy(eth->src, ifp->mac, sizeof(eth->src));       // TODO(cpq): ARP lookup
-  eth->type = mg_htons(0x800);
-  memset(ip, 0, sizeof(*ip));
-  ip->ver = 0x45;   // Version 4, header length 5 words
-  ip->frag = 0x40;  // Don't fragment
-  ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
-  ip->ttl = 64;
-  ip->proto = proto;
-  ip->src = ip_src;
-  ip->dst = ip_dst;
-  ip->csum = ipcsum(ip, sizeof(*ip));
-  return ip;
-}
+struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
+    mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
+    mg_tcpip_driver_stm32h_up};
+#endif
 
-static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t sport,
-                   uint32_t ip_dst, uint16_t dport, const void *buf,
-                   size_t len) {
-  struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len + sizeof(struct udp));
-  struct udp *udp = (struct udp *) (ip + 1);
-  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
-  udp->sport = sport;
-  udp->dport = dport;
-  udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
-  udp->csum = 0;
-  uint32_t cs = csumup(0, udp, sizeof(*udp));
-  cs = csumup(cs, buf, len);
-  cs = csumup(cs, &ip->src, sizeof(ip->src));
-  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
-  udp->csum = csumfin(cs);
-  memmove(udp + 1, buf, len);
-  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
-  ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len);
-}
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/tm4c.c"
+#endif
 
-static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
-                    uint8_t *opts, size_t optslen) {
-  struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
-  dhcp.magic = mg_htonl(0x63825363);
-  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
-  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
-  memcpy(&dhcp.options, opts, optslen);
-  tx_udp(ifp, src, mg_htons(68), dst, mg_htons(67), &dhcp, sizeof(dhcp));
-}
 
-static void tx_dhcp_request(struct mip_if *ifp, uint32_t src, uint32_t dst) {
-  uint8_t opts[] = {
-      53, 1, 3,                 // Type: DHCP request
-      55, 2, 1,   3,            // GW and mask
-      12, 3, 'm', 'i', 'p',     // Host name: "mip"
-      54, 4, 0,   0,   0,   0,  // DHCP server ID
-      50, 4, 0,   0,   0,   0,  // Requested IP
-      255                       // End of options
-  };
-  memcpy(opts + 14, &dst, sizeof(dst));
-  memcpy(opts + 20, &src, sizeof(src));
-  tx_dhcp(ifp, src, dst, opts, sizeof(opts));
-}
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
+struct tm4c_emac {
+  volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
+      EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS,
+      EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
+      EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
+      EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
+      EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
+      EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
+      EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR,
+      EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
+      EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC,
+      EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
+      EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
+      RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
+      EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
+      EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
+      RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
+      RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
+      EMACEPHYIM, EMACEPHYIMSC;
+};
+#undef EMAC
+#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
 
-static void tx_dhcp_discover(struct mip_if *ifp) {
-  uint8_t opts[] = {
-      53, 1, 1,     // Type: DHCP discover
-      55, 2, 1, 3,  // Parameters: ip, mask
-      255           // End of options
-  };
-  tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
-  MG_DEBUG(("DHCP discover sent"));
-}
+#define ETH_PKT_SIZE 1540  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 4           // Descriptor size (words)
 
-static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
-  if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
-    // ARP request. Make a response, then send
-    MG_DEBUG(("ARP op %d %I: %I?", mg_ntohs(pkt->arp->op), 4, &pkt->arp->spa, 4,
-              &pkt->arp->tpa));
-    struct eth *eth = (struct eth *) ifp->tx.ptr;
-    struct arp *arp = (struct arp *) (eth + 1);
-    memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
-    memcpy(eth->src, ifp->mac, sizeof(eth->src));
-    eth->type = mg_htons(0x806);
-    *arp = *pkt->arp;
-    arp->op = mg_htons(2);
-    memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
-    memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
-    arp->tpa = pkt->arp->spa;
-    arp->spa = ifp->ip;
-    MG_DEBUG(("ARP response: we're %I", 4, &ifp->ip));
-    ether_output(ifp, PDIFF(eth, arp + 1));
-  } else if (pkt->arp->op == mg_htons(2)) {
-    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
-    // MG_INFO(("ARP RESPONSE"));
-    arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
-  }
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
+static struct mg_tcpip_if *s_ifp;                    // MIP interface
+enum {
+  EPHY_ADDR = 0,
+  EPHYBMCR = 0,
+  EPHYBMSR = 1,
+  EPHYSTS = 16
+};  // PHY constants
+
+static inline void tm4cspin(volatile uint32_t count) {
+  while (count--) (void) 0;
 }
 
-static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("ICMP %d", (int) len));
-  if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
-    size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
-    size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
-    if (plen > space) plen = space;
-    struct ip *ip =
-        tx_ip(ifp, 1, ifp->ip, pkt->ip->src, sizeof(struct icmp) + plen);
-    struct icmp *icmp = (struct icmp *) (ip + 1);
-    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
-    memcpy(icmp + 1, pkt->pay.ptr, plen);  // Copy RX payload to TX
-    icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
-    ether_output(ifp, hlen + plen);
-  }
+static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
+  EMAC->EMACMIIADDR &= (0xf << 2);
+  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
+  EMAC->EMACMIIADDR |= MG_BIT(0);
+  while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
+  return EMAC->EMACMIIDATA;
 }
 
-static void rx_dhcp_client(struct mip_if *ifp, struct pkt *pkt) {
-  uint32_t ip = 0, gw = 0, mask = 0;
-  uint8_t *p = pkt->dhcp->options,
-          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
-  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
-  while (p + 1 < end && p[0] != 255) {  // Parse options
-    if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) {  // Mask
-      memcpy(&mask, p + 2, sizeof(mask));
-    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) {  // GW
-      memcpy(&gw, p + 2, sizeof(gw));
-      ip = pkt->dhcp->yiaddr;
-    } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) {  // Lease
-      uint32_t lease = 0;
-      memcpy(&lease, p + 2, sizeof(lease));
-      ifp->lease_expire = ifp->now + mg_ntohl(lease) * 1000;
-    }
-    p += p[1] + 2;
-  }
-  if (ip && mask && gw && ifp->ip == 0) {
-    arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *) pkt->raw.ptr)->src);
-    ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
-    ifp->state = MIP_STATE_READY;
-    onstatechange(ifp);
-    tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
-  }
+static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
+  EMAC->EMACMIIDATA = val;
+  EMAC->EMACMIIADDR &= (0xf << 2);
+  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1);
+  EMAC->EMACMIIADDR |= MG_BIT(0);
+  while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1);
 }
 
-// Simple DHCP server that assigns a next IP address: ifp->ip + 1
-static void rx_dhcp_server(struct mip_if *ifp, struct pkt *pkt) {
-  uint8_t op = 0, *p = pkt->dhcp->options,
-          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
-  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
-  // struct dhcp *req = pkt->dhcp;
-  struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
-  res.yiaddr = ifp->ip;
-  ((uint8_t *) (&res.yiaddr))[3]++;                // Offer our IP + 1
-  while (p + 1 < end && p[0] != 255) {             // Parse options
-    if (p[0] == 53 && p[1] == 1 && p + 2 < end) {  // Message type
-      op = p[2];
+static uint32_t get_sysclk(void) {
+  struct sysctl {
+    volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
+        PLLFREQ1;
+  } *sysctl = (struct sysctl *) 0x400FE000;
+  uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */;
+  if (sysctl->RSCLKCFG & (1 << 28)) {  // USEPLL
+    uint32_t fin, vco, mdiv, n, q, psysdiv;
+    uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
+    if (pllsrc == 0) {
+      clk = piosc;
+    } else if (pllsrc == 3) {
+      clk = mosc;
+    } else {
+      MG_ERROR(("Unsupported clock source"));
     }
-    p += p[1] + 2;
-  }
-  if (op == 1 || op == 3) {         // DHCP Discover or DHCP Request
-    uint8_t msg = op == 1 ? 2 : 5;  // Message type: DHCP OFFER or DHCP ACK
-    uint8_t opts[] = {
-        53, 1, msg,                 // Message type
-        1,  4, 0,   0,   0,   0,    // Subnet mask
-        54, 4, 0,   0,   0,   0,    // Server ID
-        12, 3, 'm', 'i', 'p',       // Host name: "mip"
-        51, 4, 255, 255, 255, 255,  // Lease time
-        255                         // End of options
-    };
-    memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
-    memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
-    memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
-    memcpy(&res.options, opts, sizeof(opts));
-    res.magic = pkt->dhcp->magic;
-    res.xid = pkt->dhcp->xid;
-    arp_cache_add(ifp, res.yiaddr, pkt->eth->src);
-    tx_udp(ifp, ifp->ip, mg_htons(67), op == 1 ? ~0U : res.yiaddr, mg_htons(68),
-           &res, sizeof(res));
+    q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
+    n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
+    fin = clk / ((q + 1) * (n + 1));
+    mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
+           0;  // mint + (mfrac / 1024); MFRAC not supported
+    psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
+    vco = (uint32_t) ((uint64_t) fin * mdiv);
+    return vco / (psysdiv + 1);
   }
-}
-
-static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
-                                     bool lsn) {
-  struct mg_connection *c = NULL;
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
-    if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
-        lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
-      break;
+  uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
+  if (oscsrc == 0) {
+    clk = piosc;
+  } else if (oscsrc == 3) {
+    clk = mosc;
+  } else {
+    MG_ERROR(("Unsupported clock source"));
   }
-  return c;
+  uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
+  return clk / (osysdiv + 1);
 }
 
-static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
-  struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
-  if (c == NULL) {
-    // No UDP listener on this port. Should send ICMP, but keep silent.
-  } else if (c != NULL) {
-    c->rem.port = pkt->udp->sport;
-    c->rem.ip = pkt->ip->src;
-    if (c->recv.len >= MG_MAX_RECV_SIZE) {
-      mg_error(c, "max_recv_buf_size reached");
-    } else if (c->recv.size - c->recv.len < pkt->pay.len &&
-               !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
-      mg_error(c, "oom");
-    } else {
-      memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len);
-      c->recv.len += pkt->pay.len;
-      mg_call(c, MG_EV_READ, &pkt->pay.len);
+//  Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per
+//  802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be
+//  derived from the PIOSC (internal RC), and it can go above  specs, the
+//  datasheets specify a range of frequencies and activate one of a series of
+//  dividers to keep the MDC clock safely below 2.5MHz. We guess a divider
+//  setting based on SYSCLK with a +5% drift. If the user uses a different clock
+//  from our defaults, needs to set the macros on top Valid for TM4C129x (20.7)
+//  (4.5% worst case drift)
+// The PHY receives the main oscillator (MOSC) (20.3.1)
+static int guess_mdc_cr(void) {
+  uint8_t crs[] = {2, 3, 0, 1};      // EMAC->MACMIIAR::CR values
+  uint8_t div[] = {16, 26, 42, 62};  // Respective HCLK dividers
+  uint32_t sysclk = get_sysclk();    // Guess system SYSCLK
+  int result = -1;                   // Invalid CR value
+  if (sysclk < 25000000) {
+    MG_ERROR(("SYSCLK too low"));
+  } else {
+    for (int i = 0; i < 4; i++) {
+      if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
+        result = crs[i];
+        break;
+      }
     }
+    if (result < 0) MG_ERROR(("SYSCLK too high"));
   }
+  MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
+  return result;
 }
 
-static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip, uint8_t flags,
-                     uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
-                     const void *buf, size_t len) {
-  struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
-  struct tcp *tcp = (struct tcp *) (ip + 1);
-  memset(tcp, 0, sizeof(*tcp));
-  if (buf != NULL && len) memmove(tcp + 1, buf, len);
-  tcp->sport = sport;
-  tcp->dport = dport;
-  tcp->seq = seq;
-  tcp->ack = ack;
-  tcp->flags = flags;
-  tcp->win = mg_htons(8192);
-  tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
-  uint32_t cs = 0;
-  uint16_t n = (uint16_t) (sizeof(*tcp) + len);
-  uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
-  cs = csumup(cs, tcp, n);
-  cs = csumup(cs, &ip->src, sizeof(ip->src));
-  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-  cs = csumup(cs, pseudo, sizeof(pseudo));
-  tcp->csum = csumfin(cs);
-  return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
-}
+static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_tm4c_data *d =
+      (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
+  s_ifp = ifp;
 
-static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt, uint8_t flags,
-                         uint32_t seq, const void *buf, size_t len) {
-  uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
-  return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport, pkt->tcp->sport, seq,
-                mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
-}
+  // Init RX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = MG_BIT(31);                            // Own
+    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14);       // 2nd address chained
+    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
+    s_rxdesc[i][3] =
+        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+    // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
+  }
 
-static void settmout(struct mg_connection *c, uint8_t type) {
-  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
-  struct connstate *s = (struct connstate *) (c + 1);
-  unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS : MIP_TCP_KEEPALIVE_MS;
-  s->timer = ifp->now + n;
-  s->ttype = type;
-  MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
-}
+  // Init TX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
+    s_txdesc[i][3] =
+        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+  }
 
-static struct mg_connection *accept_conn(struct mg_connection *lsn,
-                                         struct pkt *pkt) {
-  struct mg_connection *c = mg_alloc_conn(lsn->mgr);
-  struct connstate *s = (struct connstate *) (c + 1);
-  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
-  settmout(c, MIP_TTYPE_KEEPALIVE);
-  c->rem.ip = pkt->ip->src;
-  c->rem.port = pkt->tcp->sport;
-  MG_DEBUG(
-      ("%lu accepted %I:%hu", c->id, 4, &c->rem.ip, mg_ntohs(c->rem.port)));
-  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
-  c->is_accepted = 1;
-  c->is_hexdumping = lsn->is_hexdumping;
-  c->pfn = lsn->pfn;
-  c->loc = lsn->loc;
-  c->pfn_data = lsn->pfn_data;
-  c->fn = lsn->fn;
-  c->fn_data = lsn->fn_data;
-  mg_call(c, MG_EV_OPEN, NULL);
-  mg_call(c, MG_EV_ACCEPT, NULL);
-  return c;
+  EMAC->EMACDMABUSMOD |= MG_BIT(0);                            // Software reset
+  while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1);  // Wait until done
+
+  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
+  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
+  EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
+
+  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
+  // hardware checksum. Therefore, descriptor size is 4, not 8
+  // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | MG_BIT(25);
+  EMAC->EMACIM = MG_BIT(3) | MG_BIT(9);  // Mask timestamp & PMT IT
+  EMAC->EMACFLOWCTL = MG_BIT(7);      // Disable zero-quanta pause
+  // EMAC->EMACFRAMEFLTR = MG_BIT(31);   // Receive all
+  // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
+  emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15));  // Reset internal PHY (EPHY)
+  emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12));  // Set autonegotiation
+  EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
+  EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors
+  EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16);                    // RIE, NIE
+  EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14);   // RE, TE, Duplex, Fast
+  EMAC->EMACDMAOPMODE =
+      MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25);  // SR, ST, TSF, RSF
+  EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
+  EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
+                     ((uint32_t) ifp->mac[2] << 16) |
+                     ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
+  // NOTE(scaprile) There are 3 additional slots for filtering, disabled by
+  // default. This also applies to the STM32 driver (at least for F7)
+  return true;
 }
 
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
-  struct connstate *s = (struct connstate *) (c + 1);
-  size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
-  if (len + max_headers_len > ifp->tx.len) len = ifp->tx.len - max_headers_len;
-  if (tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
-             mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
-    s->seq += (uint32_t) len;
-    if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
+static uint32_t s_txno;
+static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
+                                      struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // fail
+  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
+    ifp->nerr++;
+    MG_ERROR(("No descriptors available"));
+    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
+    // EMAC->EMACDMARIS);
+    len = 0;  // fail
   } else {
-    return MG_IO_ERR;
+    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
+    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
+    s_txdesc[s_txno][0] =
+        MG_BIT(20) | MG_BIT(28) | MG_BIT(29) | MG_BIT(30);  // Chain,FS,LS,IC
+    s_txdesc[s_txno][0] |= MG_BIT(31);  // Set OWN bit - let DMA take over
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
   }
-  return (long) len;
+  EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5);  // Clear any prior TU/UNF
+  EMAC->EMACTXPOLLD = 0;               // and resume
+  return len;
+  (void) ifp;
 }
 
-long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  if (s->raw.len == 0) return MG_IO_WAIT;
-  if (len > s->raw.len) len = s->raw.len;
-  memcpy(buf, s->raw.buf, len);
-  mg_iobuf_del(&s->raw, 0, len);
-  MG_DEBUG(("%lu", len));
-  return (long) len;
+static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
+  uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
+  bool up = (bmsr & MG_BIT(2)) ? 1 : 0;
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
+    // tmp = reg with flags set to the most likely situation: 100M full-duplex
+    // if(link is slow or half) set flags otherwise
+    // reg = tmp
+    uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11);  // 100M, Full-duplex
+    if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14);                 // 10M
+    if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11);          // Half-duplex
+    EMAC->EMACCFG = emaccfg;  // IRQ handler does not fiddle with this register
+    MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10,
+              emaccfg & MG_BIT(11) ? "full" : "half"));
+  }
+  return up;
 }
 
-static void read_conn(struct mg_connection *c, struct pkt *pkt) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
-  uint32_t seq = mg_ntohl(pkt->tcp->seq);
-  s->raw.align = c->recv.align;
-  if (pkt->tcp->flags & TH_FIN) {
-    s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq = mg_htonl(pkt->tcp->ack);
-    c->is_closing = 1;
-  } else if (pkt->pay.len == 0) {
-    // TODO(cpq): handle this peer's ACK
-  } else if (seq != s->ack) {
-    // TODO(cpq): peer sent us SEQ which we don't expect. Retransmit rather
-    // than close this connection
-    mg_error(c, "SEQ != ACK: %x %x", seq, s->ack);
-  } else if (io->size - io->len < pkt->pay.len &&
-             !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
-    mg_error(c, "oom");
-  } else {
-    // Copy TCP payload into the IO buffer. If the connection is plain text, we
-    // copy to c->recv. If the connection is TLS, this data is encrypted,
-    // therefore we copy that encrypted data to the s->raw iobuffer instead,
-    // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
-    // call back mg_io_recv() which grabs raw data from s->raw
-    memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
-    io->len += pkt->pay.len;
-
-    MG_DEBUG(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
-    // Advance ACK counter
-    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
-#if 0
-    // Send ACK immediately
-    MG_DEBUG(("  imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
-    tx_tcp((struct mip_if *) c->mgr->priv, c->rem.ip, TH_ACK, c->loc.port,
-           c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
-#else
-    // if not already running, setup a timer to send an ACK later
-    if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
-#endif
-
-    if (c->is_tls) {
-      // TLS connection. Make room for decrypted data in c->recv
-      io = &c->recv;
-      if (io->size - io->len < pkt->pay.len &&
-          !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
-        mg_error(c, "oom");
-      } else {
-        // Decrypt data directly into c->recv
-        long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
-        if (n == MG_IO_ERR) {
-          mg_error(c, "TLS recv error");
-        } else if (n > 0) {
-          // Decrypted successfully - trigger MG_EV_READ
-          io->len += (size_t) n;
-          mg_call(c, MG_EV_READ, &n);
-        }
+void EMAC0_IRQHandler(void);
+static uint32_t s_rxno;
+void EMAC0_IRQHandler(void) {
+  if (EMAC->EMACDMARIS & MG_BIT(6)) {        // Frame received, loop
+    EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6);  // Clear flag
+    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
+      if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break;  // exit when done
+      if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | MG_BIT(9))) &&
+          !(s_rxdesc[s_rxno][0] & MG_BIT(15))) {  // skip partial/errored frames
+        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1));
+        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
+        //  EMAC->EMACDMARIS);
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
       }
-    } else {
-      // Plain text connection, data is already in c->recv, trigger MG_EV_READ
-      mg_call(c, MG_EV_READ, &pkt->pay.len);
+      s_rxdesc[s_rxno][0] = MG_BIT(31);
+      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
     }
   }
+  EMAC->EMACDMARIS = MG_BIT(7);  // Clear possible RU while processing
+  EMAC->EMACRXPOLLD = 0;      // and resume RX
 }
 
-static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
-  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
-  struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
-#if 0
-  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
+struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init,
+                                               mg_tcpip_driver_tm4c_tx, NULL,
+                                               mg_tcpip_driver_tm4c_up};
 #endif
-  if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN | TH_ACK)) {
-    s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
-    tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
-    c->is_connecting = 0;             // Client connected
-    settmout(c, MIP_TTYPE_KEEPALIVE);
-    mg_call(c, MG_EV_CONNECT, NULL);  // Let user know
-  } else if (c != NULL && c->is_connecting) {
-    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else if (c != NULL && pkt->tcp->flags & TH_RST) {
-    mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
-  } else if (c != NULL) {
-#if 0
-    MG_DEBUG(("%lu %d %I:%hu -> %I:%hu", c->id, (int) pkt->raw.len,
-              4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
-              4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
-    mg_hexdump(pkt->pay.buf, pkt->pay.len);
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/w5500.c"
 #endif
-    s->tmiss = 0;                      // Reset missed keep-alive counter
-    if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
-      settmout(c, MIP_TTYPE_KEEPALIVE);  // unless a former ACK timeout is pending
-    read_conn(c, pkt);                 // Override timer with ACK timeout if needed
-  } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
-    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else if (pkt->tcp->flags & TH_RST) {
-    if (c->is_accepted) mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
-    // ignore RST if not connected
-  } else if (pkt->tcp->flags & TH_SYN) {
-    // Use peer's source port as ISN, in order to recognise the handshake
-    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
-    tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
-  } else if (pkt->tcp->flags & TH_FIN) {
-    tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
-    accept_conn(c, pkt);
-  } else if (!c->is_accepted ) {  // no peer
-    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else {
-    // MG_DEBUG(("dropped silently.."));
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500
+
+enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
+
+static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr,
+                      bool wr, void *buf, size_t len) {
+  size_t i;
+  uint8_t *p = (uint8_t *) buf;
+  uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
+                   (uint8_t) ((block << 3) | (wr ? 4 : 0))};
+  s->begin(s->spi);
+  for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
+  for (i = 0; i < len; i++) {
+    uint8_t r = s->txn(s->spi, p[i]);
+    if (!wr) p[i] = r;
   }
+  s->end(s->spi);
 }
 
-static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
-  //  MG_DEBUG(("IP %d", (int) pkt->pay.len));
-  if (pkt->ip->proto == 1) {
-    pkt->icmp = (struct icmp *) (pkt->ip + 1);
-    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
-    mkpay(pkt, pkt->icmp + 1);
-    rx_icmp(ifp, pkt);
-  } else if (pkt->ip->proto == 17) {
-    pkt->udp = (struct udp *) (pkt->ip + 1);
-    if (pkt->pay.len < sizeof(*pkt->udp)) return;
-    mkpay(pkt, pkt->udp + 1);
-    if (pkt->udp->dport == mg_htons(68)) {
-      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
-      mkpay(pkt, pkt->dhcp + 1);
-      rx_dhcp_client(ifp, pkt);
-    } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) {
-      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
-      mkpay(pkt, pkt->dhcp + 1);
-      rx_dhcp_server(ifp, pkt);
-    } else {
-      rx_udp(ifp, pkt);
+// clang-format off
+static  void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
+static  void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
+static  void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
+static  void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
+static  uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
+static  uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
+// clang-format on
+
+static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
+  uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2;     // Read recv len
+  while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2;  // Until it is stable
+  // printf("RSR: %d\n", (int) n);
+  if (n > 0) {
+    uint16_t ptr = w5500_r2(s, W5500_S0, 0x28);  // Get read pointer
+    n = w5500_r2(s, W5500_RX0, ptr);             // Read frame length
+    if (n <= len + 2 && n > 1) {
+      r = (uint16_t) (n - 2);
+      w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
     }
-  } else if (pkt->ip->proto == 6) {
-    pkt->tcp = (struct tcp *) (pkt->ip + 1);
-    if (pkt->pay.len < sizeof(*pkt->tcp)) return;
-    mkpay(pkt, pkt->tcp + 1);
-    uint16_t iplen = mg_ntohs(pkt->ip->len);
-    uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
-    if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
-    rx_tcp(ifp, pkt);
+    w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n));  // Advance read pointer
+    w5500_w1(s, W5500_S0, 1, 0x40);                     // Sock0 CR -> RECV
+    // printf("  RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
   }
+  return r;
 }
 
-static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("IP %d", (int) len));
-  if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
-    pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
-    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
-    mkpay(pkt, pkt->icmp + 1);
-    rx_icmp(ifp, pkt);
-  } else if (pkt->ip6->proto == 17) {
-    pkt->udp = (struct udp *) (pkt->ip6 + 1);
-    if (pkt->pay.len < sizeof(*pkt->udp)) return;
-    // MG_DEBUG(("  UDP %u %u -> %u", len, mg_htons(udp->sport),
-    // mg_htons(udp->dport)));
-    mkpay(pkt, pkt->udp + 1);
+static size_t w5500_tx(const void *buf, size_t buflen,
+                       struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
+  uint16_t i, ptr, n = 0, len = (uint16_t) buflen;
+  while (n < len) n = w5500_r2(s, W5500_S0, 0x20);      // Wait for space
+  ptr = w5500_r2(s, W5500_S0, 0x24);                    // Get write pointer
+  w5500_wn(s, W5500_TX0, ptr, (void *) buf, len);       // Write data
+  w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len));  // Advance write pointer
+  w5500_w1(s, W5500_S0, 1, 0x20);                       // Sock0 CR -> SEND
+  for (i = 0; i < 40; i++) {
+    uint8_t ir = w5500_r1(s, W5500_S0, 2);  // Read S0 IR
+    if (ir == 0) continue;
+    // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
+    w5500_w1(s, W5500_S0, 2, ir);  // Write S0 IR: clear it!
+    if (ir & 8) len = 0;           // Timeout. Report error
+    if (ir & (16 | 8)) break;      // Stop on SEND_OK or timeout
   }
+  return len;
 }
 
-static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
-  const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
-  struct pkt pkt;
-  memset(&pkt, 0, sizeof(pkt));
-  pkt.raw.ptr = (char *) buf;
-  pkt.raw.len = len;
-  pkt.eth = (struct eth *) buf;
-  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
-  if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
-      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
-    // Not for us. Drop silently
-  } else if (pkt.eth->type == mg_htons(0x806)) {
-    pkt.arp = (struct arp *) (pkt.eth + 1);
-    if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;  // Truncated
-    rx_arp(ifp, &pkt);
-  } else if (pkt.eth->type == mg_htons(0x86dd)) {
-    pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
-    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;  // Truncated
-    if ((pkt.ip6->ver >> 4) != 0x6) return;                         // Not IP
-    mkpay(&pkt, pkt.ip6 + 1);
-    rx_ip6(ifp, &pkt);
-  } else if (pkt.eth->type == mg_htons(0x800)) {
-    pkt.ip = (struct ip *) (pkt.eth + 1);
-    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
-    // Truncate frame to what IP header tells us
-    if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
-      pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
-    }
-    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
-    if ((pkt.ip->ver >> 4) != 4) return;                           // Not IP
-    mkpay(&pkt, pkt.ip + 1);
-    rx_ip(ifp, &pkt);
-  } else {
-    MG_DEBUG(("  Unknown eth type %x", mg_htons(pkt.eth->type)));
+static bool w5500_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
+  s->end(s->spi);
+  w5500_w1(s, W5500_CR, 0, 0x80);     // Reset chip: CR -> 0x80
+  w5500_w1(s, W5500_CR, 0x2e, 0);     // CR PHYCFGR -> reset
+  w5500_w1(s, W5500_CR, 0x2e, 0xf8);  // CR PHYCFGR -> set
+  // w5500_wn(s, W5500_CR, 9, s->mac, 6);      // Set source MAC
+  w5500_w1(s, W5500_S0, 0x1e, 16);          // Sock0 RX buf size
+  w5500_w1(s, W5500_S0, 0x1f, 16);          // Sock0 TX buf size
+  w5500_w1(s, W5500_S0, 0, 4);              // Sock0 MR -> MACRAW
+  w5500_w1(s, W5500_S0, 1, 1);              // Sock0 CR -> OPEN
+  return w5500_r1(s, W5500_S0, 3) == 0x42;  // Sock0 SR == MACRAW
+}
+
+static bool w5500_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data;
+  uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
+  return phycfgr & 1;  // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
+}
+
+struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx,
+                                                w5500_up};
+#endif
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/xmc.c"
+#endif
+
+
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC
+
+struct ETH_GLOBAL_TypeDef {
+  volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH,
+  HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION,
+  DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2],
+  INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW,
+  MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW,
+  MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL,
+  MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK,
+  MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2,
+  RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK,
+  RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30],
+  RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT,
+  SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS,
+  SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE,
+  TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS,
+  SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS,
+  PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND,
+  RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS,
+  TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE,
+  INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER,
+  RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS,
+  RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR,
+  CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS,
+  CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE;
+};
+
+#undef ETH0
+#define ETH0  ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL)
+
+#define ETH_PKT_SIZE 1536 // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 4           // Descriptor size (words)
+
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
+static uint8_t s_txno;                           // Current TX descriptor
+static uint8_t s_rxno;                           // Current RX descriptor
+
+static struct mg_tcpip_if *s_ifp;  // MIP interface
+enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
+
+static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
+                        ((uint32_t)addr << 11) |
+                        ((uint32_t)reg << 6) | 1;
+  while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
+  return (uint16_t)(ETH0->GMII_DATA & 0xffff);
+}
+
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  ETH0->GMII_DATA  = val;
+  ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) |
+                        ((uint32_t)addr << 11) |
+                        ((uint32_t)reg << 6) | 3;
+  while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0;
+}
+
+static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data *d) {
+  if (d->mdc_cr == -1) {
+    // assume ETH clock is 60MHz by default
+    // then according to 13.2.8.1, we need to set value 3
+    return 3;
   }
+
+  return d->mdc_cr;
 }
 
-static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
-  if (ifp == NULL || ifp->driver == NULL) return;
-  bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, uptime_ms);
-  ifp->now = uptime_ms;
+static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_xmc_data *d =
+      (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
+  s_ifp = ifp;
 
-  // Handle physical interface up/down status
-  if (expired_1000ms && ifp->driver->up) {
-    bool up = ifp->driver->up(ifp);
-    bool current = ifp->state != MIP_STATE_DOWN;
-    if (up != current) {
-      ifp->state = up == false               ? MIP_STATE_DOWN
-                   : ifp->enable_dhcp_client ? MIP_STATE_UP
-                                             : MIP_STATE_READY;
-      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
-      onstatechange(ifp);
+  // reset MAC
+  ETH0->BUS_MODE |= 1;
+  while (ETH0->BUS_MODE & 1) (void) 0;
+
+  // set clock rate
+  ETH0->GMII_ADDRESS = get_clock_rate(d) << 2;
+
+  // init phy
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
+
+  // configure MAC: DO, DM, FES, TC
+  ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14) | MG_BIT(24);
+
+  // set the MAC address
+  ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
+  ETH0->MAC_ADDRESS0_LOW = 
+        MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
+
+  // Configure the receive filter
+  ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC
+  // Disable flow control
+  ETH0->FLOW_CONTROL = 0;
+  // Enable store and forward mode
+  ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF
+
+  // Configure DMA bus mode (AAL, USP, RPBL, PBL)
+  ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) |  (32 << 8);
+
+  // init RX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor
+    s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE;
+    s_rxdesc[i][2] = (uint32_t) s_rxbuf[i];
+    if (i == ETH_DESC_CNT - 1) {
+      s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0];
+    } else {
+      s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0];
     }
   }
-  if (ifp->state == MIP_STATE_DOWN) return;
-  // if (expired_1000ms) arp_cache_dump(ifp->arp_cache);
+  ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0];
 
-  if (ifp->ip == 0 && expired_1000ms) {
-    tx_dhcp_discover(ifp);  // If IP not configured, send DHCP
-  } else if (ifp->enable_dhcp_client == false && expired_1000ms && ifp->gw &&
-             arp_cache_find(ifp, ifp->gw) == NULL) {
-    arp_ask(ifp, ifp->gw);  // If GW's MAC address in not in ARP cache
+  // init TX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20);
+    s_txdesc[i][2] = (uint32_t) s_txbuf[i];
+    if (i == ETH_DESC_CNT - 1) {
+      s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0];
+    } else {
+      s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0];
+    }
   }
+  ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_txdesc[0][0];
 
-  // Read data from the network
-  size_t len = ifp->driver->rx((void *) ifp->rx.ptr, ifp->rx.len, ifp);
-  mip_rx(ifp, (void *) ifp->rx.ptr, len);
-  qp_mark(QP_FRAMEDONE, (int) q_space(&ifp->queue));
+  // Clear interrupts
+  ETH0->STATUS = 0xFFFFFFFF;
 
-  // Process timeouts
-  for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c = c->next) {
-    if (c->is_udp || c->is_listening) continue;
-    if (c->is_connecting || c->is_resolving) continue;
-    struct connstate *s = (struct connstate *) (c + 1);
-    if (uptime_ms > s->timer) {
-      if (s->ttype == MIP_TTYPE_ACK) {
-        MG_DEBUG(("%lu ack %x %x", c->id, s->seq, s->ack));
-        tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
-               mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
-      } else {
-        if (s->tmiss++ > 2) {
-          mg_error(c, "keepalive");
-        } else {
-          MG_DEBUG(("%lu keepalive", c->id));
-          tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port,
-                 mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
-        }
-      }
-      settmout(c, MIP_TTYPE_KEEPALIVE);
-    }
-  }
-#ifdef MIP_QPROFILE
-  qp_log();
-#endif
+  // Disable MAC interrupts
+  ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF;
+  ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
+  ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF;
+  ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM
+
+  //Enable interrupts (NIE, RIE, TIE)
+  ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0);
+
+  // Enable MAC transmission and reception (TE, RE)
+  ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2);
+  // Enable DMA transmission and reception (ST, SR)
+  ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1);
+  return true;
 }
 
-// This function executes in interrupt context, thus it should copy data
-// somewhere fast. Note that newlib's malloc is not thread safe, thus use
-// our lock-free queue with preallocated buffer to copy data and return asap
-void mip_qwrite(void *buf, size_t len, struct mip_if *ifp) {
-  if (q_write(&ifp->queue, buf, len)) {
-    qp_mark(QP_FRAMEPUSHED, (int) q_space(&ifp->queue));
+static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len,
+                                        struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // Frame is too big
+  } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) {
+    ifp->nerr++;
+    MG_ERROR(("No free descriptors"));
+    len = 0;  // All descriptors are busy, fail
   } else {
-    ifp->dropped++;
-    qp_mark(QP_FRAMEDROPPED, ifp->dropped);
-    MG_ERROR(("dropped %d", (int) len));
+    memcpy(s_txbuf[s_txno], buf, len);
+    s_txdesc[s_txno][1] = len;
+    // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH)
+    s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) | MG_BIT(20);
+    s_txdesc[s_txno][0] |= MG_BIT(31);  // OWN bit: handle control to DMA
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
   }
-}
 
-size_t mip_qread(void *buf, struct mip_if *ifp) {
-  size_t len = q_read(&ifp->queue, buf);
-  qp_mark(QP_FRAMEPOPPED, (int) q_space(&ifp->queue));
+  // Resume processing
+  ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable
+  ETH0->TRANSMIT_POLL_DEMAND = 0;
   return len;
 }
 
-size_t mip_driver_rx(void *buf, size_t len, struct mip_if *ifp) {
-  return mip_qread((void *) ifp->rx.ptr, ifp);
-  (void) len, (void) buf;
-}
+static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_xmc_data *d =
+      (struct mg_tcpip_driver_xmc_data *) ifp->driver_data;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 100,
+              full_duplex ? "full" : "half"));
+  }
+  return up;
+}
+
+void ETH0_IRQHandler(void);
+void ETH0_IRQHandler(void) {
+  uint32_t irq_status = ETH0->STATUS;
+
+  // check if a frame was received
+  if (irq_status & MG_BIT(6)) {
+    for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
+      if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) {
+        size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16;
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
+        s_rxdesc[s_rxno][0] = MG_BIT(31);   // OWN bit: handle control to DMA
+        // Resume processing
+        ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI
+        ETH0->RECEIVE_POLL_DEMAND = 0;
+        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+      }
+    }
+    ETH0->STATUS = MG_BIT(6);
+  }
 
-void mip_init(struct mg_mgr *mgr, struct mip_if *ifp) {
-  if (ifp->driver->init && !ifp->driver->init(ifp)) {
-    MG_ERROR(("driver init failed"));
-  } else {
-    size_t maxpktsize = 1540;
-    ifp->rx.ptr = (char *) calloc(1, maxpktsize), ifp->rx.len = maxpktsize;
-    ifp->tx.ptr = (char *) calloc(1, maxpktsize), ifp->tx.len = maxpktsize;
-    if (ifp->queue.len) ifp->queue.buf = (uint8_t *) calloc(1, ifp->queue.len);
-    ifp->timer_1000ms = mg_millis();
-    arp_cache_init(ifp->arp_cache, MIP_ARP_ENTRIES, 12);
-    mgr->priv = ifp;
-    ifp->mgr = mgr;
-    mgr->extraconnsize = sizeof(struct connstate);
-    if (ifp->ip == 0) ifp->enable_dhcp_client = true;
-#ifdef MIP_QPROFILE
-    qp_init();
-#endif
+  // clear Successful transmission interrupt
+  if (irq_status & 1) {
+    ETH0->STATUS = 1;
   }
-}
 
-void mip_free(struct mip_if *ifp) {
-  free((char *) ifp->rx.ptr);
-  free((char *) ifp->tx.ptr);
+  // clear normal interrupt
+  if (irq_status & MG_BIT(16)) {
+    ETH0->STATUS = MG_BIT(16);
+  }
 }
 
-int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d, bool udp) {
-  (void) m, (void) fn, (void) d, (void) udp;
-  MG_ERROR(("Not implemented"));
-  return -1;
-}
+struct mg_tcpip_driver mg_tcpip_driver_xmc = {
+    mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL,
+    mg_tcpip_driver_xmc_up};
+#endif
 
-#if 0
-static uint16_t mkeport(void) {
-  uint16_t a = 0, b = mg_millis() & 0xffffU, c = MIP_ETHEMERAL_PORT;
-  mg_random(&a, sizeof(a));
-  c += (a ^ b) % (0xffffU - MIP_ETHEMERAL_PORT);
-  return c;
-}
+#ifdef MG_ENABLE_LINES
+#line 1 "src/drivers/xmc7.c"
 #endif
 
-void mg_connect_resolved(struct mg_connection *c) {
-  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
-  c->is_resolving = 0;
-  if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport = MIP_ETHEMERAL_PORT;
-  c->loc.ip = ifp->ip;
-  c->loc.port = mg_htons(ifp->eport++);
-  MG_DEBUG(("%lu %I:%hu->%I:%hu", c->id, 4, &c->loc.ip, mg_ntohs(c->loc.port),
-            4, &c->rem.ip, mg_ntohs(c->rem.port)));
-  mg_call(c, MG_EV_RESOLVE, NULL);
-  if (c->is_udp) {
-    mg_call(c, MG_EV_CONNECT, NULL);
-  } else {
-    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
-    tx_tcp(ifp, c->rem.ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, 0);
-    c->is_connecting = 1;
-  }
-}
 
-bool mg_open_listener(struct mg_connection *c, const char *url) {
-  c->loc.port = mg_htons(mg_url_port(url));
-  return true;
-}
+#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7
+
+struct ETH_Type {
+  volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL,
+      NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG,
+      TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS,
+      INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT, PAUSE_TIME,
+      TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU, JUMBO_MAX_LENGTH,
+      EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL,
+      INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM, HASH_TOP,
+      SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP,
+      SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP,
+      SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER,
+      STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM,
+      MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST,
+      TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC,
+      TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC,
+      DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP,
+      FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED,
+      FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256,
+      FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS,
+      SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS,
+      LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM,
+      OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED,
+      PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65, FRAMES_RXED_128,
+      FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024, FRAMES_RXED_1519,
+      UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS,
+      RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS, RX_RESOURCE_ERRORS,
+      RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS,
+      AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC, TSU_TIMER_MSB_SEC,
+      TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC, TSU_TIMER_SEC,
+      TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC,
+      TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC,
+      TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL,
+      PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP,
+      PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS, RESERVED6[8],
+      TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3, RESERVED7,
+      RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1,
+      DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4, DESIGNCFG_DEBUG5,
+      DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8, DESIGNCFG_DEBUG9,
+      DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP,
+      RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS,
+      INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS, RESERVED11,
+      TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11],
+      TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR,
+      RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15,
+      DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3, RESERVED16[3],
+      DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A, CBS_IDLESLOPE_Q_B,
+      UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL, UPPER_RX_Q_BASE_ADDR,
+      RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3,
+      RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5;
+};
 
-static void write_conn(struct mg_connection *c) {
-  long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
-                       : mg_io_send(c, c->send.buf, c->send.len);
-  if (len > 0) {
-    mg_iobuf_del(&c->send, 0, (size_t) len);
-    mg_call(c, MG_EV_WRITE, &len);
-  }
+#define ETH0 ((struct ETH_Type *) 0x40490000)
+
+#define ETH_PKT_SIZE 1536  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 2           // Descriptor size (words)
+
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];  // TX descriptors
+static uint8_t s_txno;                           // Current TX descriptor
+static uint8_t s_rxno;                           // Current RX descriptor
+
+static struct mg_tcpip_if *s_ifp;  // MIP interface
+enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 };
+
+static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  // WRITE1, READ OPERATION, PHY, REG, WRITE10
+  ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf) << 24) |
+                         ((reg & 0x1f) << 18) | MG_BIT(17);
+  while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
+  return ETH0->PHY_MANAGEMENT & 0xffff;
 }
 
-static void close_conn(struct mg_connection *c) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  mg_iobuf_free(&s->raw);  // For TLS connections, release raw data
-  if (c->is_udp == false && c->is_listening == false) {   // For TCP conns,
-    struct mip_if *ifp = (struct mip_if *) c->mgr->priv;  // send TCP FIN
-    tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
-  }
-  mg_close_conn(c);
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
+  ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf) << 24) |
+                         ((reg & 0x1f) << 18) | MG_BIT(17) | val;
+  while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0;
 }
 
-static bool can_write(struct mg_connection *c) {
-  return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
-         c->is_tls_hs == 0;
+static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) {
+  // see ETH0 -> NETWORK_CONFIG register
+  (void) d;
+  return 3;
 }
 
-void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
-  struct mg_connection *c, *tmp;
-  uint64_t now = mg_millis();
-  mip_poll((struct mip_if *) mgr->priv, now);
-  mg_timer_poll(&mgr->timers, now);
-  for (c = mgr->conns; c != NULL; c = tmp) {
-    tmp = c->next;
-    mg_call(c, MG_EV_POLL, &now);
-    MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't',
-                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
-                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
-    if (c->is_tls_hs) mg_tls_handshake(c);
-    if (can_write(c)) write_conn(c);
-    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
-    if (c->is_closing) close_conn(c);
+static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_xmc7_data *d =
+      (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
+  s_ifp = ifp;
+
+  // enable controller, set RGMII mode
+  ETH0->CTL = MG_BIT(31) | 2;
+
+  uint32_t cr = get_clock_rate(d);
+  // set NSP change, ignore RX FCS, data bus width, clock rate
+  // frame length 1536, full duplex, speed
+  ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) |
+                         ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) |
+                         MG_BIT(1) | MG_BIT(0);
+
+  // config DMA settings: Force TX burst, Discard on Error, set RX buffer size
+  // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH
+  ETH0->DMA_CONFIG =
+      MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8) | 4;
+
+  // initialize descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];
+    if (i == ETH_DESC_CNT - 1) {
+      s_rxdesc[i][0] |= MG_BIT(1);  // mark last descriptor
+    }
+
+    s_txdesc[i][0] = (uint32_t) s_txbuf[i];
+    s_txdesc[i][1] = MG_BIT(31);  // OWN descriptor
+    if (i == ETH_DESC_CNT - 1) {
+      s_txdesc[i][1] |= MG_BIT(30);  // mark last descriptor
+    }
   }
-  (void) ms;
+  ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc;
+  ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc;
+
+  // disable other queues
+  ETH0->TRANSMIT_Q2_PTR = 1;
+  ETH0->TRANSMIT_Q1_PTR = 1;
+  ETH0->RECEIVE_Q2_PTR = 1;
+  ETH0->RECEIVE_Q1_PTR = 1;
+
+  // enable interrupts (TX and RX complete)
+  ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1);
+
+  // set MAC address
+  ETH0->SPEC_ADD1_BOTTOM =
+      ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 | ifp->mac[0];
+  ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4];
+
+  // enable MDIO, TX, RX
+  ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2);
+
+  // start transmission
+  ETH0->NETWORK_CONTROL |= MG_BIT(9);
+
+  // init phy
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC);
+
+  (void) d;
+  return true;
 }
 
-bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
-  bool res = false;
-  if (ifp->ip == 0 || ifp->state != MIP_STATE_READY) {
-    mg_error(c, "net down");
-  } else if (c->is_udp) {
-    tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len);
-    res = true;
+static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len,
+                                      struct mg_tcpip_if *ifp) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    MG_ERROR(("Frame too big, %ld", (long) len));
+    len = 0;  // Frame is too big
+  } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) {
+    ifp->nerr++;
+    MG_ERROR(("No free descriptors"));
+    len = 0;  // All descriptors are busy, fail
   } else {
-    res = mg_iobuf_add(&c->send, c->send.len, buf, len);
-  }
-  return res;
-}
+    memcpy(s_txbuf[s_txno], buf, len);
+    s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30) : 0) |
+                          MG_BIT(15) | len;  // Last buffer and length
 
-#ifdef MIP_QPROFILE
+    ETH0->NETWORK_CONTROL |= MG_BIT(9);  // enable transmission
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
+  }
 
-#pragma pack(push, 1)
-struct qpentry {
-  uint32_t timestamp;
-  uint16_t type;
-  uint16_t len;
-};
-#pragma pack(pop)
+  MG_DSB();
+  ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS;
+  ETH0->NETWORK_CONTROL |= MG_BIT(9);  // enable transmission
 
-static struct queue qp;
+  return len;
+}
 
-// This is called from IRQ and main contexts; two producers, single consumer
-// TODO(scaprile): avoid concurrency issues (2 queues ?)
-void qp_mark(unsigned int type, int len) {
-  static bool ovf = false;
-  static uint16_t irq_ctr = 0, drop_ctr = 0;
-  struct qpentry e = {.timestamp = (uint32_t) mg_millis(),
-                      .type = (uint16_t) type,
-                      .len = (uint16_t) len};
-  if (type == QP_IRQTRIGGERED) e.len = ++irq_ctr;
-  if (ovf) {
-    e.type = (uint16_t) QP_QUEUEOVF;
-    e.len = drop_ctr;
+static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) {
+  struct mg_tcpip_driver_xmc7_data *d =
+      (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data;
+  uint8_t speed = MG_PHY_SPEED_10M;
+  bool up = false, full_duplex = false;
+  struct mg_phy phy = {eth_read_phy, eth_write_phy};
+  up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
+  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
+    if (speed == MG_PHY_SPEED_1000M) {
+		  ETH0->NETWORK_CONFIG |= MG_BIT(10);
+	  }
+    MG_DEBUG(("Link is %uM %s-duplex",
+              speed == MG_PHY_SPEED_10M ? 10 : 
+              (speed == MG_PHY_SPEED_100M ? 100 : 1000),
+              full_duplex ? "full" : "half"));
   }
-  ovf = !q_write(&qp, &e, sizeof(e));
+  (void) d;
+  return up;
 }
 
-void qp_log(void) {
-  struct qpentry e;
-  const char *titles[] = {"IRQ ", "PUSH", "POP ", "DONE", "DROP", "OVFL"};
-  for (int i = 0; i < 10 && q_read(&qp, &e); i++) {
-    MG_INFO(("%lx %s %u", e.timestamp, titles[e.type], e.len));
+void ETH_IRQHandler(void) {
+  uint32_t irq_status = ETH0->INT_STATUS;
+  if (irq_status & MG_BIT(1)) {
+    for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
+      if (s_rxdesc[s_rxno][0] & MG_BIT(0)) {
+        size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1);
+        //MG_INFO(("Receive complete: %ld bytes", len));
+        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
+        s_rxdesc[s_rxno][0] &= ~MG_BIT(0);  // OWN bit: handle control to DMA
+        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
+      }
+    }
   }
-}
 
-void qp_init(void) {
-  qp.len = 500 * (sizeof(size_t) + sizeof(struct qpentry));
-  qp.buf = calloc(1, qp.len);  // THERE IS NO FREE
+  ETH0->INT_STATUS = irq_status;
 }
-#endif  // MIP_QPROFILE
 
-#endif  // MG_ENABLE_MIP
+struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init,
+                                               mg_tcpip_driver_xmc7_tx, NULL,
+                                               mg_tcpip_driver_xmc7_up};
+#endif
diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
index 350aad9c..3a439a6e 100644
--- a/mongoose/mongoose.h
+++ b/mongoose/mongoose.h
@@ -1,5 +1,5 @@ 
 // Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2022 Cesanta Software Limited
+// Copyright (c) 2013-2024 Cesanta Software Limited
 // All rights reserved
 //
 // This software is dual-licensed: you can redistribute it and/or modify
@@ -15,58 +15,48 @@ 
 // Alternatively, you can license this software under a commercial
 // license, as set out in https://www.mongoose.ws/licensing/
 //
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only or commercial
 
 #ifndef MONGOOSE_H
 #define MONGOOSE_H
 
-#define MG_VERSION "7.8"
+#define MG_VERSION "7.14"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 
-#define MG_ARCH_CUSTOM 0     // User creates its own mongoose_custom.h
-#define MG_ARCH_UNIX 1       // Linux, BSD, Mac, ...
-#define MG_ARCH_WIN32 2      // Windows
-#define MG_ARCH_ESP32 3      // ESP32
-#define MG_ARCH_ESP8266 4    // ESP8266
-#define MG_ARCH_FREERTOS 5   // FreeRTOS
-#define MG_ARCH_AZURERTOS 6  // MS Azure RTOS
-#define MG_ARCH_ZEPHYR 7     // Zephyr RTOS
-#define MG_ARCH_NEWLIB 8     // Bare metal ARM
-#define MG_ARCH_RTX 9        // Keil MDK RTX
-#define MG_ARCH_TIRTOS 10    // Texas Semi TI-RTOS
-#define MG_ARCH_RP2040 11    // Raspberry Pi RP2040
+#define MG_ARCH_CUSTOM 0        // User creates its own mongoose_config.h
+#define MG_ARCH_UNIX 1          // Linux, BSD, Mac, ...
+#define MG_ARCH_WIN32 2         // Windows
+#define MG_ARCH_ESP32 3         // ESP32
+#define MG_ARCH_ESP8266 4       // ESP8266
+#define MG_ARCH_FREERTOS 5      // FreeRTOS
+#define MG_ARCH_AZURERTOS 6     // MS Azure RTOS
+#define MG_ARCH_ZEPHYR 7        // Zephyr RTOS
+#define MG_ARCH_NEWLIB 8        // Bare metal ARM
+#define MG_ARCH_CMSIS_RTOS1 9   // CMSIS-RTOS API v1 (Keil RTX)
+#define MG_ARCH_TIRTOS 10       // Texas Semi TI-RTOS
+#define MG_ARCH_RP2040 11       // Raspberry Pi RP2040
+#define MG_ARCH_ARMCC 12        // Keil MDK-Core with Configuration Wizard
+#define MG_ARCH_CMSIS_RTOS2 13  // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS)
+#define MG_ARCH_RTTHREAD 14     // RT-Thread RTOS
 
 #if !defined(MG_ARCH)
 #if defined(__unix__) || defined(__APPLE__)
 #define MG_ARCH MG_ARCH_UNIX
 #elif defined(_WIN32)
 #define MG_ARCH MG_ARCH_WIN32
-#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
-#define MG_ARCH MG_ARCH_ESP8266
-#elif defined(__ZEPHYR__)
-#define MG_ARCH MG_ARCH_ZEPHYR
-#elif defined(ESP_PLATFORM)
-#define MG_ARCH MG_ARCH_ESP32
-#elif defined(FREERTOS_IP_H)
-#define MG_ARCH MG_ARCH_FREERTOS
-#define MG_ENABLE_FREERTOS_TCP 1
-#elif defined(AZURE_RTOS_THREADX)
-#define MG_ARCH MG_ARCH_AZURERTOS
-#elif defined(PICO_TARGET_NAME)
-#define MG_ARCH MG_ARCH_RP2040
 #endif
 #endif  // !defined(MG_ARCH)
 
 #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
-#include "mongoose_custom.h"  // keep this include
+#include "mongoose_config.h"  // keep this include
 #endif
 
 #if !defined(MG_ARCH)
-#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..."
+#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in your compiler"
 #endif
 
 // http://esr.ibiblio.org/?p=5095
@@ -130,7 +120,8 @@  extern "C" {
 #include <sys/types.h>
 #include <time.h>
 
-#include <esp_timer.h>
+#include <esp_ota_ops.h>  // Use angle brackets to avoid
+#include <esp_timer.h>    // amalgamation ditching them
 
 #define MG_PATH_MAX 128
 
@@ -166,15 +157,24 @@  extern "C" {
 #if MG_ARCH == MG_ARCH_FREERTOS
 
 #include <ctype.h>
-// #include <errno.h> // Cannot include errno - might conflict with lwip!
+#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
+#include <errno.h>
+#endif
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
-#include <stdlib.h> // rand(), strtol(), atoi()
+#include <stdlib.h>  // rand(), strtol(), atoi()
 #include <string.h>
+#if defined(__ARMCC_VERSION)
+#define mode_t size_t
+#include <alloca.h>
+#include <time.h>
+#elif defined(__CCRH__)
+#else
 #include <sys/stat.h>
+#endif
 
 #include <FreeRTOS.h>
 #include <task.h>
@@ -186,7 +186,7 @@  extern "C" {
 #define calloc(a, b) mg_calloc(a, b)
 #define free(a) vPortFree(a)
 #define malloc(a) pvPortMalloc(a)
-#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
+#define strdup(s) mg_mprintf("%s", s)
 
 // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
 static inline void *mg_calloc(size_t cnt, size_t size) {
@@ -240,9 +240,34 @@  static inline int mg_mkdir(const char *path, mode_t mode) {
 #include <pico/stdlib.h>
 int mkdir(const char *, mode_t);
 #endif
-
-
-#if MG_ARCH == MG_ARCH_RTX
+

+

+#if MG_ARCH == MG_ARCH_RTTHREAD

+

+#include <rtthread.h>

+#include <ctype.h>

+#include <errno.h>

+#include <fcntl.h>

+#include <sys/socket.h>

+#include <sys/select.h>

+#include <stdarg.h>

+#include <stdbool.h>

+#include <stdint.h>

+#include <stdio.h>

+#include <stdlib.h>

+#include <string.h>